8000 [AstGenerator] [WIP] New Component, add normalizer generator by joelwurtz · Pull Request #17516 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content

[AstGenerator] [WIP] New Component, add normalizer generator #17516

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Prev Previous commit
Next Next commit
Begin hydrate generator
  • Loading branch information
joelwurtz committed Aug 5, 2016
commit 8349681ba436ca76886a27aac31d98e3c144e392
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace Symfony\Component\AstGenerator\Exception;

class MissingContextException extends \RuntimeException
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\AstGenerator\Hydrate;

use PhpParser\Node\Name;
use PhpParser\Node\Expr;
use PhpParser\Node\Scalar;

/**
* Create AST Statement to normalize a Class into a stdClassObject
*
* @author Joel Wurtz <joel.wurtz@gmail.com>
*/
class ArrayHydrateGenerator extends HydrateFromObjectGenerator
{
/**
* {@inheritdoc}
*/
protected function getAssignStatement($dataVariable)
{
return new Expr\Assign($dataVariable, new Expr\Array_());
}

/**
* {@inheritdoc}
*/
protected function getSubAssignVariableStatement($dataVariable, $property)
{
return new Expr\ArrayDimFetch($dataVariable, new Scalar\String_($property));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\AstGenerator\Hydrate;

use PhpParser\Node\Name;
use PhpParser\Node\Stmt;
use PhpParser\Node\Expr;
use Symfony\Component\AstGenerator\AstGeneratorInterface;
use Symfony\Component\AstGenerator\Exception\MissingContextException;
use Symfony\Component\PropertyAccess\PropertyAccessor;
use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface;

abstract class HydrateFromObjectGenerator implements AstGeneratorInterface
{
/** @var PropertyInfoExtractorInterface Extract list of properties from a class */
protected $propertyInfoExtractor;

/** @var AstGeneratorInterface Generator for normalization of types */
protected $typeHydrateAstGenerator;

public function __construct(PropertyInfoExtractorInterface $propertyInfoExtractor, AstGeneratorInterface $typeHydrateAstGenerator)
{
$this->propertyInfoExtractor = $propertyInfoExtractor;
$this->typeHydrateAstGenerator = $typeHydrateAstGenerator;
}

/**
* {@inheritdoc}
*/
public function generate($object, array $context = [])
{
if (!isset($context['input'])) {
throw new MissingContextException('Input variable not defined in context');
}

$dataVariable = new Expr\Variable('data');
$statements = [$this->getAssignStatement($dataVariable)];

foreach ($this->propertyInfoExtractor->getProperties($object, $context) as $property) {
// Only normalize readable property
if (!$this->propertyInfoExtractor->isReadable($object, $property, $context)) {
continue;
}

// @TODO Have property info extractor extract the way of reading a property (public or method with method name)
$input = new Expr\MethodCall($context['input'], 'get'.ucfirst($property));
$output = $this->getSubAssignVariableStatement($dataVariable, $property);
$types = $this->propertyInfoExtractor->getTypes($object, $property, $context);

// If no type can be extracted, directly assign output to input
if (null === $types || count($types) == 0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 === count($type)

$statements[] = new Expr\Assign($output, $input);

continue;
}

// If there is multiple types, we need to know which one we must normalize
$conditionNeeded = (boolean)(count($types) > 1);
$noAssignment = true;

foreach ($types as $type) {
if (!$this->typeHydrateAstGenerator->supportsGeneration($type)) {
continue;
}

$noAssignment = false;
$statements = array_merge($statements, $this->typeHydrateAstGenerator->generate($type, array_merge($context, [
'input' => $input,
'output' => $output,
'condition' => $conditionNeeded
])));
}

// If nothing has been assigned, we directly put input into output
if ($noAssignment) {
$statements[] = new Expr\Assign($output, $input);
}
}

$statements[] = new Stmt\Return_($dataVariable);

return $statements;
}

/**
* {@inheritdoc}
*/
public function supportsGeneration($object)
{
return (is_string($object) && class_exists($object));
}

/**
* Create the assign statement
*
* @param Expr\Variable $dataVariable Variable to use
*
* @return Expr\Assign An assignment for the variable
*/
abstract protected function getAssignStatement($dataVariable);


/**
* Create the sub assign variable statement
*
* @param Expr\Variable $dataVariable Variable to use
* @param string $property Property name for object or array dimension
*
* @return Expr\ArrayDimFetch|Expr\PropertyFetch
*/
abstract protected function getSubAssignVariableStatement($dataVariable, $property);
}
A3D4
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\AstGenerator\Hydrate;

use PhpParser\Node\Name;
use PhpParser\Node\Expr;

/**
* Create AST Statement to normalize a Class into a stdClassObject
*
* @author Joel Wurtz <joel.wurtz@gmail.com>
*/
class StdClassHydrateGenerator extends HydrateFromObjectGenerator
{
/**
* {@inheritdoc}
*/
protected function getAssignStatement($dataVariable)
{
return new Expr\Assign($dataVariable, new Expr\New_(new Name("\\stdClass")));
}

/**
* {@inheritdoc}
*/
protected function getSubAssignVariableStatement($dataVariable, $property)
{
return new Expr\PropertyFetch($dataVariable, sprintf("{'%s'}", $property));
}
}
F438
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use PhpParser\Node\Expr;
use PhpParser\Node\Scalar;
use Symfony\Component\AstGenerator\AstGeneratorInterface;
use Symfony\Component\AstGenerator\UniqueVariableScope;

/**
* Generate a Normalizer given a Class
Expand Down Expand Up @@ -59,8 +60,12 @@ public function generate($object, array $context = [])
'stmts' => [
$this->createSupportsNormalizationMethod($object),
$this->createSupportsDenormalizationMethod($object),
$this->createNormalizeMethod($object, $context),
$this->createDenormalizeMethod($object, $context),
$this->createNormalizeMethod($object, array_merge($context, [
'unique_variable_scope' => new UniqueVariableScope()
])),
$this->createDenormalizeMethod($object, array_merge($context, [
'unique_variable_scope' => new UniqueVariableScope()
])),
],
'implements' => [
new Name('\Symfony\Component\Serializer\Normalizer\DenormalizerInterface'),
Expand Down Expand Up @@ -151,7 +156,9 @@ protected function createNormalizeMethod($class, array $context = [])
new Param('format', new Expr\ConstFetch(new Name("null"))),
new Param('context', new Expr\Array_(), 'array'),
],
'stmts' => $this->normalizeStatementsGenerator->generate($class, $context)
'stmts' => $this->normalizeStatementsGenerator->generate($class, array_merge($context, [
'input' => new Expr\Variable('object')
]))
]);
}

Expand All @@ -173,7 +180,9 @@ protected function createDenormalizeMethod($class, array $context = [])
new Param('format', new Expr\ConstFetch(new Name("null"))),
new Param('context', new Expr\Array_(), 'array'),
],
'stmts' => $this->denormalizeStatementsGenerator->generate($class, $context)
'stmts' => $this->denormalizeStatementsGenerator->generate($class, array_merge($context, [
'input' => new Expr\Variable('data')
]))
]);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\AstGenerator\Tests\Hydrate;

use PhpParser\Node\Expr;
use PhpParser\PrettyPrinter\Standard;
use Prophecy\Argument;
use Symfony\Component\AstGenerator\AstGeneratorInterface;
use Symfony\Component\AstGenerator\Hydrate\ArrayHydrateGenerator;
use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface;
use Symfony\Component\PropertyInfo\Type;

class ArrayHydrateGeneratorTest extends \PHPUnit_Framework_TestCase
{
/** @var Standard */
protected $printer;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this could be private.


public function setUp()
{
$this->printer = new Standard();
}

public function testHydrateGenerator()
{
$propertyInfoExtractor = $this->prophesize(PropertyInfoExtractorInterface::class);
$propertyInfoExtractor->getProperties(Dummy::class, Argument::type('array'))->willReturn(['foo', 'bar']);
$propertyInfoExtractor->isReadable(Dummy::class, 'foo', Argument::type('array'))->willReturn(true);
$propertyInfoExtractor->isReadable(Dummy::class, 'bar', Argument::type('array'))->willReturn(false);
$propertyInfoExtractor->getTypes(Dummy::class, 'foo', Argument::type('array'))->willReturn([
new Type('string')
]);
$hydrateGenerator = new ArrayHydrateGenerator($propertyInfoExtractor->reveal(), new DummyTypeGenerator());

$this->assertTrue($hydrateGenerator->supportsGeneration(Dummy::class));

$dummyObject = new Dummy();
$dummyObject->foo = "test";

$dummyArray = eval($this->printer->prettyPrint($hydrateGenerator->generate(Dummy::class, [
'input' => new Expr\Variable('dummyObject')
])));

$this->assertInternalType('array', $dummyArray);
$this->assertArrayHasKey('foo', $dummyArray);
$this->assertEquals('test', $dummyArray['foo']);
}

/**
* @expectedException \Symfony\Component\AstGenerator\Exception\MissingContextException
*/
public function testNoInput()
{
$propertyInfoExtractor = $this->prophesize(PropertyInfoExtractorInterface::class);
$hydrateGenerator = new ArrayHydrateGenerator($propertyInfoExtractor->reveal(), new DummyTypeGenerator());
$hydrateGenerator->generate(Dummy::class);
}
}

class Dummy
{
public $foo;

public $bar;

/**
* @return mixed
*/
public function getFoo()
{
return $this->foo;
}

/**
* @param mixed $bar
*/
public function setBar($bar)
{
$this->bar = $bar;
}
}

class DummyTypeGenerator implements AstGeneratorInterface
{
public function generate($object, array $context = [])
{
if (!isset($context['input'])) {
throw new \Exception('no input');
}

if (!isset($context['output'])) {
throw new \Exception('no output');
}

return [new Expr\Assign($context['output'], $context['input'])];
}

public function supportsGeneration($object)
{
return true;
}
}
Loading
0