【问题标题】:Creating php classes tree from a json object从 json 对象创建 php 类树
【发布时间】:2014-11-17 11:26:12
【问题描述】:

编辑

好吧,看来我真的不擅长描述我的问题。我在网上找到了this generator,我正在寻找的是完全相同的东西,但它是针对 php 代码的。有什么想法吗?


原始问题

我愿意从 json 表示(API 映射到对象)构建许多 php 类,为此我想将其转换:

{
"success": true,
"domains": [
  {
     "id": "13",
     "manual": "0",
     "name": "silo3.mobi",
     "lastname": "Doe",
     "firstname": "John",
     "cid": "1",
     "period": "1",
     "recurring_amount": "9.95",
     "currency_id": "0",
     "module": "namesilo",
     "next_due": "2012-12-12",
     "expires": "2012-12-12",
     "status": "Active",
     "type": "Register",
     "date_created": "2011-12-12",
     "autorenew": "1",
     "reglock": "1",
     "idprotection": "1"
  },
  {
     "id": "11",
     "manual": "0",
     "name": "netearthorg.org",
     "lastname": "Doe",
     "firstname": "John",
     "cid": "1",
     "period": "1",
     "recurring_amount": "9.95",
     "currency_id": "0",
     "module": "NetEarthOne",
     "next_due": "2012-11-22",
     "expires": "2012-11-22",
     "status": "Active",
     "type": "Register",
     "date_created": "2011-11-22",
     "autorenew": "1",
     "reglock": "1",
     "idprotection": "0"
  },
  {
     "id": "10",
     "manual": "0",
     "name": "hbappreseller.co.uk",
     "lastname": "Blue",
     "firstname": "Mike",
     "cid": "6",
     "period": "2",
     "recurring_amount": "9.95",
     "currency_id": "0",
     "module": "NetEarthOne",
     "next_due": "2012-11-22",
     "expires": "0000-00-00",
     "status": "Pending",
     "type": "Register",
     "date_created": "0000-00-00",
     "autorenew": "1",
     "reglock": "0",
     "idprotection": "0"
  }
],
"call": "getDomains",
"server_time": 1323793581
}

到具有 bool:success 属性的对象、“域”对象的数组等。

这并不难,我可以自己开发,但我想知道是否有一些 php 库可以解决这个问题,还没有找到

编辑

好吧,我想我还没有很好地解释自己,我想做的是构建一个 php 类文件,依赖于其他类等等,这样我就可以匹配 json 结构。

例如,给定的 json 应该生成以下内容:

class Domain {
    protected $id;
    protected $manual;
    protected $name;
    protected $lastname;
    protected $firstname;
    protected $cid;
    protected $period;
    protected $recurring_amount;
    // and so on
}

目的是为具有复杂对象的 WSDL 提供服务,并避免在对原始 API 进行任何修改时使 wsdl 签名演变(自定义类不会动态更改,只有在需要时才可以使 WSDL 保持不变)

api 生成数百个 json 对象,其中一些共享属性,所以这样做的目的是有一个全局的方式来处理所有 json 字符串并构建或获取构建的对象,例如两个 json 可以具有“域”属性,所以我第一次想生成一个名为 Domain 的类(如果 property=array 然后创建属性名称为 -S 的文件并填充属性,然后保存到文件以供进一步使用)

【问题讨论】:

  • 你试试 json_decode 功能吗? docs.php.net/json_decode
  • 我的错,我想我表达得不太好,我会编辑我的问题
  • @kitensei,你能改写你的问题吗?使用这个公式,它应该被关闭:stackoverflow.com/help/on-topic
  • 当然,抱歉表述含糊
  • 你只想从 json 中的数据生成类??

标签: php json code-generation


【解决方案1】:

假设您的 JSON 对象存储在 $json 中,那么您可以像这样动态创建一个类 -

$data = json_decode($json, true);

$class = new Domain();
foreach ($data AS $key => $value) $class->{$key} = $value;

如果您想要更通用的方式,假设您想即时更改类名 -

$data = json_decode($json, true);

$className = "Domain"; // Or assign it to something else like pick from DB, from JSON from anywhere.
$class = new {$className}();
foreach ($data AS $key => $value) $class->{$key} = $value;

【讨论】:

  • 问题是我不是在寻找程序化的方法,我需要更多的抽象,因为这是数百个 json 对象之一,让我再次编辑我的问题,对不起,如果我没有好清晰
  • 就是这样,但我正在寻找一个类文件生成,“类型化”反序列化不是问题,我想知道是否有一些库可以构建一个 php 类树一个json对象
  • 您想生成类并将其保存在实际的 .php 文件中吗?
  • 是的,就是这样,该死我的标题不够清楚,对不起,我会再次更新它:/
【解决方案2】:

好的,最后我没有找到json2csharp工具的工作,所以我开发了我的:

namespace Hostbill\Api\Generator;


use Zend\Code\Generator\ClassGenerator;
use Zend\Code\Generator\PropertyValueGenerator;
use Zend\Code\Reflection\ClassReflection;
use Zend\Json\Json;
use Zend\Json\Exception\RuntimeException as JsonRuntimeException;

class DataGenerator extends AbstractGenerator
{
    const DATA_NAMESPACE = 'Hostbill\Api\Data';
    const RESPONSE_SUFFIX = 'Response';
    const DATA_ABSTRACT_CLASS = 'AbstractData';

    /**
     * @var ClassGenerator[]
     */
    protected $classes = array();

    /**
     * @var ClassGenerator
     */
    protected $responseClass;

    /**
     * Build classes from a source json string
     * @param string $json
     */
    public function fromSource($json)
    {
        try {
            $data = Json::decode($json, Json::TYPE_ARRAY);
        } catch (JsonRuntimeException $e) {
            $this->err(sprintf('Could not generate classes for given Json, err:"%s"', $e->getMessage()));
            return;
        }

        $this->parse($data);

        // write classes files
        $this->write($this->responseClass, sprintf('%s/../Data/', __DIR__));

        foreach ($this->classes as $class) {
            if (self::RESPONSE_SUFFIX === substr($class->getName(), -strlen(self::RESPONSE_SUFFIX))) {
                $this->write($class, sprintf('%s/../Data/Response/', __DIR__));
            } else {
                $this->write($class, sprintf('%s/../Data/', __DIR__));
            }
        }
    }

    /**
     * Parse json decoded object and generate corresponding classes
     * @param array $data associative array retrieved from json_decode
     * @return DataGenerator
     */
    public function parse($data)
    {
        $responseClassNamespace = sprintf('%s\%s', self::DATA_NAMESPACE, self::RESPONSE_SUFFIX);

        // get "call" property and build Response class name on it: getClientDetails => ClientDetailResponse
        $parts = preg_split('/(?=[A-Z])/', $data['call'], -1, PREG_SPLIT_NO_EMPTY);
        array_shift($parts); // remove verb
        $parts[] = $this->inflector()->singularize(array_pop($parts));
        $parts[] = self::RESPONSE_SUFFIX;
        $baseResponseClassName = sprintf('%s\%s', self::DATA_NAMESPACE, self::RESPONSE_SUFFIX);
        $responseClass = new ClassGenerator(
            implode('', $parts),
            $responseClassNamespace,
            null,
            self::RESPONSE_SUFFIX
        );
        $responseClass->addUse($baseResponseClassName);
        $this->addClass($responseClass);

        if (!class_exists($baseResponseClassName)) {
            $baseResponseClassGenerated = true;
            $baseResponseClass = new ClassGenerator(
                self::RESPONSE_SUFFIX,
                self::DATA_NAMESPACE,
                ClassGenerator::FLAG_ABSTRACT
            );
        } else {
            $baseResponseClassGenerated = false;
            $baseResponseClass = ClassGenerator::fromReflection(new ClassReflection($baseResponseClassName));
        }
        $this->responseClass = $baseResponseClass;

        foreach ($data as $key => $value) {
            $key = $this->inflector()->pascalize($key);
            if (is_scalar($value)) {
                // thoses properties belongs to the response class
                // if we just have generated the "base" response class (Response.php)
                // store properties there (there are only 3 basic properties: success, call, serverTime)
                // otherwise store them in the child response class, but avoid any overriding of the
                // 3 properties which are stored in base Response class
                if ($baseResponseClassGenerated) {
                    $responseClassToUpdate = $baseResponseClass;
                } else {
                    $responseClassToUpdate = $responseClass;
                }
                // update base response class
                if (!$responseClassToUpdate->hasProperty($key) && !$baseResponseClass->hasProperty($key)) {
                    $responseClassToUpdate->addProperty($key);
                }
            } else {
                // object
                if ($this->isArrayAssociative($value)) {
                    if (!$responseClass->hasProperty($key)) {
                        $responseClass->addProperty($key);
                    }
                    $this->parseObject($key, $value);

                    // array
                } else {
                    if (!$responseClass->hasProperty($key)) {
                        $responseClass->addProperty($key, new PropertyValueGenerator(array(), PropertyValueGenerator::TYPE_ARRAY));
                    }

                    // if array is simple array, do nothing
                    if (!is_scalar(reset($value))) {
                        $this->parseArrayOfObjects($key, $value);
                    }
                }
            }
        }
        return $this;
    }

    /**
     * Parse ordered array and create class object
     * @param string $name key name
     * @param array $data
     * @return DataGenerator
     */
    public function parseArrayOfObjects($name, $data)
    {
        $class = $this->getOrCreateClass($this->inflector()->singularize($name));

        foreach ($data as $object) {
            foreach ($object as $key => $value) {
                if (!$class->hasProperty($key)) {
                    $class->addProperty($key);
                }
            }
        }

        return $this;
    }

    /**
     * Parse associative array and create class object
     * @param string $name key name
     * @param array $data
     * @return DataGenerator
     */
    public function parseObject($name, $data)
    {
        $class = $this->getOrCreateClass($this->inflector()->singularize($name));

        foreach ($data as $key => $value) {
            if (!$class->hasProperty($key)) {
                $class->addProperty($key);
            }
        }

        return $this;
    }

    /**
     * Add class to current stack
     * @param ClassGenerator $class
     * @return DataGenerator
     */
    protected function addClass(ClassGenerator $class)
    {
        $this->classes[$this->inflector()->lowerize($class->getName())] = $class;
        return $this;
    }

    /**
     * Get class from current stack
     * @param string $name
     * @return false|ClassGenerator False if not found
     */
    protected function getClass($name)
    {
        $id = $this->inflector()->lowerize($name);
        if (!isset($this->classes[$id])) {
            return false;
        }
        return $this->classes[$id];
    }

    /**
     * Try to retrievea class from current stack, create it if not found
     * @param string $name
     * @return ClassGenerator
     */
    protected function getOrCreateClass($name)
    {
        if (!$class = $this->getClass($name)) {
            $class = new ClassGenerator(
                $this->inflector()->camelize($name),
                self::DATA_NAMESPACE,
                null,
                self::DATA_ABSTRACT_CLASS
            );
            $this->addClass($class);
        }
        return $class;
    }

    /**
     * Check if the given array is associative
     * @param array $array
     * @return bool
     */
    protected function isArrayAssociative($array)
    {
        return (bool)count(array_filter(array_keys($array), 'is_string'));
    }
}

这段代码非常适合我的需求,但它可以很容易地适应任何 json 文件,结果如下:

JSON

  {
  "success": true,
  "client": {
     "id": "1",
     "email": "jondoe@email.com",
     "password": "474bf122c92de249ace867a003cb7196",
     "lastlogin": "2011-11-25 04:32:40",
     "ip": "213.54.21.3",
     "host": "cmt-random.uk",
     "status": "Active",
     "parent_id": "0",
     "firstname": "John",
     "lastname": "Doe",
     "companyname": "",
     "address1": "Address 54",
     "address2": "",
     "city": "Soullans",
     "state": "Birmingham",
     "postcode": "B33 8TH",
     "country": "GB",
     "phonenumber": "357755733",
     "datecreated": "2011-09-24",
     "notes": "",
     "language": "spanish",
     "company": "0",
     "credit": "0.00",
     "taxexempt": "0",
     "latefeeoveride": "0",
     "cardtype": "Visa",
     "cardnum": null,
     "expdate": null,
     "overideduenotices": "0",
     "client_id": "1",
     "currency_id": "0",
     "countryname": "United Kingdom"
  },
  "call": "getClientDetails",
  "server_time": 1323442995

}

生成的文件(缺少文档块,但将被集成以便正确提供 WSDL)

ClientResponse.php (基础对象)

namespace Hostbill\Api\Data\Response;

use Hostbill\Api\Data\Response;

class ClientResponse extends Response
{

    public $clientId = null;

    public $info = array(

    );


}

Client.php

namespace Hostbill\Api\Data;

class Client extends AbstractData
{

    public $id = null;

    public $email = null;

    public $password = null;

    public $lastlogin = null;

    public $ip = null;

    public $host = null;

    public $status = null;

    public $parent_id = null;

    public $firstname = null;

    public $lastname = null;

    public $companyname = null;

    public $address1 = null;

    public $address2 = null;

    public $city = null;

    public $state = null;

    public $postcode = null;

    public $country = null;

    public $phonenumber = null;

    public $datecreated = null;

    public $notes = null;

    public $language = null;

    public $company = null;

    public $credit = null;

    public $taxexempt = null;

    public $latefeeoveride = null;

    public $cardtype = null;

    public $cardnum = null;

    public $expdate = null;

    public $overideduenotices = null;

    public $client_id = null;

    public $currency_id = null;

    public $countryname = null;

    public $services = null;
}

【讨论】:

    【解决方案3】:

    我制作了一个 PHP 类生成器,它将使用 JSON https://json2php.strikebit.io/ 创建模型。它将递归检查您的 JSON 并创建相应的类。

    【讨论】:

    【解决方案4】:

    在我看来,您不应该为这样的通用数据创建对象。您可以轻松地将其映射到通用数据对象。

    所以你的框架就是标准的 PHP。喜欢:

    class JsonObject
    {
    
        protected $data = array();
    
        public function __construct($data)
        {
            $this->data = $data;
        }
    
        public function __get($var)
        {
            if (array_key_exists($var, $this->data)) {
                return $this->data[$var];
            } else {
                throw new Exception($var . ' not found in ' . __CLASS__);
            }
        }
    
        public function __set($var, $val)
        {
            if (array_key_exists($var, $this->data)) {
                return $this->data[$var];
            } else {
                throw new Exception($var . ' not found in ' . __CLASS__);
            }
        }
    
    
    }
    
    class Domain extends JsonObject
    {
        //some domain specific functionality
    
    }
    
    class getDomainResult
    {
    
        public $domains = array();
        public $success = false;
        public $lastTime = 0;
    
        //some methods to do the calls
    
        public function callback($result)
        {
            $res = json_decode($result, true);
            $this->success = $res['success'];
            $this->lastTime = $res['server_time'];
            foreach ($res['domains'] as $domain) {
                $this->domains[] = new Domain($domain);
            }
        }
    }
    

    【讨论】:

    • 事实上,我并不真正关心 getter/setter,所有属性都是公共的,因此我可以“按原样”将类提供给 WSDL,但要硬输入;用户将拥有一个包含另一个对象等的域对象,而不是对象。
    • 对,那么懒惰编码,或者根本不想要代码。如果您查看 [Yii][1],它有一个内置模块来生成基于数据源的模型(不确定它是否理解肥皂响应)[1]yiiframework.com/doc/guide/1.1/en/topics.gii
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-08-04
    • 1970-01-01
    • 2020-10-25
    • 1970-01-01
    • 2012-12-14
    • 2015-03-27
    • 1970-01-01
    相关资源
    最近更新 更多