РЕДАКТИРОВАТЬ
Хорошо, кажется, я действительно плохо описываю свою проблему. я нашел этот генератор в Интернете, и то, что я ищу, это то же самое, но для php-кода. любая идея ?
ОРИГИНАЛЬНЫЙ ВОПРОС
Я готов построить много классов php из представления json (сопоставление API с объектом), и для этого я хотел бы преобразовать это:
{
"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 со сложными объектами и не допускать эволюции сигнатуры wsdl, если в исходный API были внесены какие-либо изменения (пользовательские классы не изменятся динамически, только при желании, поэтому WSDL останется прежним)
API генерирует сотни объектов json, некоторые из которых имеют общие свойства, поэтому цель этого состоит в том, чтобы иметь глобальный способ обработки всех строк json и создания или получения построенных объектов, например, два json могут иметь свойство «domains», поэтому в первый раз я хочу создать класс с именем Domain (если свойство = массив, то создать файл с именем свойства -S и заполнить атрибутами, а затем сохранить в файл для дальнейшего использования)
Допустим, ваш 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;
Хорошо, в конце концов я не нашел ничего, что могло бы сделать инструмент 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": "[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
}
СОЗДАННЫЕ ФАЙЛЫ (блоки документов отсутствуют, но будут интегрированы, чтобы 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;
}
На мой взгляд, вы не должны создавать объекты для общих данных, как это. Вы можете легко сопоставить это с универсальным объектом данных.
Таким образом, ваш фреймворк будет просто стандартным 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);
}
}
}