首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何将多维数组递归转换为相关的实体对象

如何将多维数组递归转换为相关的实体对象
EN

Stack Overflow用户
提问于 2018-12-19 16:57:31
回答 1查看 138关注 0票数 3

我正在尝试将一个多维HTML表单数组转换为具有一对多关系和嵌套一对多关系的相关实体(数据库)对象类。考虑以下输入示例(人类可读):

代码语言:javascript
复制
order[id]: 1
order[note]: test note
order[ordertime]: 13. Dez. 2018 09:01
order[position][0][id]: 1
order[position][0][ordernumber]: ADSF-11
order[position][0][price]: 45.99
order[position][0][supplier][id]: 1
order[position][0][supplier][name]: test supplier 1
order[position][1][id]: 2
order[position][1][ordernumber]: ADSF-12
order[position][1][price]: 50.99
order[position][1][supplier][id]: 2
order[position][1][supplier][name]: test supplier 2
order[customer][firstname]: Human
order[customer][surname]: Being
order[customer][billingAddress][id]: 1
order[customer][billingAddress][firstname]: Human 2
order[customer][billingAddress][surname]: Being 2
order[customer][billingAddress][street]: test street 1
order[customer][billingAddress][zip]: 99999
order[customer][billingAddress][city]: test city
order[customer][shippingAddress][id]: 2
order[customer][shippingAddress][firstname]: Human 3
order[customer][shippingAddress][surname]: Being 3
order[customer][shippingAddress][street]: test street 100
order[customer][shippingAddress][zip]: 88888
order[customer][shippingAddress][city]: test city 2

我们得到了一个名为AbstractEntity的具有空体的抽象类,每个实体都会对其进行扩展,并且实体具有用于简单类型的公共成员变量。对于数组,它的访问是私有的,有setter方法和addXX方法在数组的末尾添加一个条目(这就是为什么需要反射,也是为什么我们有$method1$method2)。此外,它还将日期和时间从国际化的string解析为DateTime

我想以ORM框架的方式访问它们,就像Doctrine那样:

代码语言:javascript
复制
$order->getPosition()[0]->getBillingAddress()->firstname

下面是我的worker类,它完成了主要任务:

代码语言:javascript
复制
<?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;
    }
}

Lets say this example calls the pubic methodsave. For some reason, it does the nesting wrong. Although the other way around, from objects to array usingconvertToPublicObjects`工作得很好。

以下是我的其他尝试:

使用绕过的参考深度:

代码语言:javascript
复制
/**
 * 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) && !is_numeric($column)) {
            $refDepth .= $column .',';

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

            $this->mapArrayToObj($value, $newObj, $veryBaseObj, $refDepth);
        } elseif (is_array($value) && is_numeric($column)) {
            $refDepth .= $column .',';
            $refKeys = explode(',', substr($refDepth, 0, strrpos($refDepth, ',')));
            $toAddObj = $this->getNestedObject($veryBaseObj, $refKeys);
            $column = substr($refDepth, 0, strrpos($refDepth, ','));
            $column = substr($column, 0, strrpos($column, ','));
            $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) {
                if (is_array($subVal)) {
                    // sanitize strings like userMain,0,1,:
                    $refDepth = substr($refDepth, 0, strrpos($refDepth, ','));
                    $refDepth = substr($refDepth, 0, strrpos($refDepth, ',') + 1);

                    $refDepth .= $subCol . ',';
                    $this->mapArrayToObj($subVal, $newObj, $veryBaseObj, $refDepth);
                } else {
                    $method2 = 'set' . ucfirst($subCol);
                    $this->parseSimpleType($newObj, $subCol, $subVal, $methods, $method2);
                }
            }
            // sanitize strings like position,0,1,:
            $refDepth = substr($refDepth, 0, strrpos($refDepth, ','));
            $refDepth = substr($refDepth, 0, strrpos($refDepth, ',') + 1);
        } else {
            $refDepth = '';
            $this->parseSimpleType($baseObj, $column, $value, $methods, $method2);
        }
    }
}

其中包含if分支:

代码语言:javascript
复制
/**
 * 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循环:

代码语言:javascript
复制
/**
 * 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);
        }
    }
}
EN

回答 1

Stack Overflow用户

发布于 2018-12-20 17:28:33

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

一些提示:

  1. 我用的是php 5.6.30,所以你的用法可能不同。
  2. OOP是信息隐藏,这意味着教每个类做什么,没有reflection.
  3. Use字段来实现数据驱动的get和调用来动态访问数据和对象
  4. 每个类必须验证它的数据,而不是在这里实现
  5. 每个类必须抛出并捕获自己的异常,此处未实现
  6. 使用工厂模式创建数据类。
  7. 接口定义order类外观模式。
  8. 特征为所有order类实现默认方法。

我曾尝试过使用XML类的想法,但这似乎工作得很好。

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

代码语言:javascript
复制
<?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;
        }
    }
}
?>

模型文件(和main函数)。从命令提示符运行此命令

/*假设php设置正确*/

ordermodel

此文件包含顶层模型,即用于检查数据的订单模型。toArray()返回一个多维数组。必须实例化OrderModel类并向其传递(html)多维数组。

代码语言:javascript
复制
<?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);
?>

输出应类似于:

代码语言:javascript
复制
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"
}

希望这能做你想要的那种检查,可能与Doctrine不同,但可能足够接近,以便有用。

  • 更新*

要在您的代码中实现答案,请尝试执行以下命令:

代码语言:javascript
复制
<?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'])
            )
        );
    }
?>
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/53847665

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档