Skip to content

Commit

Permalink
- new attribute IdentifiableValueListLoad
Browse files Browse the repository at this point in the history
  • Loading branch information
Arthur Mogliev committed Jan 30, 2022
1 parent d304644 commit 95c411a
Show file tree
Hide file tree
Showing 9 changed files with 671 additions and 3 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
- php: '8.0'
upload_coverage: true
has_unique_phpspec_tests: true
container: ghcr.io/articus/phpdbg-coveralls:${{ matrix.php }}_2.4.3_2021-10-23
container: ghcr.io/articus/phpdbg-coveralls:${{ matrix.php }}_2.5.1_2021-12-05
steps:
- name: Checkout code
uses: actions/checkout@v2
Expand Down
100 changes: 98 additions & 2 deletions docs/attributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,9 +248,10 @@ class Handler
```

## Build-in attributes
Library provides two attributes out of the box:
Library provides three attributes out of the box:

- `IdentifiableValueLoad` that uses [Data Transfer library](https://github.com/Articus/DataTransfer) to load some value by its identifier stored in request attribute
- `IdentifiableValueListLoad` that uses [Data Transfer library](https://github.com/Articus/DataTransfer) to load list of values by their identifiers stored in request
- `Transfer` that uses [Data Transfer library](https://github.com/Articus/DataTransfer) to construct DTO and fill it with request data only if this data is valid.

### `IdentifiableValueLoad` usage
Expand Down Expand Up @@ -279,7 +280,7 @@ class LoaderFactory
return $result;
}
]
])
]);
}
}
```
Expand Down Expand Up @@ -329,6 +330,101 @@ class Handler

For details see available options: `Articus\PathHandler\Attribute\Options\IdentifiableValueLoad`.

### `IdentifiableValueListLoad` usage

Add `Articus\DataTransfer\IdentifiableValueLoader` service inside your container, for example with a factory like:

```PHP
namespace My;

use Articus\DataTransfer\IdentifiableValueLoader;
use Psr\Container\ContainerInterface;

class LoaderFactory
{
public function __invoke(ContainerInterface $container)
{
return new IdentifiableValueLoader([
"entity_type" => [
static function (EntityClass $value) {
return $value->getId();//...or any other ay to get id from EntityClass instance
},
static function (array $ids) {
/** @var EntityClass[] $result */
$result = []
// load EntityClass instances for specified ids from database, external service, etc...
return $result;
}
]
]);
}
}
```

Then provide custom callable service inside your container for emitting identifiers from request, for example `entity_id_emitter` with a factory like:

```PHP
namespace My;

use Psr\Container\ContainerInterface;
use Psr\Http\Message\ServerRequestInterface as Request;

class EmitterFactory
{
public function __invoke(ContainerInterface $container)
{
return static function (string $type, Request $request)/*: array<int|string> */
{
return $request->getAttribute('attr_with_array_of_ids');
};
}
}
```

And then just add attribute to your handler:

```PHP
namespace My;

use Articus\PathHandler\Annotation as PHA;
use Psr\Http\Message\ServerRequestInterface;

/**
* @PHA\Route(pattern="/entities")
* @PHA\Attribute(name="IdentifiableValueListLoad", options={"id_emitter":"entity_id_emitter","type":"entity_type","list_attr":"entity_list"})
*/
class Handler
{
/**
* @PHA\Get()
*/
public function handleGet(ServerRequestInterface $request)
{
/** @var EntityClass[] $entities */
$entities = $request->getAttribute('entity_list');//This attribute will store list of loaded identifiable values
}
}
```
```PHP
namespace My;

use Articus\PathHandler\PhpAttribute as PHA;
use Psr\Http\Message\ServerRequestInterface;

#[PHA\Route("/entity/{entity_id:[1-9][0-9]*}")]
#[PHA\Attribute("IdentifiableValueLoad", ["id_emitter" => "entity_id_emitter", "type" => "entity_type", "list_attr" => "entity_list"])
class Handler
{
#[PHA\Get()]
public function handleGet(ServerRequestInterface $request)
{
/** @var EntityClass[] $entities */
$entities = $request->getAttribute('entity_list');//This attribute will store list of loaded identifiable values
}
}
```

For details see available options: `Articus\PathHandler\Attribute\Options\IdentifiableValueListLoad`.

### `Transfer` usage

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);

namespace spec\Articus\PathHandler\Attribute\Factory;

use Articus\DataTransfer\IdentifiableValueLoader;
use Articus\PathHandler as PH;
use Interop\Container\ContainerInterface;
use PhpSpec\ObjectBehavior;
use PhpSpec\Wrapper\Subject;
use Psr\Http\Message\ServerRequestInterface as Request;

class IdentifiableValueListLoadSpec extends ObjectBehavior
{
public function it_builds_attribute_with_simple_config(
ContainerInterface $container,
IdentifiableValueLoader $loader,
Request $in,
Request $out
)
{
$type = 'test_type';
$ids = [123, 456, 789];
$values = ['abc', 'def', 'ghi'];
$options = [
'type' => $type,
'identifierEmitter' => 'test_emitter',
];
$emitter = static function (string $type, Request $request)
{
return $request->getAttribute('ids');
};

$container->get(IdentifiableValueLoader::class)->shouldBeCalledOnce()->willReturn($loader);
$container->get('test_emitter')->shouldBeCalledOnce()->willReturn($emitter);
$loader->wishMultiple($type, $ids)->shouldBeCalledOnce();
$loader->get($type, $ids[0])->shouldBeCalledOnce()->willReturn($values[0]);
$loader->get($type, $ids[1])->shouldBeCalledOnce()->willReturn($values[1]);
$loader->get($type, $ids[2])->shouldBeCalledOnce()->willReturn($values[2]);
$in->getAttribute('ids')->shouldBeCalledOnce()->willReturn($ids);
$in->withAttribute('list', $values)->shouldBeCalledOnce()->willReturn($out);

/** @var Subject $wrapper */
$wrapper = $this->__invoke($container, 'test', $options);
$wrapper->shouldBeAnInstanceOf(PH\Attribute\IdentifiableValueListLoad::class);
$wrapper->callOnWrappedObject('__invoke', [$in])->shouldBe($out);
}
}
145 changes: 145 additions & 0 deletions spec/Articus/PathHandler/Attribute/IdentifiableValueListLoadSpec.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
<?php
declare(strict_types=1);

namespace spec\Articus\PathHandler\Attribute;

use spec\Example\InstanciatorInterface as Invokable;
use Articus\DataTransfer\IdentifiableValueLoader;
use Articus\PathHandler as PH;
use PhpSpec\ObjectBehavior;
use Psr\Http\Message\ServerRequestInterface as Request;

class IdentifiableValueListLoadSpec extends ObjectBehavior
{
public function it_loads_and_stores_values_if_emitter_and_receiver_do_not_have_arg_attrs(
IdentifiableValueLoader $loader,
Invokable $emitter,
Invokable $receiverFactory,
$list,
Request $in,
Request $out
)
{
$type = 'test_type';
$ids = [123, 456, 789];
$values = ['abc', 'def', 'ghi'];
$emitterArgAttrs = [];
$receiver = static function () use ($ids, $values, &$list)
{
foreach ($ids as $index => $id)
{
$tuple = yield;
if ($tuple !== [$index, $id, $values[$index]])
{
throw new \LogicException('Invalid tuple');
}
}
return $list;
};
$receiverFactoryArgAttrs = [];
$listAttr = 'test_list_attr';

$loader->wishMultiple($type, $ids)->shouldBeCalledOnce();
$loader->get($type, $ids[0])->shouldBeCalledOnce()->willReturn($values[0]);
$loader->get($type, $ids[1])->shouldBeCalledOnce()->willReturn($values[1]);
$loader->get($type, $ids[2])->shouldBeCalledOnce()->willReturn($values[2]);
$emitter->__invoke($type, $in)->shouldBeCalledOnce()->willReturn($ids);
$receiverFactory->__invoke($type, $in)->shouldBeCalledOnce()->willReturn($receiver());
$in->withAttribute($listAttr, $list)->shouldBeCalledOnce()->willReturn($out);

$this->beConstructedWith($loader, $type, $emitter, $emitterArgAttrs, $receiverFactory, $receiverFactoryArgAttrs, $listAttr);
$this->__invoke($in)->shouldBe($out);
}

public function it_loads_and_stores_values_if_emitter_and_receiver_have_arg_attrs(
IdentifiableValueLoader $loader,
Invokable $emitter,
Invokable $receiverFactory,
$list,
Request $in,
Request $out
)
{
$type = 'test_type';
$ids = [123, 456, 789];
$values = ['abc', 'def', 'ghi'];
$emitterArgAttrs = ['test_e_arg_attr1', 'test_e_arg_attr2'];
$emitterArgs = ['test_e_arg1', 'test_e_arg2'];
$receiver = static function () use ($ids, $values, &$list)
{
foreach ($ids as $index => $id)
{
$tuple = yield;
if ($tuple !== [$index, $id, $values[$index]])
{
throw new \LogicException('Invalid tuple');
}
}
return $list;
};
$receiverFactoryArgAttrs = ['test_rf_arg_attr1', 'test_rf_arg_attr2'];
$receiverFactoryArgs = ['test_rf_arg1', 'test_rf_arg2'];
$listAttr = 'test_list_attr';

$loader->wishMultiple($type, $ids)->shouldBeCalledOnce();
$loader->get($type, $ids[0])->shouldBeCalledOnce()->willReturn($values[0]);
$loader->get($type, $ids[1])->shouldBeCalledOnce()->willReturn($values[1]);
$loader->get($type, $ids[2])->shouldBeCalledOnce()->willReturn($values[2]);
$emitter->__invoke($type, $emitterArgs[0], $emitterArgs[1])->shouldBeCalledOnce()->willReturn($ids);
$receiverFactory->__invoke($type, $receiverFactoryArgs[0], $receiverFactoryArgs[1])->shouldBeCalledOnce()->willReturn($receiver());
$in->getAttribute($emitterArgAttrs[0])->shouldBeCalledOnce()->willReturn($emitterArgs[0]);
$in->getAttribute($emitterArgAttrs[1])->shouldBeCalledOnce()->willReturn($emitterArgs[1]);
$in->getAttribute($receiverFactoryArgAttrs[0])->shouldBeCalledOnce()->willReturn($receiverFactoryArgs[0]);
$in->getAttribute($receiverFactoryArgAttrs[1])->shouldBeCalledOnce()->willReturn($receiverFactoryArgs[1]);
$in->withAttribute($listAttr, $list)->shouldBeCalledOnce()->willReturn($out);

$this->beConstructedWith($loader, $type, $emitter, $emitterArgAttrs, $receiverFactory, $receiverFactoryArgAttrs, $listAttr);
$this->__invoke($in)->shouldBe($out);
}

public function it_throws_if_there_is_no_value_for_one_of_ids(
IdentifiableValueLoader $loader,
Invokable $emitter,
Invokable $receiverFactory,
$list,
Request $in,
Request $out
)
{
$type = 'test_type';
$ids = [12, 34, 56, 78];
$values = ['abc', null, 'def', null];
$emitterArgAttrs = [];
$receiver = static function () use ($ids, $values, &$list)
{
foreach ($ids as $index => $id)
{
if ($values[$index] !== null)
{
$tuple = yield;
if ($tuple !== [$index, $id, $values[$index]])
{
throw new \LogicException('Invalid tuple');
}
}
}
return $list;
};
$receiverFactoryArgAttrs = [];
$listAttr = 'test_list_attr';
$error = new PH\Exception\UnprocessableEntity([
'unknownIdentifiers' => 'Unknown identifier(s): 34, 78'
]);

$loader->wishMultiple($type, $ids)->shouldBeCalledOnce();
$loader->get($type, $ids[0])->shouldBeCalledOnce()->willReturn($values[0]);
$loader->get($type, $ids[1])->shouldBeCalledOnce()->willReturn($values[1]);
$loader->get($type, $ids[2])->shouldBeCalledOnce()->willReturn($values[2]);
$loader->get($type, $ids[3])->shouldBeCalledOnce()->willReturn($values[3]);
$emitter->__invoke($type, $in)->shouldBeCalledOnce()->willReturn($ids);
$receiverFactory->__invoke($type, $in)->shouldBeCalledOnce()->willReturn($receiver());

$this->beConstructedWith($loader, $type, $emitter, $emitterArgAttrs, $receiverFactory, $receiverFactoryArgAttrs, $listAttr);
$this->shouldThrow($error)->during('__invoke', [$in]);
}
}
Loading

0 comments on commit 95c411a

Please sign in to comment.