Add a parser for jsonSerializable classes

This commit is contained in:
Fred Cox 2015-06-30 23:34:15 +03:00
parent a343bb7c45
commit 37c6465700
6 changed files with 257 additions and 0 deletions

View File

@ -0,0 +1,86 @@
<?php
/**
* Created by mcfedr on 30/06/15 21:03
*/
namespace Nelmio\ApiDocBundle\Parser;
class JsonSerializableParser implements ParserInterface
{
/**
* {@inheritdoc}
*/
public function supports(array $item)
{
if (!is_subclass_of($item['class'], 'JsonSerializable')) {
return false;
}
$ref = new \ReflectionClass($item['class']);
if ($ref->hasMethod('__construct')) {
foreach ($ref->getMethod('__construct')->getParameters() as $parameter) {
if (!$parameter->isOptional()) {
return false;
}
}
}
return true;
}
/**
* {@inheritdoc}
*/
public function parse(array $item)
{
/** @var \JsonSerializable $obj */
$obj = new $item['class']();
$encoded = $obj->jsonSerialize();
$top = $this->getItemMetaData($encoded);
return $top['children'];
}
public function getItemMetaData($item)
{
$type = gettype($item);
$meta = array(
'dataType' => $type,
'required' => true,
'description' => '',
'readonly' => false
);
if ($type == 'object' && $item instanceof \JsonSerializable) {
$meta = $this->getItemMetaData($item->jsonSerialize());
$meta['class'] = get_class($item);
} elseif (($type == 'object' && $item instanceof \stdClass) || ($type == 'array' && !$this->isSequential($item))) {
$meta['dataType'] = 'object';
$meta['children'] = array();
foreach ($item as $key => $value) {
$meta['children'][$key] = $this->getItemMetaData($value);
}
}
return $meta;
}
/**
* Check for numeric sequential keys, just like the json encoder does
* Credit: http://stackoverflow.com/a/25206156/859027
*
* @param array $arr
* @return bool
*/
private function isSequential(array $arr)
{
for ($i = count($arr) - 1; $i >= 0; $i--) {
if (!isset($arr[$i]) && !array_key_exists($i, $arr)) {
return false;
}
}
return true;
}
}

View File

@ -16,6 +16,7 @@
<parameter key="nelmio_api_doc.parser.collection_parser.class">Nelmio\ApiDocBundle\Parser\CollectionParser</parameter> <parameter key="nelmio_api_doc.parser.collection_parser.class">Nelmio\ApiDocBundle\Parser\CollectionParser</parameter>
<parameter key="nelmio_api_doc.parser.form_errors_parser.class">Nelmio\ApiDocBundle\Parser\FormErrorsParser</parameter> <parameter key="nelmio_api_doc.parser.form_errors_parser.class">Nelmio\ApiDocBundle\Parser\FormErrorsParser</parameter>
<parameter key="nelmio_api_doc.parser.json_serializable_parser.class">Nelmio\ApiDocBundle\Parser\JsonSerializableParser</parameter>
</parameters> </parameters>
<services> <services>
@ -68,6 +69,10 @@
<service id="nelmio_api_doc.parser.form_errors_parser" class="%nelmio_api_doc.parser.form_errors_parser.class%"> <service id="nelmio_api_doc.parser.form_errors_parser" class="%nelmio_api_doc.parser.form_errors_parser.class%">
<tag name="nelmio_api_doc.extractor.parser" /> <tag name="nelmio_api_doc.extractor.parser" />
</service> </service>
<service id="nelmio_api_doc.parser.json_serializable_parser" class="%nelmio_api_doc.parser.json_serializable_parser.class%">
<tag name="nelmio_api_doc.extractor.parser" />
</service>
</services> </services>
</container> </container>

View File

@ -0,0 +1,22 @@
<?php
/**
* Created by mcfedr on 30/06/15 21:05
*/
namespace Nelmio\ApiDocBundle\Tests\Fixtures\Model;
class JsonSerializableOptionalConstructorTest implements \JsonSerializable
{
public function __construct($optional = null)
{
}
/**
* {@inheritdoc}
*/
public function jsonSerialize()
{
return array();
}
}

View File

@ -0,0 +1,22 @@
<?php
/**
* Created by mcfedr on 30/06/15 21:05
*/
namespace Nelmio\ApiDocBundle\Tests\Fixtures\Model;
class JsonSerializableRequiredConstructorTest implements \JsonSerializable
{
public function __construct($required)
{
}
/**
* {@inheritdoc}
*/
public function jsonSerialize()
{
return array();
}
}

View File

@ -0,0 +1,24 @@
<?php
/**
* Created by mcfedr on 30/06/15 21:05
*/
namespace Nelmio\ApiDocBundle\Tests\Fixtures\Model;
class JsonSerializableTest implements \JsonSerializable
{
/**
* {@inheritdoc}
*/
public function jsonSerialize()
{
return array(
'id' => 123,
'name' => 'My name',
'child' => array(
'value' => array(1, 2, 3)
),
'another' => new JsonSerializableOptionalConstructorTest()
);
}
}

View File

@ -0,0 +1,98 @@
<?php
/**
* Created by mcfedr on 30/06/15 21:06
*/
namespace NelmioApiDocBundle\Tests\Parser;
use Nelmio\ApiDocBundle\Parser\JsonSerializableParser;
class JsonSerializableParserTest extends \PHPUnit_Framework_TestCase
{
/**
* @var JsonSerializableParser
*/
private $parser;
public function setUp()
{
$this->parser = new JsonSerializableParser();
}
/**
* @dataProvider dataTestParser
*/
public function testParser($property, $expected)
{
$result = $this->parser->parse(array('class' => 'Nelmio\ApiDocBundle\Tests\Fixtures\Model\JsonSerializableTest'));
foreach ($expected as $name => $value) {
$this->assertArrayHasKey($property, $result);
$this->assertArrayHasKey($name, $result[$property]);
$this->assertEquals($result[$property][$name], $expected[$name]);
}
}
/**
* @dataProvider dataTestSupports
*/
public function testSupports($class, $expected)
{
$this->assertEquals($this->parser->supports(array('class' => $class)), $expected);
}
public function dataTestParser()
{
return array(
array(
'property' => 'id',
'expected' => array(
'dataType' => 'integer'
)
),
array(
'property' => 'name',
'expected' => array(
'dataType' => 'string'
)
),
array(
'property' => 'child',
'expected' => array(
'dataType' => 'object',
'children' => array(
'value' => array(
'dataType' => 'array',
'required' => true,
'description' => '',
'readonly' => false
)
)
)
)
);
}
public function dataTestSupports()
{
return array(
array(
'class' => 'Nelmio\ApiDocBundle\Tests\Fixtures\Model\JsonSerializableTest',
'expected' => true
),
array(
'class' => 'Nelmio\ApiDocBundle\Tests\Fixtures\Model\JsonSerializableRequiredConstructorTest',
'expected' => false
),
array(
'class' => 'Nelmio\ApiDocBundle\Tests\Fixtures\Model\JsonSerializableOptionalConstructorTest',
'expected' => true
),
array(
'class' => 'Nelmio\ApiDocBundle\Tests\Fixtures\Model\Popo',
'expected' => false
)
);
}
}