The main performance bottleneck is usually the unique values handling. As you can see in the UniqueValueResolver, the resolver tries to generate a value, check if that value has already been generated for a given scope (the check is rather expensive), and try to generate another one again until it either get a new value or the maximum number of attempt is reached.
If you are interested in seeing how huge the impact can be, you can compare the scenario 2 profiling fixtures with the scenario 3 profiling fixtures with Blackfire.
A the moment, alice does not focus on generating huge sets of object either (lack of use cases) as alice objects are often persister after with an ORM, ORM which are not designed to persist huge sets of objects. If however you are interested in that scenario, feel free to investigate ;)
'<{X}>'
where X
is evaluated and can be anything.
Warning: nested parameters are currently not supported.
Escaped expression: '\<{X}>'
(will return the string '<{X}>'
).
Example:
parameters:
foo: 'bar'
stdClass:
dummy:
foo: '<{foo}>'
escapedFoo: '\<{foo}>'
will result in:
[
'dummy' => stdClass {
+"foo": "bar",
+"escapedFoo": "<{foo}>",
}
]
'<function(X)>'
where X
is evaluated and can be anything.
function
can be a Faker or a PHP native (or registered in the global scope) function.
Escaped expression: '\<function(X)>'
(will return the string '<function(X)>'
).
stdClass:
dummy:
functionValue: '<strtolower("BAR")>'
nestedFunctionValue: '<strtolower(<(implode(" ", ["HELLO", "WORLD", \<foo()>]))>)> \<bar()>'
will result in:
[
'dummy' => stdClass {
+"functionValue": "bar",
+"nestedFunctionValue": "hellow world <foo()> <bar()>",
}
]
'<(X)>'
shortcut for <identity(X)>
Returns X
evaluated as a PHP element, e.g. 2 + $nbr
(provided $nbr === 2
) will result in4
.
When using the identity function, you will still have access to:
- The value current (result of
<current()>
) with the variable$current
- The existing variables
- The reference to another fixture (via the
@fixtureId
notation)
Escaped expression: '\<(X)>'
(will return the string '<(X)>'
).
Example:
# Simple example
stdClass:
dummy:
foo: '<(strtolower("BAR"))>'
will result in:
[
'dummy' => stdClass {
+"foo": "bar",
}
]
# Example with current
stdClass:
dummy{1..2}:
foo: '<($current)>'
will result in:
[
'dummy1' => stdClass {
+"foo": "1",
},
'dummy2' => stdClass {
+"foo": "2",
}
]
# Example with variables and references
stdClass:
dummy1:
foo: '<($current)>'
will result in:
[
'dummy1' => stdClass {
+"foo": "1",
},
'dummy2' => stdClass {
+"foo": "2",
}
]
Regular arrays: [X, Y, ... Z]
.
Each element of the array is evaluated and can be anything.
String arrays: 'Xx Y'
X
must be either a positive integer or a function/param, e.g.10
or<numberBetween(0,20)>
Y
must be a non-empty string- This expression cannot be surrounded, i.e.
'foo 10x bar'
is invalid and'10x foo bar'
match the above pattern withX='10'
andY='foo bar'
.
String array '[X, Y, ... Z]'
Similar to the regular array, the difference is that the annotation is a string one, useful for being able to use the previous pattern with arrays: '10x [@user*->name, @group->name]'
.
The expression cannot be surrounded either, otherwise will be interpreted as a plain string (this does not mean that an element could not be evaluated, but it certainly won't return the expected result.
Escaped expression: '\[X]'
(will return the string '[X]'
.
D%? X: Y
where D
must be an element of ]0;100[
and X
, Y
are evaluated
- If
Y
isnull
, the condition is writtenD%? X
instead. X
must be an non-empty string and must not contain the:
Y
must be a non-empty string and must not contain a space as it is used as a delimiter to determine the end ofY
. So20%? foo: bar baz
will be matched with(D, X, Y)=('20', 'foo', 'bar')
('baz'
is not part ofY
).
@ref
where ref
is evaluated as a reference.
ref
must be first resolved like any value to get a string from it. Once done, the resulting value can be used to find a reference matching that mask.
Examples:
@user{1..2}
=>@user1
or@user2
@user*
@user_{alice, bob}
=>@user_alice
or@user_bob
@user<numberBetween(1,20)>
=>@userX
,@user<current()>
There is obvious limits to this annotation: we cannot use the optional pattern with it, e.g. '@user50%? foo'
but they are such edge cases and really complicated, I am not willing to support them. In other words, ref
is limited to:
- characters allowed by references
*
character (for wildcards){}
, digits and.
(for lists)'<X>'
pattern
Escaped expression: '\@ref'
will return '@ref'
@ref->prop
where @ref
is evaluated (like described in the References section) (but not prop
).
prop
refers to a property of the object pointed by @ref
and is accessed via a property accessor (which will determined which getter to use if a getter is required or get it directly if it is a public property). For the sake of simplicity, it is assumed that properties are plain english and as such should only be composed of letters or underscores _
.
As prop
is not resolved, having @user1->name<current()>
will be equivalent to Alice<current()>
if @user1->name
results in 'Alice'
.
Flags are being parsed by the FlagParser. You can easily decorate alice built-in flag parser or add a chainable flag parser.
How the flag is then interpreted and influence the generation of the object
highly depends on the purpose of the flag. For example the ExtendFlag
and
TemplateFlag
are handled by TemplateFixtureBagResolver
which resolves
the dependencies to generate a clean collection of fixture (replaces the
templates/extends by their real values).
The instantiation is being handled by the InstantiatorInterface. By default, you have a set of instantiator to handle several use cases: regular constructor, factory or without any instantiator (via Reflection).
If you need to customize the way a fixture is instantiated, for example like done in HautelookAliceBundle, to be able to use a service registered to Symfony Dependency Injection Container to instantiate a given fixture, you can easily add a chained instantiator.
Another way to extend the instantiation is to simply decorate Alice built-in instantiator, for example:
<?php
namespace App;
use Nelmio\Alice\Definition\Object\SimpleObject;
use Nelmio\Alice\FixtureInterface;
use Nelmio\Alice\Generator\GenerationContext;
use Nelmio\Alice\Generator\InstantiatorInterface;
use Nelmio\Alice\Generator\ResolvedFixtureSet;
final class DummyInstantiator implements InstantiatorInterface
{
private $instantiator;
public function __construct(InstantiatorInterface $decoratedInstantiator)
{
$this->instantiator = $decoratedInstantiator;
}
/**
* @inheritdoc
*/
public function instantiate(
FixtureInterface $fixture,
ResolvedFixtureSet $fixtureSet,
GenerationContext $context
): ResolvedFixtureSet
{
if ('App\Dummy' === $fixture->getClassName()) {
$instance = new Dummy();
$objects = $fixtureSet->getObjects()->with(
new SimpleObject(
$fixture->getId(),
$instance
)
);
return $fixtureSet->withObjects($objects);
}
return $this->instantiator->instantiate($fixture, $fixtureSet, $context);
}
}
Guessing accessors is always a tricky job. Alice does it by relying on the Symfony Property Component which assumes you are using PSR-2. This does not however cover all the use cases which may lead you to extend that behaviour. To achieve that, you have 2 easy ways.
- Create a custom
PropertyAccessor
.
By far the simplest solution, this is actually what is done for setting the
values of an stdClass
object (see StdPropertyAccessor)
or for setting private properties using reflection (see ReflectionPropertyAccessor
)
You can decorate the property accessor to use your own by extending the NativeLoader
or by using the decoration feature of the Symfony DI component when using the provided integration:
services:
app.fixtures.reflection_property_accessor:
class: Nelmio\Alice\PropertyAccess\ReflectionPropertyAccessor
public: false
decorates: nelmio_alice.property_accessor
decoration_priority: -10
arguments: ['@app.fixtures.reflection_property_accessor.inner']
- Decorate the PropertyHydrator
Setting a value is done by a PropertyHydratorInterface. By default, Alice uses SymfonyPropertyAccessorHydrator. which is a simple adapter for Symfony Property Component. So you could easily decorate it to do what you want:
<?php
namespace App;
use Nelmio\Alice\Definition\Object\SimpleObject;
use Nelmio\Alice\Definition\Property;
use Nelmio\Alice\Generator\GenerationContext;
use Nelmio\Alice\Generator\Hydrator\PropertyHydratorInterface;
use Nelmio\Alice\ObjectInterface;
final class DummyPropertyHydrator implements PropertyHydratorInterface
{
private $hydrator;
public function __construct(PropertyHydratorInterface $decoratedPropertyHydrator)
{
$this->hydrator = $decoratedPropertyHydrator;
}
/**
* @inheritdoc
*/
public function hydrate(ObjectInterface $object, Property $property, GenerationContext $context): ObjectInterface
{
if ('key' === $property->getName()) {
$instance = $object->getInstance()->withKey($property->getValue());
return new SimpleObject($object->getId(), $instance);
}
return $this->hydrator->hydrate($object, $property, $context);
}
}