2

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)

10
  • do you try json_decode fucntion ? docs.php.net/json_decode Commented Sep 23, 2014 at 9:53
  • my bad, I didn't expressed myself so well I guess, I'll edit my question Commented Sep 23, 2014 at 9:55
  • @kitensei , could you reword your question? With this formulation it should be closed as off-top: stackoverflow.com/help/on-topic Commented Sep 23, 2014 at 10:02
  • sure, sorry for vague formulation Commented Sep 23, 2014 at 10:04
  • you just want generate class from data in json ?? Commented Sep 23, 2014 at 10:06

3 Answers 3

3

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;
Sign up to request clarification or add additional context in comments.

4 Comments

The problem is that I'm not looking for procedural way to do, I need more abstraction as this is one of hundreds json objects, let me edit again my question, sorry if I haven't been so clear
That's quite it, but I'm looking for a class file generation, the "typed" unserialization is not a problem, I wanted to know if there were some libraries that would build a php class tree from a json object
You want to generate the class and save it in an actual .php file?
yep that's it, damn my title wasn't clear enough, sorry I'll update it again :/
1

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": "[email protected]",
     "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;
}

Comments

0

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

2 Comments

I don't really care about getter/setter in fact, all properties will be public so I can serve the class to WSDL "as is" but hard typed; instead of object the user will have a Domain object containing another object etc..
right, so lazy coding then, or rather not want code at all. If you look at [Yii][1], it has a build in module to generate models based on datasources (not sure if it understands soap responses) [1]yiiframework.com/doc/guide/1.1/en/topics.gii

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.