Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/arrays/13.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Php 如何从多维表单数组递归转换为相关的实体对象_Php_Arrays_Recursion - Fatal编程技术网

Php 如何从多维表单数组递归转换为相关的实体对象

Php 如何从多维表单数组递归转换为相关的实体对象,php,arrays,recursion,Php,Arrays,Recursion,我试图将多维HTML表单数组转换为具有一对多关系和嵌套一对多关系的相关实体(数据库)对象类。 考虑下面的输入(人类可读的):< /P> 我们得到了一个名为AbstractEntity的抽象类,它的主体为空,每个实体都进行了扩展,并且这些实体都有用于简单类型的公共成员变量。对于数组,其访问是私有的,有setter方法和addXX方法在数组末尾添加一个条目(这就是为什么需要反射,以及为什么我们有$method1和$method2)。此外,它还将日期和时间从非国有化的字符串解析为日期时间 我希望在OR

我试图将多维HTML表单数组转换为具有一对多关系和嵌套一对多关系的相关实体(数据库)对象类。 考虑下面的输入(人类可读的):< /P> 我们得到了一个名为
AbstractEntity
的抽象类,它的主体为空,每个实体都进行了扩展,并且这些实体都有用于简单类型的公共成员变量。对于数组,其访问是私有的,有
setter
方法和
addXX
方法在数组末尾添加一个条目(这就是为什么需要反射,以及为什么我们有
$method1
$method2
)。此外,它还将日期和时间从非国有化的
字符串
解析为
日期时间

我希望在ORM框架风格中访问它们,例如:

$order->getPosition()[0]->getBillingAddress()->firstname
这是我的工人阶级,主要工作是:

<?php
namespace MyApp\Ajax;

use MyApp\Entity\AbstractEntity;
use MyApp\Entity\Repository;

class AjaxRequest
{
    private $inputType;
    private $data;
    private $formatter;
    private $objMapping;
    private $repo;

    public function __construct()
    {
        $this->inputType = strtolower($_SERVER['REQUEST_METHOD']) === 'post' ? \INPUT_POST : \INPUT_GET;
        $this->formatter = new \IntlDateFormatter(
            'de_DE',
            \IntlDateFormatter::LONG,
            \IntlDateFormatter::SHORT,
            null,
            \IntlDateFormatter::GREGORIAN,
            'd. MMM Y HH:mm'
        );
        $this->objMapping = array(
            'order' => "MyApp\\Entity\\Order",
            'position' => "MyApp\\Entity\\Article",
            'supplier' => "MyApp\\Entity\\Supplier",
            'customer' => "MyApp\\Entity\\User",
            'billingAddress' => "MyApp\\Entity\\UserAddress",
            'shippingAddress' => "MyApp\\Entity\\UserAddress"
        );
        $this->repo = new Repository();
    }

    public function save()
    {
        $obj = $this->convertRequestToObj('order');
        $this->data['success'] = $this->repo->save($obj);
        $this->data['data'] = $obj;

        $this->jsonResponse();
    }

    private function jsonResponse()
    {
        header('Content-type: application/json');
        echo json_encode(
            array(
                'success' => $this->data['success'],
                'data' => $this->convertToPublicObjects($this->data['data'])
            )
        );
    }

    private function convertToPublicObjects($object)
    {
        $names = array();
        if (is_object($object) && !$object instanceof \DateTimeInterface) {
            $reflection = new \ReflectionClass($object);
            $columns = $reflection->getProperties();
            $methods = $reflection->getMethods(\ReflectionMethod::IS_PUBLIC);

            foreach ($columns as $column) {
                $colName = $column->getName();
                $method1 = 'get' . ucfirst($colName);
                $method2 = 'is' . ucfirst($colName);

                try {
                    if ($column->isPublic()) {
                        $names[$colName] = $column->getValue($object);
                    } else {
                        if ($reflection->hasMethod($method1) && $this->checkPublicMethods($methods, $method1)) {
                            $names[$colName] = $object->{$method1}();
                        } else {
                            if ($reflection->hasMethod($method2) && $this->checkPublicMethods($methods, $method2)) {
                                $names[$colName] = $object->{$method2}();
                            }
                        }
                    }
                } catch (\ReflectionException $ex) {
                    $names[$colName] = null;
                } catch (\TypeError $exc) {
                    $names[$colName] = null;
                }

                if (array_key_exists($colName, $names) && is_object($names[$colName])) {
                    if ($names[$colName] instanceof \DateTimeInterface) {
                        $names[$colName] = $this->formatter->format($names[$colName]);
                    } else {
                        $names[$colName] = $this->convertToPublicObjects($names[$colName]);
                    }
                } elseif (array_key_exists($colName, $names) && is_array($names[$colName])) {
                    array_walk_recursive($names[$colName], array($this, 'walkReturnArray'));
                }
            }
        }

        return $names;
    }

    private function walkReturnArray(&$item, $key)
    {
        if (is_object($item)) {
            $item = $this->convertToPublicObjects($item);
        }
    }

    /**
     * @param \ReflectionMethod[] $methods
     * @param string $method
     *
     * @return bool
     */
    private function checkPublicMethods(array $methods, string $method)
    {
        $found = false;
        foreach ($methods as $meth) {
            if ($meth->getName() === $method) {
                $found = true;
                break;
            }
        }

        return $found;
    }

    /**
     * Converts ORM like objects from the request from arrays to objects.
     *
     * @param string $key
     *
     * @return AbstractEntity
     */
    private function convertRequestToObj(string $key)
    {
        $ar = filter_input($this->inputType, $key, \FILTER_DEFAULT, \FILTER_REQUIRE_ARRAY);
        $baseObj = new $this->objMapping[$key]();
        $this->mapArrayToObj($ar, $baseObj);

        return $baseObj;
    }

    private function mapArrayToObj(array $ar, AbstractEntity $baseObj)
    {
        foreach ($ar as $column => $value) {
            $reflection = new \ReflectionClass($baseObj);
            $method1 = 'add' . ucfirst($column);
            $method2 = 'set' . ucfirst($column);
            $methods = $reflection->getMethods(\ReflectionMethod::IS_PUBLIC);

            if (is_array($value)) {
                $newObj = new $this->objMapping[$column]();
                $this->addObjectTo($methods, $method1, $method2, $baseObj, $newObj);
                $reflection = new \ReflectionClass($newObj);
                $methods = $reflection->getMethods(\ReflectionMethod::IS_PUBLIC);
                foreach ($value as $subCol => $subVal) {
                    $method2 = 'set' . ucfirst($subCol);
                    if (is_array($subVal)) {
                        if (is_numeric($subCol)) {
                            $this->mapArrayToObj($subVal, $newObj);
                        }
                    } else {
                        $this->parseSimpleType($newObj, $column, $value, $methods, $method2);
                    }
                }
            } else {
                $this->parseSimpleType($baseObj, $column, $value, $methods, $method2);
            }
        }
    }

    private function parseSimpleType(AbstractEntity $obj, $column, $value, array $methods, $method2)
    {
        $timestamp = $this->formatter->parse($value);

        if ($timestamp) {
            try {
                $value = new \DateTime($timestamp);
            } catch (\Exception $ex) {
                // nothing to do...
            }
        }

        if ($this->checkPublicMethods($methods, $method2)) {
            $obj->$method2($value);
        } else {
            $obj->{$column} = $value;
        }
    }

    private function addObjectTo(array $methods, $method1, $method2, AbstractEntity $baseObj, AbstractEntity $newObj)
    {
        if ($this->checkPublicMethods($methods, $method1)) {
            $baseObj->$method1($newObj);
        } elseif ($this->checkPublicMethods($methods, $method2)) {
            $baseObj->$method2($newObj);
        } else {
            $baseObj->{$column} = $newObj;
        }
    }

    private function getNestedObject(AbstractEntity $obj, array $keys, $levelUp = 0)
    {
        if ($levelUp > 0) {
            for ($i = 0; $i < $levelUp; $i++) {
                unset($keys[count($keys) - 1]);
            }
        }

        $innerObj = $obj;
        $lastObj = $obj;

        if (count($keys) > 0) {
            foreach ($keys as $key) {
                if (is_numeric($key)) {
                    $innerObj = $innerObj[$key];
                } else {
                    $method = 'get' . ucfirst($key);
                    $reflection = new \ReflectionClass($innerObj);
                    $methods = $reflection->getMethods(\ReflectionMethod::IS_PUBLIC);

                    $lastObj = $innerObj;
                    if ($this->checkPublicMethods($methods, $method)) {
                        $innerObj = $innerObj->$method();
                    } else {
                        $innerObj = $innerObj->{$key};
                    }
                }
            }

            if ($innerObj === null) {
                $innerObj = $lastObj;
            }
        }

        return $innerObj;
    }

    private function setNestedObject(array $parsedObjs, array $keys, AbstractEntity $objToAdd)
    {
        $ref = &$parsedObjs;
        foreach ($keys as $key) {
            $ref = &$ref[$key];
        }

        $ref = $objToAdd;

        return $parsedObjs;
    }
}
内部带有if分支:

/**
 * Converts ORM like objects from the request from arrays to objects.
 *
 * @param string $key
 *
 * @return AbstractEntity
 */
private function convertRequestToObj(string $key)
{
    $ar = filter_input($this->inputType, $key, \FILTER_DEFAULT, \FILTER_REQUIRE_ARRAY);
    $baseObj = new $this->objMapping[$key]();
    $this->mapArrayToObj($ar, $baseObj, $baseObj);

    return $baseObj;
}

private function mapArrayToObj(array $ar, AbstractEntity $baseObj, AbstractEntity $veryBaseObj, $refDepth = '')
{
    foreach ($ar as $column => $value) {
        $reflection = new \ReflectionClass($baseObj);
        $method1 = 'add' . ucfirst($column);
        $method2 = 'set' . ucfirst($column);
        $methods = $reflection->getMethods(\ReflectionMethod::IS_PUBLIC);

        if (is_array($value)) {
            $refDepth .= $column .',';
            $refDepthBackup = $refDepth;
            $refKeys = explode(',', substr($refDepth, 0, strrpos($refDepth, ',')));
            if (is_numeric($column)) {
                $column = substr($refDepth, 0, strrpos($refDepth, ','));
                $column = substr($column, 0, strrpos($column, ','));
                $method1 = 'add' . ucfirst($column);
                $toAddObj = $this->getNestedObject($veryBaseObj, $refKeys, 2);
                // sanitize strings like position,0,1,:
                $refDepth = substr($refDepth, 0, strrpos($refDepth, ','));
                $refDepth = substr($refDepth, 0, strrpos($refDepth, ',') + 1);
            } else {
                $toAddObj = $baseObj;
            }

            $reflection = new \ReflectionClass($toAddObj);
            $method1 = 'add' . ucfirst($column);
            $method2 = 'set' . ucfirst($column);
            $methods = $reflection->getMethods(\ReflectionMethod::IS_PUBLIC);

            $newObj = new $this->objMapping[$column]();
            $this->addObjectTo($methods, $method1, $method2, $toAddObj, $newObj);

            $this->mapArrayToObj($value, $newObj, $veryBaseObj, $refDepthBackup);
        } else {
            $refDepth = '';
            $this->parseSimpleType($baseObj, $column, $value, $methods, $method2);
        }
    }
}
使用内部
foreach
循环:

/**
 * Converts ORM like objects from the request from arrays to objects.
 *
 * @param string $key
 *
 * @return AbstractEntity
 */
private function convertRequestToObj(string $key)
{
    $ar = filter_input($this->inputType, $key, \FILTER_DEFAULT, \FILTER_REQUIRE_ARRAY);
    $baseObj = new $this->objMapping[$key]();
    $this->mapArrayToObj($ar, $baseObj);

    return $baseObj;
}

private function mapArrayToObj(array $ar, AbstractEntity $baseObj)
{
    foreach ($ar as $column => $value) {
        $reflection = new \ReflectionClass($baseObj);
        $method1 = 'add' . ucfirst($column);
        $method2 = 'set' . ucfirst($column);
        $methods = $reflection->getMethods(\ReflectionMethod::IS_PUBLIC);

        if (is_array($value)) {
            $newObj = new $this->objMapping[$column]();
            $this->addObjectTo($methods, $method1, $method2, $baseObj, $newObj);
            $reflection = new \ReflectionClass($newObj);
            $methods = $reflection->getMethods(\ReflectionMethod::IS_PUBLIC);
            foreach ($value as $subCol => $subVal) {
                $method2 = 'set' . ucfirst($subCol);
                if (is_array($subVal)) {
                    if (is_numeric($subCol)) {
                        $this->mapArrayToObj($subVal, $newObj);
                    }
                } else {
                    $this->parseSimpleType($newObj, $column, $value, $methods, $method2);
                }
            }
        } else {
            $this->parseSimpleType($baseObj, $column, $value, $methods, $method2);
        }
    }
}

我没有使用过orm,所以不确定这是否是您想要的

一些提示:

  • 我使用了PHP5.6.30,因此您的理解可能会有所不同

  • OOP是信息隐藏,这意味着教每一个班级做什么,没有反思

  • 使用字段实现数据驱动的框架

  • 实现magic get和调用以动态访问数据和对象

  • 每个类都必须验证其数据,而不是在这里实现

  • 每个类都必须抛出并捕获自己的异常,而不是在这里实现

  • 使用工厂模式创建数据类

  • 接口定义了order类外观模式

  • trait为所有订单类实现默认方法

  • 我曾想过使用XML类,但这似乎还可以

    这是实现订单工厂模式的类文件。创建模型对象时,请使用工厂类(静态,不实例化),不要直接实例化类。getValue()在需要时处理factory::create。结果是类使用工厂创建自己

    <?php /* ormorder.php */
    // Object Relational Mapping (OrmOrder)
    
    // order OrmOrder class interface methods
    interface IORM
    {
    //  function initFields(); // this should not be public?
        function toArray();
        function __get($name);
        function __call($name,$value);
    }
    // order OrmOrder class trait methods
    trait FORM 
    {
        protected $fields;
        protected $data;
    
        function __construct($data) 
        { 
            parent::__construct();
            $this->initFields();
            $this->setData($data);
        }
        // always override, never call 
        protected function initFields(){ $this->fields = null;}
        // sometimes override, never call
        protected   function setData($data)
        {
            foreach($this->fields as $field)
                if(isset($data[$field]))
                    $this->data[$field] =   $this->getValue($field,$data[$field]);
        }
        // seldom override, never call
        protected   function getValue($field,$data) { return $data; }
        function toArray(){ return $this->data; }
        final function __get($name)
        {
            if('data' == $name)
                return $this->data;
            return $this->data[$name];
        }
        function __call($name,$value)
        {
            $attr = $value[0];
            $val = $value[1];
            $result = null;
            if(in_array($name, $this->fields))
                if(isset($this->data[$name]))
                    if(is_array($this->data[$name]))
                        foreach($this->data[$name] as $obj)
                            if($obj->$attr == $val)
                            {
                                $result = $obj;
                                break;
                            }
            else $result = $this->data[$name];
            return $result;
        }
    }
    // pacify php parent::_construct()
    abstract
    class ORMAbstract
    {
        function __construct() {}
    }
    // Main Order class that does (almost) everything
    abstract
    class Orm extends ORMAbstract implements IORM
    { use FORM;
    }
    // you should override getValue()
    class Order extends Orm
    {
    }
    class Position extends Orm
    {
    }
    class Supplier extends Orm
    {
    }
    class Customer extends Orm
    {
    }
    class Address extends Orm
    {
    }
    
    // static class to return OrmOrder objects
    // keep factory in sync with classes
    // Call directly never implement
    class OrderFactory
    {
        static
        function create($name, $data)
        {
            switch($name)
            {
                case 'supplier': return new Item($data);
                case 'position': return new LineItem($data);
                case 'address': return new Address($data);
                case 'customer': return new Customer($data);
                case 'order': return new Order($data);
                default: return null;
            }
        }
    }
    ?>
    
    希望这能像你所期待的那样进行检查,可能和条令不一样,但可能足够接近有用

    • 更新*
    要在代码中实现答案,请尝试以下操作:

    <?PHP
        require_once('ordermodel.php');
       /*..... */
        private function jsonResponse()
        {
            header('Content-type: application/json');
            echo json_encode(
                array(
                    'success' => $this->data['success'],
                    'data' => new OrderModel($this->data['data'])
                )
            );
        }
    ?>
    
    
    
    我只需要一个通用方法将多维数组映射到其嵌套对象。请不要把重点放在这个例子上,它只是一个输入数据的exmaple,可以完全不同。此外,类映射的键可以不同。成员变量需要是公共的,因为
    PDO
    的fetch模式
    PDO::fetch_CLASS
    只允许这样做。为了解释我的示例:
    Order
    应该是外部对象。成员变量
    position
    是一个
    Article
    对象数组。请在回答中指出它不起什么作用。它采用多维数组并创建一个顺序(类)树。调用toArray()并返回多维数组。这就是你要的,不是吗?通过创建覆盖initFields()和getValue()的类,您可以从任何数组中创建任意树结构。但是,首先,对于简单的数据类型,我需要在每个类上使用公共成员变量。对于对象的数组列表,我们获得了私有成员及其相关的
    set
    add
    get
    方法。对象成员变量也有getter和setter。我将投票支持这个答案,因为这是一个替代方案。在另一个答案中修改我的算法怎么样?我来看看我能用你的代码做些什么。“公共成员变量”是动态创建的,所以您只需使用它们。e、 g.订单->id应返回客户列表。这意味着您可以更改数据库列/名称,更新initFields(),但它仍然有效。我正在研究PDO::FETCH_类,它似乎与我的示例一起工作,但会返回给您:)您似乎可以将该特性添加到您的AbstractEntity中。如果删除FETCH_类,因此结果是纯数据,那么它应该可以工作。我看不到数据来自何处,因为这是您需要实例化ordermodel类(new ordermodel($data))的地方。AjaxRequest->save()然后调用model->toArray()将树转换为数据。您不需要AjaxRequest类中的所有其他方法。
    <?php /* ormorder.php */
    // Object Relational Mapping (OrmOrder)
    
    // order OrmOrder class interface methods
    interface IORM
    {
    //  function initFields(); // this should not be public?
        function toArray();
        function __get($name);
        function __call($name,$value);
    }
    // order OrmOrder class trait methods
    trait FORM 
    {
        protected $fields;
        protected $data;
    
        function __construct($data) 
        { 
            parent::__construct();
            $this->initFields();
            $this->setData($data);
        }
        // always override, never call 
        protected function initFields(){ $this->fields = null;}
        // sometimes override, never call
        protected   function setData($data)
        {
            foreach($this->fields as $field)
                if(isset($data[$field]))
                    $this->data[$field] =   $this->getValue($field,$data[$field]);
        }
        // seldom override, never call
        protected   function getValue($field,$data) { return $data; }
        function toArray(){ return $this->data; }
        final function __get($name)
        {
            if('data' == $name)
                return $this->data;
            return $this->data[$name];
        }
        function __call($name,$value)
        {
            $attr = $value[0];
            $val = $value[1];
            $result = null;
            if(in_array($name, $this->fields))
                if(isset($this->data[$name]))
                    if(is_array($this->data[$name]))
                        foreach($this->data[$name] as $obj)
                            if($obj->$attr == $val)
                            {
                                $result = $obj;
                                break;
                            }
            else $result = $this->data[$name];
            return $result;
        }
    }
    // pacify php parent::_construct()
    abstract
    class ORMAbstract
    {
        function __construct() {}
    }
    // Main Order class that does (almost) everything
    abstract
    class Orm extends ORMAbstract implements IORM
    { use FORM;
    }
    // you should override getValue()
    class Order extends Orm
    {
    }
    class Position extends Orm
    {
    }
    class Supplier extends Orm
    {
    }
    class Customer extends Orm
    {
    }
    class Address extends Orm
    {
    }
    
    // static class to return OrmOrder objects
    // keep factory in sync with classes
    // Call directly never implement
    class OrderFactory
    {
        static
        function create($name, $data)
        {
            switch($name)
            {
                case 'supplier': return new Item($data);
                case 'position': return new LineItem($data);
                case 'address': return new Address($data);
                case 'customer': return new Customer($data);
                case 'order': return new Order($data);
                default: return null;
            }
        }
    }
    ?>
    
    <?php /* ordermodel.php */
    require_once('ormorder.php');
    
    // sample database, development only, delete in production
    $data['order'][0]['id'] = 0;
    $data['order'][0]['note'] = 'test orders';
    $data['order'][0]['date'] = '23 Mar 13';
    $data['order'][0]['customer'][0]['id'] = 1;
    $data['order'][0]['customer'][0]['account'] = '3000293826';
    $data['order'][0]['customer'][0]['name'] = 'John Doe';
    $data['order'][0]['customer'][0]['billing'][0] = 'Sand Castle';
    $data['order'][0]['customer'][0]['billing'][1] = '1 beach street';
    $data['order'][0]['customer'][0]['billing'][2] = 'strand';
    $data['order'][0]['customer'][0]['billing'][3] = 'Lagoon';
    $data['order'][0]['customer'][0]['billing'][4] = 'Fairy Island';
    $data['order'][0]['customer'][0]['billing'][5] = '55511';
    $data['order'][0]['customer'][0]['delivery'][0] = 'Nine Acres';
    $data['order'][0]['customer'][0]['delivery'][1] = '3 corn field';
    $data['order'][0]['customer'][0]['delivery'][2] = 'Butterworth';
    $data['order'][0]['customer'][0]['delivery'][3] = 'Foam Vale';
    $data['order'][0]['customer'][0]['delivery'][4] = 'Buttress Lake';
    $data['order'][0]['customer'][0]['delivery'][5] = '224433';
    $data['order'][0]['customer'][0]['items'][0]['supplier'] = '4000392292';
    $data['order'][0]['customer'][0]['items'][0]['stock'] = '2000225571';
    $data['order'][0]['customer'][0]['items'][0]['quantity'] = 5;
    $data['order'][0]['customer'][0]['items'][0]['unitprice'] = 35.3;
    $data['order'][0]['customer'][0]['items'][1]['supplier'] = '4000183563';
    $data['order'][0]['customer'][0]['items'][1]['stock'] = '2000442279';
    $data['order'][0]['customer'][0]['items'][1]['quantity'] = 12;
    $data['order'][0]['customer'][0]['items'][1]['unitprice'] = 7.4;
    
    // Top level Order management class
    // could also be an OrmOrder class
    class OrderModel
    {
        private $orders;
    
        function __construct($data)
        {
            foreach($data['order'] as $order)
                $this->orders[] = OrderFactory::create('order',$order);
        }
        function __call($name,$value)
        {
            $o = null;
            $attribute = $value[0];
            $val = $value[1];
            foreach($this->orders as $order)
            {
                if($order->$attribute == $val)
                {
                    $o = $order;
                    break;
                }
            }
            return $o;
        }
        function toArray()
        {
            $data = null;
            foreach($this->orders as $order)
                $data['order'][] = $order->toArray();
            return $data;
        }
    }
    /* development only, delete in production */
    function main($data)
    {
        $model = new OrderModel($data);
        echo $model->order('id',12)->note;
        var_dump($model->order('date',
        '23 Mar 13')->customer('account','3000293826')->delivery->data);
    //  var_dump($model->toArray());
    }
    main($data);
    ?>
    
    PHP Notice:  Trying to get property 'note' of non-object in C:\Users\Peter\Docum
    ents\php\ordermodel.php on line 70
    
    Notice: Trying to get property 'note' of non-object in C:\Users\Peter\Documents\
    php\ordermodel.php on line 70
    array(6) {
      [0]=>
      string(10) "Nine Acres"
      [1]=>
      string(12) "3 corn field"
      [2]=>
      string(11) "Butterworth"
      [3]=>
      string(9) "Foam Vale"
      [4]=>
      string(13) "Buttress Lake"
      [5]=>
      string(6) "224433"
    }
    
    <?PHP
        require_once('ordermodel.php');
       /*..... */
        private function jsonResponse()
        {
            header('Content-type: application/json');
            echo json_encode(
                array(
                    'success' => $this->data['success'],
                    'data' => new OrderModel($this->data['data'])
                )
            );
        }
    ?>