Skip to content

Commit

Permalink
Merge 7255ca7 into d26cb12
Browse files Browse the repository at this point in the history
  • Loading branch information
OndraM committed Nov 21, 2017
2 parents d26cb12 + 7255ca7 commit 6d144f8
Show file tree
Hide file tree
Showing 9 changed files with 325 additions and 2 deletions.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
],
"analyze": [
"vendor/bin/php-cs-fixer fix -vvv --diff --dry-run --ansi",
"vendor/bin/phpstan.phar analyze ./src ./tests --level 7 --ansi"
"vendor/bin/phpstan.phar analyze ./src ./tests -c phpstan.neon --level 7 --ansi"
]
}
}
3 changes: 3 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
parameters:
ignoreErrors:
- '#expects .*, PHPUnit_Framework_MockObject_MockObject given#'
2 changes: 1 addition & 1 deletion src/Exception/InvalidDomainModelArgumentException.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
/**
* Exception thrown when invalid argument is passed while creating domain model.
*/
class InvalidDomainModelArgumentException extends AbstractMatejException
class InvalidDomainModelArgumentException extends LogicException
{
public static function forInconsistentNumberOfCommands(
int $numberOfCommands,
Expand Down
10 changes: 10 additions & 0 deletions src/Exception/LogicException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php declare(strict_types=1);

namespace Lmc\Matej\Exception;

/**
* Exception represents error in the program logic.
*/
class LogicException extends AbstractMatejException
{
}
54 changes: 54 additions & 0 deletions src/RequestBuilder/AbstractRequestBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php declare(strict_types=1);

namespace Lmc\Matej\RequestBuilder;

use Lmc\Matej\Exception\LogicException;
use Lmc\Matej\Http\RequestManager;
use Lmc\Matej\Model\Request;
use Lmc\Matej\Model\Response;

/**
* Request builders provides methods for simple and type-safe assembly of request to specific Matej endpoint.
*
* If `RequestManager` is injected to the builder via `setRequestManager()`, the request could be executed right from
* the builder using `send()` method.
*/
abstract class AbstractRequestBuilder
{
/** @var RequestManager */
protected $requestManager;

/**
* Use Commands and other settings which were passed to this builder object to build instance of
* Lmc\Matej\Model\Request.
*/
abstract public function build(): Request;

/**
* If instance of RequestManager is injected to this builder object, you can build and send the request directly
* via send() method of the builder itself.
*/
public function setRequestManager(RequestManager $requestManager): self
{
$this->requestManager = $requestManager;

return $this;
}

public function send(): Response
{
$this->assertRequestManagerIsAvailable();

return $this->requestManager->sendRequest($this->build());
}

private function assertRequestManagerIsAvailable(): void
{
if ($this->requestManager === null) {
throw new LogicException(
'Instance of RequestManager must be set to request builder via setRequestManager() before'
. ' calling send()'
);
}
}
}
62 changes: 62 additions & 0 deletions src/RequestBuilder/ItemPropertiesSetupRequestBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php declare(strict_types=1);

namespace Lmc\Matej\RequestBuilder;

use Fig\Http\Message\RequestMethodInterface;
use Lmc\Matej\Exception\LogicException;
use Lmc\Matej\Model\Command\ItemPropertySetup;
use Lmc\Matej\Model\Request;

class ItemPropertiesSetupRequestBuilder extends AbstractRequestBuilder
{
protected const ENDPOINT_PATH = '/item-properties';

/** @var ItemPropertySetup[] */
protected $commands = [];
/** @var bool */
protected $willDelete = false;

public function addProperty(ItemPropertySetup $itemPropertySetup): self
{
$this->commands[] = $itemPropertySetup;

return $this;
}

/**
* @param ItemPropertySetup[] $itemPropertiesSetup
* @return self
*/
public function addProperties(array $itemPropertiesSetup): self
{
foreach ($itemPropertiesSetup as $itemPropertySetup) {
$this->addProperty($itemPropertySetup);
}

return $this;
}

/**
* Mark the request as "deleting" - item properties added to this request will be IRREVERSIBLY removed from
* all items in the database and the item property will from now be rejected by Matej.
*/
public function requestWillDelete(): self
{
$this->willDelete = true;

return $this;
}

public function build(): Request
{
if (empty($this->commands)) {
throw new LogicException(
'At least one ItemPropertySetup command must be added to the builder before sending the request'
);
}

$method = $this->willDelete ? RequestMethodInterface::METHOD_DELETE : RequestMethodInterface::METHOD_PUT;

return new Request(self::ENDPOINT_PATH, $method, $this->commands);
}
}
41 changes: 41 additions & 0 deletions src/RequestBuilder/RequestBuilderFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php declare(strict_types=1);

namespace Lmc\Matej\RequestBuilder;

use Lmc\Matej\Http\RequestManager;

/**
* Factory to create concrete RequestBuilder which helps you to create request for each Matej API
*/
class RequestBuilderFactory
{
/** @var RequestManager */
private $requestManager;

public function __construct(RequestManager $requestManager)
{
$this->requestManager = $requestManager;
}

public function itemPropertiesSetup(): ItemPropertiesSetupRequestBuilder
{
return $this->createConfiguredBuilder(ItemPropertiesSetupRequestBuilder::class);
}

// TODO: builders for other endpoints

/**
* @param string $builderClass
* @param array ...$args
* @return mixed
*/
private function createConfiguredBuilder(string $builderClass, ...$args)
{
/** @var AbstractRequestBuilder $requestBuilder */
$requestBuilder = new $builderClass(...$args);

$requestBuilder->setRequestManager($this->requestManager);

return $requestBuilder;
}
}
94 changes: 94 additions & 0 deletions tests/RequestBuilder/ItemPropertiesSetupRequestBuilderTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<?php declare(strict_types=1);

namespace Lmc\Matej\RequestBuilder;

use Fig\Http\Message\RequestMethodInterface;
use Lmc\Matej\Exception\LogicException;
use Lmc\Matej\Http\RequestManager;
use Lmc\Matej\Model\Command\ItemPropertySetup;
use Lmc\Matej\Model\Request;
use Lmc\Matej\Model\Response;
use PHPUnit\Framework\TestCase;

class ItemPropertiesSetupRequestBuilderTest extends TestCase
{
/** @test */
public function shouldThrowExceptionWhenBuildingEmptyCommands(): void
{
$builder = new ItemPropertiesSetupRequestBuilder();

$this->expectException(LogicException::class);
$this->expectExceptionMessage('At least one ItemPropertySetup command must be added to the builder');
$builder->build();
}

/**
* @test
* @dataProvider provideBuilderVariants
*/
public function shouldBuildRequestWithCommands(bool $shouldDelete, string $expectedMethod): void
{
$builder = new ItemPropertiesSetupRequestBuilder();

$command1 = ItemPropertySetup::timestamp('property1');
$command2 = ItemPropertySetup::string('property2');
$command3 = ItemPropertySetup::string('property3');

$builder->addProperty($command1);
$builder->addProperties([$command2, $command3]);

if ($shouldDelete) {
$builder->requestWillDelete();
}

$request = $builder->build();

$this->assertInstanceOf(Request::class, $request);
$this->assertSame($expectedMethod, $request->getMethod());
$this->assertSame('/item-properties', $request->getPath());
$this->assertContainsOnlyInstancesOf(ItemPropertySetup::class, $request->getData());
$this->assertSame($command1, $request->getData()[0]);
$this->assertSame($command2, $request->getData()[1]);
$this->assertSame($command3, $request->getData()[2]);
}

/**
* @return array[]
*/
public function provideBuilderVariants(): array
{
return [
'builder to create item properties' => [false, RequestMethodInterface::METHOD_PUT],
'builder to delete item properties' => [true, RequestMethodInterface::METHOD_DELETE],
];
}

/** @test */
public function shouldThrowExceptionWhenSendingCommandsWithoutRequestManager(): void
{
$builder = new ItemPropertiesSetupRequestBuilder();

$builder->addProperty(ItemPropertySetup::timestamp('property1'));

$this->expectException(LogicException::class);
$this->expectExceptionMessage('Instance of RequestManager must be set to request builder');
$builder->send();
}

/** @test */
public function shouldSendRequestViaRequestManager(): void
{
$requestManagerMock = $this->createMock(RequestManager::class);
$requestManagerMock->expects($this->once())
->method('sendRequest')
->with($this->isInstanceOf(Request::class))
->willReturn(new Response(0, 0, 0, 0));

$builder = new ItemPropertiesSetupRequestBuilder();
$builder->setRequestManager($requestManagerMock);

$builder->addProperty(ItemPropertySetup::timestamp('property1'));

$builder->send();
}
}
59 changes: 59 additions & 0 deletions tests/RequestBuilder/RequestBuilderFactoryTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php declare(strict_types=1);

namespace Lmc\Matej\RequestBuilder;

use Lmc\Matej\Http\RequestManager;
use Lmc\Matej\Model\Command\ItemPropertySetup;
use Lmc\Matej\Model\Request;
use Lmc\Matej\Model\Response;
use PHPUnit\Framework\TestCase;

/**
* @covers \Lmc\Matej\RequestBuilder\RequestBuilderFactory
*/
class RequestBuilderFactoryTest extends TestCase
{
/**
* @test
* @dataProvider provideBuilderMethods
*/
public function shouldInstantiateBuilderToBuildAndSendRequest(
string $factoryMethod,
string $expectedBuilderClass,
\Closure $minimalBuilderInit
): void {
$requestManagerMock = $this->createMock(RequestManager::class);
$requestManagerMock->expects($this->once())
->method('sendRequest')
->with($this->isInstanceOf(Request::class))
->willReturn(new Response(0, 0, 0, 0));

$factory = new RequestBuilderFactory($requestManagerMock);

/** @var AbstractRequestBuilder $builder */
$builder = $factory->$factoryMethod();

// Builders may require some minimal setup to be able to execute the build() method
$minimalBuilderInit($builder);

$this->assertInstanceOf($expectedBuilderClass, $builder);
$this->assertInstanceOf(Request::class, $builder->build());

// Make sure the builder has been properly configured and it can execute send() via RequestManager mock:
$this->assertInstanceOf(Response::class, $builder->send());
}

/**
* @return array[]
*/
public function provideBuilderMethods(): array
{
$itemPropertiesSetupInit = function (ItemPropertiesSetupRequestBuilder $builder): void {
$builder->addProperty(ItemPropertySetup::timestamp('valid_from'));
};

return [
['itemPropertiesSetup', ItemPropertiesSetupRequestBuilder::class, $itemPropertiesSetupInit],
];
}
}

0 comments on commit 6d144f8

Please sign in to comment.