简体   繁体   中英

Creating php classes tree from a json object

EDIT

Ok it seems I'm really bad at describing my problem. I found this generator on the web, and what I'm looking for it's the exact same thing but for php code. any idea ?


ORIGINAL QUESTION

I am willing to build many php classes from a json representation (API mapping to object), and to do that I'd like to convert this:

{
"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
}

to an object with a bool:success property, an array of "domain" object and so on.

It's not that hard to do, I could develop that myself, but I'm wondering if there is some php libs that take care of that, haven't found any

EDIT

Ok I haven't explained myself so well I guess, what I'd like to do it's build a php class file, with dependencies on other classes and so on so I can match the json structure.

For instance, the given json should generate the following:

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

The purpose is to serve a WSDL with complex objects, and avoid making the wsdl signature evolve if any modifications are made on the original API (custom classes won't change dinamically, only when wanted so the WSDL will stay the same)

The api generate hundred of json objects, some of them sharing properties, so the purpose of this is to have a global way to handle all json strings and build or get builded objects, for example two json can have the "domains" property, so the first time I want to generate a class named Domain (if property=array then create file with property name -S and fill with attributes then save to file for further usage)

Lets say your JSON object is stored in $json , then you can create a class on the fly like this -

$data = json_decode($json, true);

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

If you want a more generic way, let's say you want to change the class name on the fly -

$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;

Ok, finally I found nothing to do the job of json2csharp tool, so I developed mine:

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'));
    }
}

This code is so oriented for my needs, but it can easily be adapted to any json file, here the result:

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

}

GENERATED FILES (docblocks are missing but will be integrated so the WSDL is served correctly)

ClientResponse.php (base object)

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;
}

I made a PHP class generator that will make models with JSON https://json2php.strikebit.io/ . It will recursively inspect your JSON and make corresponding classes.

In my opinion, you should not create objects for generic data like this. You could easily map this against a generic data object.

So your framework would be just standard PHP. Like :

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);
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM