-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'develop'. Prepare release v0.2.0
- Loading branch information
Showing
8 changed files
with
408 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# CHANGELOG | ||
|
||
## 0.2.0 | ||
|
||
### Features | ||
|
||
- Introduced a new `AbstractDelegateHydrator` class to allow for implementing a hydrator while using a delegate callback | ||
- While this facilitates simple method delegation, its real design was to allow for the use of type-hinted hydrators | ||
that could circumvent PHP's type-system limitations. | ||
- For more info, read the class doc-block of the new `AbstractDelegateHydrator` class |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
all: install test lint checkstyle | ||
all: install test lint check-style | ||
|
||
install: | ||
composer install --prefer-dist | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
<?php | ||
/** | ||
* Incoming | ||
* | ||
* @author Trevor Suarez (Rican7) | ||
* @copyright (c) Trevor Suarez | ||
* @link https://github.com/Rican7/incoming | ||
* @license MIT | ||
*/ | ||
|
||
namespace Incoming\Hydrator; | ||
|
||
use Incoming\Hydrator\Exception\InvalidDelegateException; | ||
|
||
/** | ||
* AbstractDelegateHydrator | ||
* | ||
* An abstract hydrator that allows for the hydration to be delegated to another | ||
* callable. By default, a named method is attempted to be found, but any | ||
* callable could be returned through overrides. | ||
* | ||
* This enables a lot of interesting uses, most notably this allows hydrators to | ||
* be created that have strongly type-hinted hydration arguments while still | ||
* perfectly satisfying the `HydratorInterface`. Essentially this allows the | ||
* bypassing of the type variance rules enforced by PHP in a way that provides a | ||
* generics-like definition. Ultimately, if/when PHP gets generics this will no | ||
* longer be necessary, as one could simply implement a hydrator using typed | ||
* arguments like: `HydratorInterface<IncomingDataType, ModelType>` | ||
* | ||
* @link http://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science) | ||
* @link http://en.wikipedia.org/wiki/Generic_programming | ||
*/ | ||
abstract class AbstractDelegateHydrator implements HydratorInterface | ||
{ | ||
|
||
/** | ||
* Constants | ||
*/ | ||
|
||
/** | ||
* The name of the default delegate method | ||
* | ||
* @type string | ||
*/ | ||
const DEFAULT_DELEGATE_METHOD_NAME = 'hydrateModel'; | ||
|
||
|
||
/** | ||
* Methods | ||
*/ | ||
|
||
/** | ||
* {@inheritdoc} | ||
* | ||
* @param mixed $incoming The input data | ||
* @param mixed $model The model to hydrate | ||
* @return mixed The hydrated model | ||
*/ | ||
public function hydrate($incoming, $model) | ||
{ | ||
return call_user_func( | ||
$this->getDelegate(), | ||
$incoming, | ||
$model | ||
); | ||
} | ||
|
||
/** | ||
* Get the delegate hydration callable | ||
* | ||
* Override this method if a custom delegate is desired | ||
* | ||
* @return callable The delegate hydrator callable | ||
*/ | ||
protected function getDelegate() | ||
{ | ||
$delegate = [$this, static::DEFAULT_DELEGATE_METHOD_NAME]; | ||
|
||
if (!is_callable($delegate, false, $callable_name)) { | ||
throw InvalidDelegateException::forNonCallable($callable_name); | ||
} | ||
|
||
return $delegate; | ||
} | ||
|
||
/** | ||
* The delegate hydrate method | ||
* | ||
* This doc-block and commented out abstract method is provided here to show | ||
* what the delegate method signature WOULD be if PHP allowed the proper | ||
* typing support to enable a generic definition in this manner | ||
* | ||
* See the class description for more info | ||
* | ||
* @param IncomingDataType $incoming The input data | ||
* @param ModelType $model The model to hydrate | ||
* @return ModelType The hydrated model | ||
*/ | ||
// abstract protected function hydrateModel(IncomingDataType $incoming, ModelType $model); | ||
} |
93 changes: 93 additions & 0 deletions
93
src/Incoming/Hydrator/Exception/InvalidDelegateException.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
<?php | ||
/** | ||
* Incoming | ||
* | ||
* @author Trevor Suarez (Rican7) | ||
* @copyright (c) Trevor Suarez | ||
* @link https://github.com/Rican7/incoming | ||
* @license MIT | ||
*/ | ||
|
||
namespace Incoming\Hydrator\Exception; | ||
|
||
use BadFunctionCallException; | ||
use Exception; | ||
|
||
/** | ||
* InvalidDelegateException | ||
* | ||
* An exception to be thrown when an invalid delegate method, function, or | ||
* callback is provided to a caller | ||
*/ | ||
class InvalidDelegateException extends BadFunctionCallException | ||
{ | ||
|
||
/** | ||
* Constants | ||
*/ | ||
|
||
/** | ||
* @type string | ||
*/ | ||
const DEFAULT_MESSAGE = 'Invalid delegate'; | ||
|
||
/** | ||
* The exception code for when a delegate isn't callable | ||
* | ||
* @type int | ||
*/ | ||
const CODE_FOR_NON_CALLABLE = 1; | ||
|
||
/** | ||
* The message extension for when a delegate isn't callable | ||
* | ||
* @type string | ||
*/ | ||
const MESSAGE_EXTENSION_FOR_NON_CALLABLE = ' is unable to be called'; | ||
|
||
/** | ||
* The message extension format for when a delegate's name is provided | ||
* | ||
* @type string | ||
*/ | ||
const MESSAGE_EXTENSION_NAME_FORMAT = ' named `%s`'; | ||
|
||
|
||
/** | ||
* Properties | ||
*/ | ||
|
||
/** | ||
* @type string | ||
*/ | ||
protected $message = self::DEFAULT_MESSAGE; | ||
|
||
|
||
/** | ||
* Methods | ||
*/ | ||
|
||
/** | ||
* Create an exception instance for a delegate that isn't callable | ||
* | ||
* @param mixed|null $name The name of the delegate | ||
* @param int $code The exception code | ||
* @param Exception|null $previous A previous exception used for chaining | ||
* @return InvalidDelegateException The newly created exception | ||
*/ | ||
public static function forNonCallable($name = null, $code = self::CODE_FOR_NON_CALLABLE, Exception $previous = null) | ||
{ | ||
$message = self::DEFAULT_MESSAGE; | ||
|
||
if (null !== $name) { | ||
$message .= sprintf( | ||
self::MESSAGE_EXTENSION_NAME_FORMAT, | ||
$name | ||
); | ||
} | ||
|
||
$message .= self::MESSAGE_EXTENSION_FOR_NON_CALLABLE; | ||
|
||
return new static($message, $code, $previous); | ||
} | ||
} |
98 changes: 98 additions & 0 deletions
98
tests/Incoming/Test/Hydrator/AbstractDelegateHydratorTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
<?php | ||
/** | ||
* Incoming | ||
* | ||
* @author Trevor Suarez (Rican7) | ||
* @copyright (c) Trevor Suarez | ||
* @link https://github.com/Rican7/incoming | ||
* @license MIT | ||
*/ | ||
|
||
namespace Incoming\Test\Hydrator; | ||
|
||
use DateTime; | ||
use Incoming\Hydrator\AbstractDelegateHydrator; | ||
use Incoming\Structure\Map; | ||
use Incoming\Test\Hydrator\MockDelegateHydrator; | ||
use PHPUnit_Framework_TestCase; | ||
|
||
/** | ||
* AbstractDelegateHydratorTest | ||
*/ | ||
class AbstractDelegateHydratorTest extends PHPUnit_Framework_TestCase | ||
{ | ||
|
||
/** | ||
* Helpers | ||
*/ | ||
|
||
private function getMockDelegateHydrator(callable $delegate) | ||
{ | ||
$mock = $this->getMockBuilder('Incoming\Hydrator\AbstractDelegateHydrator') | ||
->setMethods([AbstractDelegateHydrator::DEFAULT_DELEGATE_METHOD_NAME]) | ||
->getMock(); | ||
|
||
$mock->expects($this->any()) | ||
->method(AbstractDelegateHydrator::DEFAULT_DELEGATE_METHOD_NAME) | ||
->will($this->returnCallback($delegate)); | ||
|
||
return $mock; | ||
} | ||
|
||
|
||
/** | ||
* Tests | ||
*/ | ||
|
||
public function testHydrate() | ||
{ | ||
$test_input_data = Map::fromArray([ | ||
'year' => 1983, | ||
'month' => 1, | ||
'day' => 2, | ||
]); | ||
$test_model = new DateTime(); | ||
|
||
$test_delegate_callable = function (Map $incoming, DateTime $model) { | ||
$model->setDate( | ||
$incoming->get('year'), | ||
$incoming->get('month'), | ||
$incoming->get('day') | ||
); | ||
|
||
return $model; | ||
}; | ||
|
||
$test_hydrator = $this->getMockDelegateHydrator($test_delegate_callable); | ||
|
||
$hydrated = $test_hydrator->hydrate($test_input_data, $test_model); | ||
|
||
$this->assertEquals($test_model, $hydrated); | ||
$this->assertSame($test_input_data['year'], (int) $hydrated->format('Y')); | ||
$this->assertSame($test_input_data['month'], (int) $hydrated->format('m')); | ||
$this->assertSame($test_input_data['day'], (int) $hydrated->format('j')); | ||
} | ||
|
||
/** | ||
* @expectedException Incoming\Hydrator\Exception\InvalidDelegateException | ||
*/ | ||
public function testHydrateWithNonCallableThrowsException() | ||
{ | ||
$mock_hydrator = new MockDelegateHydrator(); | ||
|
||
$mock_hydrator->hydrate([], new DateTime()); | ||
} | ||
|
||
/** | ||
* @expectedException PHPUnit_Framework_Error | ||
*/ | ||
public function testHydrateWithImproperTypesCausesTypeError() | ||
{ | ||
$test_delegate_callable = function (Map $incoming, DateTime $model) { | ||
}; | ||
|
||
$test_hydrator = $this->getMockDelegateHydrator($test_delegate_callable); | ||
|
||
$test_hydrator->hydrate([], new DateTime()); | ||
} | ||
} |
Oops, something went wrong.