Skip to content

Commit

Permalink
Add range name reference denormalizer (#903)
Browse files Browse the repository at this point in the history
Closes #692
  • Loading branch information
reinfi authored and theofidry committed Apr 7, 2018
1 parent 9140f9e commit ddcc459
Show file tree
Hide file tree
Showing 10 changed files with 391 additions and 4 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ for 2.x, head [here](https://github.com/nelmio/alice/tree/2.x)**.
1. [PHP](doc/complete-reference.md#php)
1. [Fixture Ranges](doc/complete-reference.md#fixture-ranges)
1. [Fixture Lists](doc/complete-reference.md#fixture-lists)
1. [Fixture Reference](doc/complete-reference.md#fixture-reference)
1. [Calling Methods](doc/complete-reference.md#calling-methods)
1. [Method arguments with flags](doc/complete-reference.md#method-arguments-with-flags)
1. [Method arguments with parameters](doc/complete-reference.md#method-arguments-with-parameters)
Expand Down
30 changes: 30 additions & 0 deletions doc/complete-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
1. [YAML](#yaml)
1. [PHP](#php)
1. [Fixture Ranges](#fixture-ranges)
1. [Fixture Reference](#fixture-reference)
1. [Fixture Lists](#fixture-lists)
1. [Calling Methods](#calling-methods)
1. [Method arguments with flags](#method-arguments-with-flags)
Expand Down Expand Up @@ -121,6 +122,35 @@ Nelmio\Entity\User:

To go further we the example above, we can just randomize data.

## Fixture Reference

You can also specify a reference to a previously created list of fixtures:

```yaml
Nelmio\Entity\User:
user_{1..10}:
username: '<name()>'
Nelmio\Entity\UserDetail:
userdetail_{@user_*}: # is going to generate `userdetail_user_1`, `userdetail_user_2`, ..., `userdetail_user_10`
user: <current()>
email: '<email()>'
```
You could either use a star to get all created fixtures matched by the reference or use just one by giving the full fixture name.
```yaml
Nelmio\Entity\User:
user_bob:
username: 'bob'

Nelmio\Entity\UserDetail:
userdetail_{@user_bob}:
       user: <current()>   # holds `@user_bob`
       email: 'bob@test.de'
```
>The `<current()>` function holds the value of the referenced fixture.

## Calling Methods

Expand Down
45 changes: 45 additions & 0 deletions fixtures/User.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

/*
* This file is part of the Alice package.
*
* (c) Nelmio <hello@nelm.io>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Nelmio\Alice;

class User
{
private $id;

private $name;

public function getId(): string
{
return $this->id;
}

public function setId(string $id): User
{
$this->id = $id;

return $this;
}

public function getName(): ?string
{
return $this->name;
}

public function setName(?string $name): User
{
$this->name = $name;

return $this;
}
}
51 changes: 51 additions & 0 deletions fixtures/UserDetail.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

/*
* This file is part of the Alice package.
*
* (c) Nelmio <hello@nelm.io>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Nelmio\Alice;

class UserDetail
{
/**
* @var string|null
*/
private $email;

/**
* @var User
*/
private $user;

public function getEmail(): ?string
{
return $this->email;
}

public function setEmail(?string $email): UserDetail
{
$this->email = $email;

return $this;
}

public function getUser(): User
{
return $this->user;
}

public function setUser(User $user): UserDetail
{
$this->user = $user;

return $this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@
class="Nelmio\Alice\FixtureBuilder\Denormalizer\Fixture\Chainable\NullRangeNameDenormalizer">
</service>

<service id="nelmio_alice.fixture_builder.denormalizer.fixture.chainable.reference_range_name"
class="Nelmio\Alice\FixtureBuilder\Denormalizer\Fixture\Chainable\ReferenceRangeNameDenormalizer">
<argument type="service" id="nelmio_alice.fixture_builder.denormalizer.specs.simple" />

<tag name="nelmio_alice.fixture_builder.denormalizer.chainable_fixture_denormalizer" />
</service>

<service id="nelmio_alice.fixture_builder.denormalizer.fixture.chainable.temporary_range"
class="Nelmio\Alice\FixtureBuilder\Denormalizer\Fixture\Chainable\CollectionDenormalizerWithTemporaryFixture">
<argument type="service" id="nelmio_alice.fixture_builder.denormalizer.fixture.chainable.null_range" />
Expand Down
10 changes: 8 additions & 2 deletions src/Definition/Fixture/SimpleFixture.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,17 @@ final class SimpleFixture implements FixtureInterface
private $specs;

/**
* @var string
* @var string|int|FixtureInterface
*/
private $valueForCurrent;

public function __construct(string $id, string $className, SpecificationBag $specs, string $valueForCurrent = null)
/**
* @param string $id
* @param string $className
* @param SpecificationBag $specs
* @param string|int|FixtureInterface|null $valueForCurrent
*/
public function __construct(string $id, string $className, SpecificationBag $specs, $valueForCurrent = null)
{
$this->id = $id;
$this->className = $className;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
<?php

/*
* This file is part of the Alice package.
*
* (c) Nelmio <hello@nelm.io>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Nelmio\Alice\FixtureBuilder\Denormalizer\Fixture\Chainable;

use Nelmio\Alice\Definition\Fixture\SimpleFixture;
use Nelmio\Alice\Definition\Fixture\SimpleFixtureWithFlags;
use Nelmio\Alice\Definition\Fixture\TemplatingFixture;
use Nelmio\Alice\Definition\FlagBag;
use Nelmio\Alice\Definition\MethodCallBag;
use Nelmio\Alice\Definition\PropertyBag;
use Nelmio\Alice\Definition\SpecificationBag;
use Nelmio\Alice\FixtureBag;
use Nelmio\Alice\FixtureBuilder\Denormalizer\Fixture\ChainableFixtureDenormalizerInterface;
use Nelmio\Alice\FixtureBuilder\Denormalizer\Fixture\SpecificationsDenormalizerInterface;
use Nelmio\Alice\FixtureBuilder\Denormalizer\FlagParserAwareInterface;
use Nelmio\Alice\FixtureBuilder\Denormalizer\FlagParserInterface;
use Nelmio\Alice\FixtureInterface;
use Nelmio\Alice\IsAServiceTrait;
use Nelmio\Alice\Throwable\Exception\LogicExceptionFactory;

final class ReferenceRangeNameDenormalizer implements ChainableFixtureDenormalizerInterface, FlagParserAwareInterface
{
use IsAServiceTrait;

private const REGEX = '/.+\{(?<expression>@(?<name>([A-Za-z0-9-_]+))(?<flag>(\*+))?)\}/';

/**
* @var FlagParserInterface|null
*/
private $flagParser;

/**
* @var SpecificationsDenormalizerInterface
*/
private $specsDenormalizer;

public function __construct(SpecificationsDenormalizerInterface $specsDenormalizer, FlagParserInterface $parser = null)
{
$this->specsDenormalizer = $specsDenormalizer;
$this->flagParser = $parser;
}

/**
* @inheritdoc
*/
public function withFlagParser(FlagParserInterface $parser): self
{
return new self($this->specsDenormalizer, $parser);
}

/**
* @inheritdoc
*/
public function canDenormalize(string $name, array &$matches = []): bool
{
return 1 === preg_match(self::REGEX, $name, $matches);
}

/**
* @inheritdoc
*/
public function denormalize(
FixtureBag $builtFixtures,
string $className,
string $fixtureId,
array $specs,
FlagBag $flags
): FixtureBag {
$matches = [];
if (false === $this->canDenormalize($fixtureId, $matches)) {
throw LogicExceptionFactory::createForCannotDenormalizerForChainableFixtureBuilderDenormalizer(__METHOD__);
}

$referencedName = $matches['name'];
$allFlag = ($matches['flag'] ?? null) === '*';

$fixtureIds = $this->buildReferencedValues($builtFixtures, $referencedName, $allFlag);

$fixtureIdPrefix = $this->determineFixtureIdPrefix($fixtureId);

foreach ($fixtureIds as $referencedFixtureId => $valueForCurrent) {
if ($valueForCurrent->isATemplate()) {
continue;
}

$builtFixtures = $builtFixtures->with(
$this->buildFixture(
$fixtureIdPrefix . $referencedFixtureId,
$className,
$specs,
$flags,
$valueForCurrent
)
);
}

return $builtFixtures;
}

/**
* @param FixtureBag $builtFixtures
* @param string $referencedName
* @param bool $allFlag
*
* @return TemplatingFixture[]
*/
private function buildReferencedValues(
FixtureBag $builtFixtures,
string $referencedName,
bool $allFlag
): array {
if (false === $allFlag) {
$fixture = $builtFixtures->get($referencedName);

return [$referencedName => $fixture];
}

$matchedFixtures = array_filter(
$builtFixtures->toArray(),
function (string $referenceName) use ($referencedName) {
return strpos($referenceName, $referencedName) === 0;
},
ARRAY_FILTER_USE_KEY
);

return $matchedFixtures;
}

private function determineFixtureIdPrefix(string $fixtureId): string
{
$matches = [];
if (false === $this->canDenormalize($fixtureId, $matches)) {
throw LogicExceptionFactory::createForCannotDenormalizerForChainableFixtureBuilderDenormalizer(__METHOD__);
}

return str_replace(
sprintf('{%s}', $matches['expression']),
'',
$matches[0]
);
}

private function buildFixture(
string $fixtureId,
string $className,
array $specs,
FlagBag $flags,
FixtureInterface $valueForCurrent
): FixtureInterface {
$fixture = new SimpleFixture(
$fixtureId,
$className,
new SpecificationBag(null, new PropertyBag(), new MethodCallBag()),
$valueForCurrent
);
$fixture = $fixture->withSpecs(
$this->specsDenormalizer->denormalize($fixture, $this->flagParser, $specs)
);

return new TemplatingFixture(
new SimpleFixtureWithFlags(
$fixture,
$flags->withKey($fixtureId)
)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

namespace Nelmio\Alice\Generator\Resolver\Value\Chainable;

use Nelmio\Alice\Definition\Fixture\SimpleFixture;
use Nelmio\Alice\Definition\Value\ValueForCurrentValue;
use Nelmio\Alice\Definition\ValueInterface;
use Nelmio\Alice\FixtureInterface;
Expand Down Expand Up @@ -49,8 +50,21 @@ public function resolve(
array $scope,
GenerationContext $context
): ResolvedValueWithFixtureSet {
$valueForCurrent = $fixture->getValueForCurrent();

if ($valueForCurrent instanceof FixtureInterface) {
$valueForCurrent = new SimpleFixture(
$valueForCurrent->getId(),
$valueForCurrent->getClassName(),
$valueForCurrent->getSpecs(),
$fixtureSet->getObjects()->get($valueForCurrent)->getInstance()
);
} else {
$valueForCurrent = $fixtureSet->getFixtures()->get($fixture->getId());
}

return new ResolvedValueWithFixtureSet(
$fixtureSet->getFixtures()->get($fixture->getId()),
$valueForCurrent,
$fixtureSet
);
}
Expand Down
Loading

0 comments on commit ddcc459

Please sign in to comment.