Skip to content

Commit

Permalink
reflection
Browse files Browse the repository at this point in the history
  • Loading branch information
dennis84 committed Sep 16, 2015
1 parent 1f95e10 commit adc51b5
Show file tree
Hide file tree
Showing 10 changed files with 319 additions and 23 deletions.
60 changes: 42 additions & 18 deletions src/Extension/TransformTo.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use Mapped\Mapping;

/**
* TransformTo.
* TransformTo (Experimental).
*/
class TransformTo implements ExtensionInterface
{
Expand All @@ -23,26 +23,40 @@ class TransformTo implements ExtensionInterface
*/
public function transformTo(Mapping $mapping, $object)
{
$mapping->transform(new Callback(function ($data) use ($object, $mapping) {
if (is_string($object)) {
$object = new $object;
$refl = new \ReflectionClass($object);
if (is_string($object)) {
$constr = $refl->getConstructor();
if (!$constr || 0 === $constr->getNumberOfRequiredParameters()) {
$object = $refl->newInstance();
} else {
$object = $refl->newInstanceWithoutConstructor();
}
}

$mapping->transform(new Callback(function ($data) use ($refl, $object, $mapping) {
if ($object instanceof \stdClass) {
return json_decode(json_encode($data));
}

foreach ($mapping->getChildren() as $name => $child) {
if (array_key_exists($name, $data) && null !== $data[$name]) {
$this->setValue($object, $name, $data[$name]);
$this->setValue($refl, $object, $name, $data[$name]);
}
}

return $object;
}, function ($data) use ($object, $mapping) {
}, function ($data) use ($refl, $object, $mapping) {
if (!$data instanceof $object) {
return;
}

if ($object instanceof \stdClass) {
return json_decode(json_encode($data), true);
}

$result = [];
foreach ($mapping->getChildren() as $name => $child) {
$result[$name] = $this->getValue($data, $name);
$result[$name] = $this->getValue($refl, $data, $name);
}

return $result;
Expand All @@ -54,37 +68,47 @@ public function transformTo(Mapping $mapping, $object)
/**
* Sets the given value into the object.
*
* @param object $object The object
* @param string $name The property name
* @param mixed $value The value
* @param ReflectionClass $refl The reflection class
* @param object $object The object
* @param string $name The property name
* @param mixed $value The value
*/
private function setValue($object, $name, $value)
private function setValue(\ReflectionClass $refl, $object, $name, $value)
{
$setter = 'set' . ucfirst($name);
if (method_exists($object, $setter)) {
return $object->$setter($value);
}

throw new \RuntimeException(sprintf(
'Call to undefined method "%s::%s()"', get_class($object), $setter));
$prop = $refl->getProperty($name);
if (!$prop->isPublic()) {
$prop->setAccessible(true);
}

$prop->setValue($object, $value);
}

/**
* Gets a value from given object.
*
* @param object $object The object
* @param string $name The property name
* @param ReflectionClass $refl The reflection class
* @param object $object The object
* @param string $name The property name
*
* @return mixed
*/
private function getValue($object, $name)
private function getValue(\ReflectionClass $refl, $object, $name)
{
$getter = 'get' . ucfirst($name);
if (method_exists($object, $getter)) {
return $object->$getter();
}

throw new \RuntimeException(sprintf(
'Call to undefined method "%s::%s()"', get_class($object), $getter));
$prop = $refl->getProperty($name);
if (!$prop->isPublic()) {
$prop->setAccessible(true);
}

return $prop->getValue($object);
}
}
2 changes: 1 addition & 1 deletion src/Factory.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/
class Factory
{
private $extensions = [];
protected $extensions = [];

/**
* Constructor.
Expand Down
89 changes: 89 additions & 0 deletions src/ReflectionFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<?php

namespace Mapped;

/**
* ReflectionFactory (Experimental).
*/
class ReflectionFactory extends Factory
{
private $mappings = [];

/**
* @see Factory::__construct()
*/
public function __construct(array $extensions = [])
{
parent::__construct($extensions);
$this->mappings = [
'string' => $this->string(),
'int' => $this->int(),
'float' => $this->float(),
'bool' => $this->bool(),
'array' => $this->mapping()->multiple(),
];
}

/**
* Creates a mapping from given type.
*
* @param string $type The type
*
* @return Mapping
*/
public function of($type)
{
$l2c = substr($type, -2);
if ('[]' === $l2c) {
$type = substr($type, 0, -2);
}

if (array_key_exists($type, $this->mappings)) {
$mapping = $this->mappings[$type];
} else {
$refl = new \ReflectionClass($type);
$mapping = new Mapping(new Emitter, $this->extensions);

foreach ($refl->getProperties() as $prop) {
if ($child = $this->mappingFromProp($prop)) {
$mapping->addChild($prop->getName(), $child);
}
}

$mapping->transformTo($type);
}

if ('[]' === $l2c) {
$mapping = $mapping->multiple();
}

return $mapping;
}

/**
* Creates a mapping from `ReflectionProperty`.
*
* @param ReflectionProperty $prop The reflection property
*
* @return Mapping
*/
private function mappingFromProp(\ReflectionProperty $prop)
{
$comment = $prop->getDocComment();
preg_match('/@var\s+(.*)[\s|\n]/', $comment, $matches);

if (!isset($matches[1])) {
return;
}

$annotation = $matches[1];
$type = $annotation;

if (ctype_upper($type[0]) && !class_exists($type)) {
$ns = $prop->getDeclaringClass()->getNamespaceName();
$type = $ns . '\\' . $type;
}

return $this->of($type)->optional();
}
}
34 changes: 30 additions & 4 deletions tests/Extension/TransformToTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,6 @@ public function testC()

public function testD()
{
// It's not possible to check if public object properties exists
// without reflection.
$this->setExpectedException('RuntimeException');

$factory = new Factory;
$user = new User('a', 'b');

Expand All @@ -85,4 +81,34 @@ public function testD()
'password' => 'passwd',
]);
}

public function testE()
{
$factory = new Factory;
$mapping = $factory->mapping()->transformTo('stdClass');

$data = [
'username' => 'dennis',
'password' => 'passwd',
'address' => [
'street' => 'Foo',
'city' => 'Bar',
],
];

$result = $mapping->apply($data);

$user = new \stdClass;
$user->username = 'dennis';
$user->password = 'passwd';
$address = new \stdClass;
$address->street = 'Foo';
$address->city = 'Bar';
$user->address = $address;

$this->assertEquals($user, $result);

$result = $mapping->unapply($user);
$this->assertEquals($data, $result);
}
}
5 changes: 5 additions & 0 deletions tests/Fixtures/Address.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,15 @@
class Address
{
/**
* @var string
* @Assert\NotBlank(message="not-blank")
*/
public $city;

/** @var string */
public $street;

/** @var Location */
public $location;

public function __construct($city, $street, $location = null)
Expand Down
3 changes: 3 additions & 0 deletions tests/Fixtures/Attribute.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ class Attribute
{
/**
* @Assert\NotBlank(message="not-blank")
* @var string
*/
public $name;

/** @var string */
public $value;

public function __construct($name, $value)
Expand Down
2 changes: 2 additions & 0 deletions tests/Fixtures/Book.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

class Book
{
/** @var string */
private $title;
/** @var string */
private $author;

public function __construct()
Expand Down
4 changes: 4 additions & 0 deletions tests/Fixtures/Post.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,16 @@ class Post
{
/**
* @Assert\NotBlank(message="not-blank")
* @var string
*/
public $title;

/** @var string[] */
public $tags = [];

/**
* @Assert\Valid
* @var Attribute[]
*/
public $attributes = [];

Expand Down
5 changes: 5 additions & 0 deletions tests/Fixtures/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@

class User
{
/** @var string */
public $username;

/** @var string */
public $password;

/** @var Address */
public $address;

public function __construct($username, $password, $address = null)
Expand Down
Loading

0 comments on commit adc51b5

Please sign in to comment.