Skip to content
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

AbstractModel refactored; Tests updated #39

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions src/AbstractService.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Gietos\Dadata;

use Gietos\Dadata\Model\Factory\MainFactory;
use Gietos\Dadata\Model\Response\Error;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\ResponseInterface;
Expand Down Expand Up @@ -36,9 +37,7 @@ protected function getBaseUri(): string
*/
public function getResult(ResponseInterface $response, $expectedResponseClass)
{
$responseMediator = new JsonMediator;
$result = $responseMediator->getResult($response, $expectedResponseClass);

return $result;
$responseMediator = new JsonMediator(new MainFactory());
return $responseMediator->getResult($response, $expectedResponseClass);
}
}
50 changes: 23 additions & 27 deletions src/JsonMediator.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,50 +2,50 @@

namespace Gietos\Dadata;

use Gietos\Dadata\Model\ConfigurableInterface;
use Gietos\Dadata\Model\AbstractCollection;
use Gietos\Dadata\Model\Factory\MainFactory;
use Gietos\Dadata\Model\Response\Error;
use Psr\Http\Message\ResponseInterface;

class JsonMediator
{
/**
* @param int $code
* @param array $data
* @return Error
*/
protected function getError(int $code, array $data): Error
/** @var MainFactory */
private $factory;

public function __construct(MainFactory $factory)
{
$this->factory = $factory;
}

protected function getError(int $code, \stdClass $data): Error
{
return new Error($code, $data['detail']);
return new Error($code, property_exists($data, 'details') ? $data->detail : '');
}

/**
*
* @param string $className
* @param array $data
* @param mixed $data
* @return object
*/
protected function getObject($className, array $data)
protected function getObject(string $className, $data)
{
if (null === $className) {
throw new \InvalidArgumentException('Expected string, got NULL');
}

$object = new $className;
if (!$object instanceof ConfigurableInterface) {
throw new \InvalidArgumentException(sprintf('Class %s must implement ConfigurableInterface', $className));
$reflection = new \ReflectionClass($className);
if ($reflection->isSubclassOf(AbstractCollection::class)) {
return $this->factory->createCollection($reflection, $data);
}
$object->configure($data);

return $object;
return $this->factory->createObject($reflection, $data);
}

/**
* @param ResponseInterface $response
* @param string $expectedResponseClass
* @param string $expectedResponseClassName
* @return object|Error
*/
public function getResult(ResponseInterface $response, string $expectedResponseClass)
public function getResult(ResponseInterface $response, string $expectedResponseClassName)
{
$data = json_decode((string) $response->getBody(), true);
$data = json_decode((string) $response->getBody());

if (json_last_error() !== JSON_ERROR_NONE) {
throw new \Exception(sprintf('Could not parse JSON response: %s', json_last_error_msg()));
Expand All @@ -55,10 +55,6 @@ public function getResult(ResponseInterface $response, string $expectedResponseC
return $this->getError($response->getStatusCode(), $data);
}

if (!is_array($data)) {
throw new \Exception('Unexpected JSON response. Array is expected');
}

return $this->getObject($expectedResponseClass, $data);
return $this->getObject($expectedResponseClassName, $data);
}
}
26 changes: 5 additions & 21 deletions src/Model/AbstractCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

use Doctrine\Common\Collections\ArrayCollection;

abstract class AbstractCollection extends ArrayCollection implements ConfigurableInterface
abstract class AbstractCollection extends ArrayCollection
{
public function __construct(array $elements = [])
{
Expand All @@ -17,7 +17,7 @@ public function __construct(array $elements = [])
/**
* Declares which elements can this collection contain.
*/
abstract protected function getClass(): string;
abstract public function getElementClass(): string;

/**
* Checks if element collection constructed with has correct class.
Expand All @@ -31,33 +31,17 @@ public function validateElement($element)
'Invalid element of type %s passed to %s, expected: %s',
gettype($element),
get_class($this),
$this->getClass()
$this->getElementClass()
));
}

if (!is_a($element, $this->getClass())) {
if (!is_a($element, $this->getElementClass())) {
throw new \InvalidArgumentException(sprintf(
'Invalid element (instance of %s) passed to %s, expected: %s',
get_class($element),
get_class($this),
$this->getClass()
$this->getElementClass()
));
}
}

/**
* @param array $config
*/
public function configure(array $config = [])
{
$className = $this->getClass();
foreach ($config as $item) {
$reflection = new \ReflectionClass($className);
$object = $reflection->newInstanceWithoutConstructor();
if ($object instanceof ConfigurableInterface) {
$object->configure($item);
}
$this->add($object);
}
}
}
62 changes: 0 additions & 62 deletions src/Model/AbstractModel.php

This file was deleted.

5 changes: 1 addition & 4 deletions src/Model/ConfigurableInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,5 @@

interface ConfigurableInterface
{
/**
* @param array $config
*/
public function configure(array $config = []);
public function configure(\stdClass $config): void;
}
11 changes: 11 additions & 0 deletions src/Model/Factory/DateTimeImmutableFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace Gietos\Dadata\Model\Factory;

class DateTimeImmutableFactory
{
public static function create(string $value): \DateTimeImmutable
{
return new \DateTimeImmutable($value);
}
}
7 changes: 7 additions & 0 deletions src/Model/Factory/DateTimeInterfaceFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace Gietos\Dadata\Model\Factory;

class DateTimeInterfaceFactory extends DateTimeImmutableFactory
{
}
124 changes: 124 additions & 0 deletions src/Model/Factory/MainFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
<?php

namespace Gietos\Dadata\Model\Factory;

use Doctrine\Common\Inflector\Inflector;
use Gietos\Dadata\Model\AbstractCollection;
use Gietos\Dadata\Model\NullableCollection;

class MainFactory
{
/**
* @param mixed $prototype
* @return object
*/
public function createObject(\ReflectionClass $class, $prototype)
{
if ($factoryClass = $this->getCustomFactory($class)) {
return call_user_func_array([$factoryClass, 'create'], [$prototype]);
}

if (!$prototype instanceof \stdClass) {
throw new \TypeError(sprintf(
'Received unexpected value of type %s as a prototype for class %s. Instance of \stdClass expected',
gettype($prototype),
$class->getName()
));
}

$object = $class->newInstanceWithoutConstructor();
foreach ($prototype as $key => $value) {
$propertyName = Inflector::camelize($key);
$this->setAttribute($object, $propertyName, $value);
}

return $object;
}

private function createObjects(\ReflectionClass $class, ...$values): array
{
foreach ($values as $i => $value) {
$values[$i] = $this->createObject($class, $value);
}

return $values;
}

/**
* @return AbstractCollection
*/
public function createCollection(\ReflectionClass $class, ?array $elements)
{
/** @var AbstractCollection $collection */
$collection = $class->newInstance();

if ($collection instanceof NullableCollection && $elements === null) {
return $collection;
}

$itemClass = new \ReflectionClass($collection->getElementClass());
foreach ($elements as $elementPrototype) {
$collection->add(self::createObject($itemClass, $elementPrototype));
}
return $collection;
}

private function isAssoc(array $array): bool
{
if ($array === []) {
return false;
}

return array_keys($array) !== range(0, count($array) - 1);
}

private function getCustomFactory(\ReflectionClass $class): string
{
$factoryClass = sprintf(__NAMESPACE__ . '\%sFactory', $class->getShortName());
return class_exists($factoryClass) ? $factoryClass : '';
}

/**
* @param object $object
* @param string $name
* @param mixed $value
*/
public function setAttribute($object, string $name, $value): void
{
if (method_exists($object, 'unpack' . ucfirst($name))) {
$methodReflection = new \ReflectionMethod($object, 'unpack' . ucfirst($name));
} elseif (method_exists($object, 'set' . ucfirst($name))) {
$methodReflection = new \ReflectionMethod($object, 'set' . ucfirst($name));
}

if (isset($methodReflection)) {
$params = $methodReflection->getParameters();
if (count($params) !== 1) {
throw new \InvalidArgumentException(sprintf(
'Only methods with exactly 1 argument is supported. Unsupported method: %s',
$methodReflection->getName()
));
}

$param = $params[0];
if ($class = $param->getClass()) {
if (is_array($value) && !$this->isAssoc($value)) {
$methodReflection->invokeArgs($object, $this->createObjects($class, ...$value));

return;
} elseif ($value !== null) {
$value = $this->createObject($class, $value);
}
}
$methodReflection->invokeArgs($object, [$value]);

return;
}

if (property_exists($object, $name)) {
$property = new \ReflectionProperty($object, $name);
$property->setAccessible(true);
$property->setValue($object, $value);
}
}
}
Loading