titleTemplate | theme | background | highlighter | lineNumbers | info | css |
---|---|---|---|---|---|---|
%s |
seriph |
shiki |
true |
false |
unocss |
Pour Sylius, Symfony et n'importe quel projet PHP en fait
- Qui suis-je ?
- Qu'est-ce que Foundry ?
- Exemples
- Exemple de la collection de livre n°1
- Exemple de la collection de livre n°2
- On y verra :
- Debug
- Fixtures
- Reusable Model Factory "States"
- Beaucoup de fonctions utiles
- Au sein de frameworks
Package officiellement recommandé par Symfony
Foundry permet de créer des fixtures :
- expressive
- auto-complétable
- chargée à la demande
- au sein de Symfony et/ou Doctrine
final class UserFactory extends ModelFactory
{
/** @todo inject services if required */
public function __construct()
{
parent::__construct();
}
protected function getDefaults(): array
{
return [
'email' => self::faker()->text(180),
'password' => self::faker()->text(),
'roles' => [],
];
}
protected function initialize(): self
{
return $this;
}
protected static function getClass(): string
{
return User::class;
}
}
final class UserFactory extends ModelFactory
{
/** @todo inject services if required */
public function __construct()
{
parent::__construct();
}
protected function getDefaults(): array
{
return [
'email' => self::faker()->text(180),
'password' => self::faker()->text(),
'roles' => [],
];
}
protected function initialize(): self
{
return $this;
}
protected static function getClass(): string
{
return User::class;
}
}
~~~
final class UserFactory extends ModelFactory
{
/** @todo inject services if required */
public function __construct()
{
parent::__construct();
}
protected function getDefaults(): array
{
return [
'email' => self::faker()->email(),
'password' => self::faker()->password(),
'roles' => [],
];
}
protected function initialize(): self
{
return $this;
}
protected static function getClass(): string
{
return User::class;
}
}
class ReviewTest extends ApiTestCase
{
public function testUserLeaveAReview(): void
{
static::createClient()->request('POST', sprintf("/books/%s/reviews", $book->getId()), [
'json' => [
'content' => 'Lorem ipsum dolor sit amet',
],
]);
$this->assertResponseIsSuccessful();
$this->assertJsonContains(['@id' => '/reviews/1']);
}
}
- ResetDatabase : (re)création de la base via les schémas en place
- Factories : Initialise Foundry au sein du Kernel
class ReviewTest extends AbstractTest
{
use ResetDatabase, Factories;
public function testUserLeaveAReview(): void
{
$book = BookFactory::createOne();
$user = UserFactory::createOne();
$token = $this->getToken(['email' => $user->getEmail(), 'password' => $user->getPassword()]);
static::createClientWithCredentials($token)->request('POST', sprintf("/books/%s/reviews", $book->getId()), [
'json' => [
'content' => 'Lorem ipsum dolor sit amet',
],
]);
$this->assertResponseIsSuccessful();
$this->assertJsonContains(['@id' => '/reviews/1']);
}
}
class ReviewTest extends AbstractTest
{
use ResetDatabase, Factories;
public function testUserLeaveAReview(): void
{
$book = BookFactory::createOne();
$user = UserFactory::createOne();
$token = $this->getToken(['email' => $user->getEmail(), 'password' => $user->getPassword()]);
static::createClientWithCredentials($token)->request('POST', sprintf("/books/%s/reviews", $book->getId()), [
'json' => [
'content' => 'Lorem ipsum dolor sit amet',
],
]);
$this->assertResponseIsSuccessful();
$this->assertJsonContains(['@id' => '/reviews/1']);
}
}
layout: center title: Les stories
final class DefaultBookStory extends Story
{
public function build(): void
{
BookFactory::createOne();
}
}
class ReviewTest extends AbstractTest
{
public function testUserLeaveAReviewStory(): void
{
DefaultBookStory::load();
$user = UserFactory::createOne();
$token = $this->getToken(['email' => $user->getEmail(), 'password' => $user->getPassword()]);
static::createClientWithCredentials($token)->request('POST', '/books/1/reviews', [
'json' => [
'content' => 'Lorem ipsum dolor sit amet',
],
]);
$this->assertResponseIsSuccessful();
$this->assertJsonContains(['@id' => '/reviews/1', 'book' => '/books/1']);
}
final class DefaultBookStory extends Story
{
public function build(): void
{
BookFactory::createOne();
}
}
class ReviewTest extends AbstractTest
{
public function testUserLeaveAReviewStory(): void
{
DefaultBookStory::load();
$user = UserFactory::createOne();
$token = $this->getToken(['email' => $user->getEmail(), 'password' => $user->getPassword()]);
static::createClientWithCredentials($token)->request('POST', '/books/1/reviews', [
'json' => [
'content' => 'Lorem ipsum dolor sit amet',
],
]);
$this->assertResponseIsSuccessful();
$this->assertJsonContains(['@id' => '/reviews/1', 'book' => '/books/1']);
}
class ReviewTest extends AbstractTest
{
use ResetDatabase, Factories;
public function testGetReviews(): void
{
$book = BookFactory::createOne();
static::createClient()->request('GET', '/book/1/reviews');
$this->assertResponseIsSuccessful();
$this->assertJsonContains(['@id' => '/books/1/reviews']);
}
}
class ReviewTest extends AbstractTest
{
use ResetDatabase, Factories;
public function testGetReviews(): void
{
$book = BookFactory::createOne();
while(!$jenAiMarre) {
$user = UserFactory::createOne();
$token = $this->getToken(['email' => $user->getEmail(), 'password' => $user->getPassword()]);
static::createClientWithCredentials($token)->request('POST', sprintf("/books/%s/reviews", $book->getId()), [
'json' => [
'content' => 'Lorem ipsum dolor sit amet',
],
]);
}
static::createClient()->request('GET', '/book/1/reviews');
$this->assertResponseIsSuccessful();
$this->assertJsonContains(['@id' => '/reviews/1']);
}
}
class ReviewTest extends AbstractTest
{
use ResetDatabase, Factories;
public function testGetReviews(): void
{
$book = BookFactory::createOne();
while(!$jenAiMarre) {
$user = UserFactory::createOne();
$token = $this->getToken(['email' => $user->getEmail(), 'password' => $user->getPassword()]);
static::createClientWithCredentials($token)->request('POST', sprintf("/books/%s/reviews", $book->getId()), [
'json' => [
'content' => 'Lorem ipsum dolor sit amet',
],
]);
}
static::createClient()->request('GET', sprintf('/book/%s/reviews', $book->getId()));
$this->assertResponseIsSuccessful();
$this->assertJsonContains(['@id' => '/reviews/1']);
}
}
- Performance
- Je teste des choses qui ne sont pas liées à mon test
class ReviewTest extends AbstractTest
{
use ResetDatabase, Factories;
public function testGetReviews(): void
{
ReviewFactory::createMany(50, function() {
return [
'book' => BookFactory::findOrCreate(['isbn' => 143987391287]),
'reviewer' => UserFactory::randomOrCreate(),
];
});
static::createClient()->request('GET', '/book/1/reviews');
$this->assertResponseIsSuccessful();
$this->assertJsonContains(['@id' => '/books/1/reviews']);
}
}
class ReviewTest extends AbstractTest
{
use ResetDatabase, Factories;
public function testGetReviews(): void
{
ReviewFactory::createMany(50, function() {
return [
'book' => BookFactory::findOrCreate(['isbn' => 143987391287]),
'reviewer' => UserFactory::randomOrCreate(),
];
});
static::createClient()->request('GET', '/book/1/reviews');
$this->assertResponseIsSuccessful();
$this->assertJsonContains(['@id' => '/books/1/reviews']);
}
}
layout: center title: Beaucoup de fonctionnalités
new(), findOrCreate, randomOrCreate et les autres fonctions ?
final class BookFactory extends ModelFactory
{
protected function getDefaults(): array
{
return [
'isbn' => self::faker()->isbn10(),
'name' => self::faker()->name(),
'author' => AuthorFactory::randomOrCreate(),
];
}
public function withIsbn($isbn): self
{
return $this->addState(['isbn' => $isbn]);
}
final class BookFactory extends ModelFactory
{
protected function getDefaults(): array
{
return [
'isbn' => self::faker()->isbn10(),
'name' => self::faker()->name(),
'author' => AuthorFactory::randomOrCreate(),
];
}
public function withIsbn($isbn): self
{
return $this->addState(['isbn' => $isbn]);
}
// src/Factory/AuthorFactory
protected function getDefaults(): array
{
return [
'account' => UserFactory::new(),
];
}
public function testAuthorGetReviewFind(): void
{
BookFactory::new()->withIsbn(193847625)->create();
ReviewFactory::createMany(50, [
'book' => BookFactory::find(['isbn' => 193847625]),
'reviewer' => UserFactory::randomOrCreate(),
]);
static::createClient()->request('GET',
sprintf('/books/%s/reviews', BookFactory::find(['isbn' => 193847625])->getId())
);
$this->assertResponseIsSuccessful();
$this->assertJsonContains(['@id' =>
sprintf('/books/%s/reviews', BookFactory::find(['isbn' => 193847625])->getId())]
);
}
public function testAuthorGetReviewFind(): void
{
$book = BookFactory::new()->withIsbn(193847625)->create();
ReviewFactory::createMany(50, [
'book' => $book,
'reviewer' => UserFactory::randomOrCreate(),
]);
static::createClient()->request('GET',sprintf('/books/%s/reviews', $book->getId()));
$this->assertResponseIsSuccessful();
$this->assertJsonContains(['@id' => sprintf('/books/%s/reviews', $book->getId())]);
}
final class DefaultReviewsStory extends Story
{
public function build(): void
{
AuthorFactory::new()->withBook()->createMany(10);
ReviewFactory::createMany(50, [
'book' => BookFactory::random(),
'reviewer' => UserFactory::randomOrCreate(),
]);
}
}
- ça fait pas exactement ce que ça semble dire faire
- on fait beaucoup de code pour pas grand chose
- ok mais faut des preuves
class ReviewTest extends AbstractTest
{
public function testGetReviewsStory(): void
{
DefaultReviewsStory::load();
die();
static::createClient()->request('GET', sprintf('/books/%s/reviews', BookFactory::random()->getId()));
$this->assertResponseIsSuccessful();
$this->assertJsonContains(['@id' => sprintf('/books/%s/reviews', BookFactory::random()->getId())]);
}
}
final class DefaultReviewsStory extends Story
{
public function build(): void
{
AuthorFactory::new()->withBook()->createMany(10);
ReviewFactory::createMany(50, [
'book' => BookFactory::random(),
'reviewer' => UserFactory::randomOrCreate(),
]);
}
}
final class ReviewFactory extends ModelFactory
{
protected function getDefaults(): array
{
return [
'reviewer' => UserFactory::new(),
'content' => self::faker()->text(),
'book' => UserFactory::new()
];
}
final class DefaultReviewsStory extends Story
{
public function build(): void
{
ReviewFactory::createMany(50);
}
}
class ReviewTest extends AbstractTest
{
public function setUp(): void
{
DefaultReviewsStory::load();
}
public function testGetReviewsStory(): void
{
$book = BookFactory::random();
static::createClient()->request('GET',
sprintf('/books/%s/reviews', $book->getId())
);
$this->assertResponseIsSuccessful();
$this->assertJsonContains(['@id' => sprintf('/books/%s/reviews', $book->getId())]);
}
}
--- Expected
+++ Actual
@@ @@
array (
'@context' => '/contexts/Review',
- '@id' => '/books/49/reviews',
+ '@id' => '/books/42/reviews',
'@type' => 'hydra:Collection',
'hydra:totalItems' => 1,
'hydra:member' =>
final class DefaultBookStory extends Story
{
public function build(): void
{
$this->addState(
'thefasttrack',
BookFactory::createOne(['name' => 'The Fast Track'])
);
}
}
final class DefaultReviewsStory extends Story
{
public function build(): void
{
ReviewFactory::createMany(10, [
'book' => DefaultBookStory::get('thefasttrack'),
//'book' => DefaultBookStory::$thefasttrack,
]);
}
}
final class DefaultBookStory extends Story
{
public const THE_FAST_TRACK = 'thefasttrack'
public function build(): void
{
$this->addState(
self::THE_FAST_TRACK,
BookFactory::createOne(['name' => 'The Fast Track'])
);
}
}
class ReviewTest extends AbstractTest
{
use ResetDatabase;
use Factories;
public function setUp(): void
{
DefaultReviewsStory::load();
}
public function testGetReviewsStory(): void
{
static::createClient()->request('GET',
sprintf(
'/books/%s/reviews',
DefaultBookStory::get(DefaultBookStory::THE_FAST_TRACK)->getId()
)
);
$this->assertResponseIsSuccessful();
$this->assertJsonContains([
'@id' => sprintf(
'/books/%s/reviews',
DefaultBookStory::get(DefaultBookStory::THE_FAST_TRACK)->getId()
),
'hydra:totalItems' => 10
]);
}
}
<?php
namespace App\DataFixtures;
use App\Factory\AuthorFactory;
use App\Factory\UserFactory;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Persistence\ObjectManager;
class AppFixtures extends Fixture
{
public function load(ObjectManager $manager): void
{
UserFactory::createMany(10);
AuthorFactory::createMany(2);
}
}
// src/Factory/BookFactory.php
protected function getDefaults(): array
{
return [
'author' => self::faker()->name(),
'description' => self::faker()->text(),
'isbn' => self::faker()->isbn13(),
'publication_date' => \DateTimeImmutable::createFromMutable(self::faker()->dateTime()),
'title' => self::faker()->sentence(4),
];
}
// src/Factory/ReviewFactory.php
use function Zenstruck\Foundry\lazy;
protected function getDefaults(): array
{
return [
'author' => self::faker()->name(),
'body' => self::faker()->text(),
'book' => lazy(fn() => BookFactory::randomOrCreate()),
'publicationDate' => \DateTimeImmutable::createFromMutable(self::faker()->dateTime()),
'rating' => self::faker()->numberBetween(0, 5),
];
}
- Default est appelé à chaque fois que la factory est instanciée
- Permet de limiter les effets de bord
use Zenstruck\Foundry\Attributes\LazyValue;
use function Zenstruck\Foundry\lazy;
protected function getDefaults(): array
{
return [
'category' => new LazyValue(fn() => CategoryFactory::random()),
'file' => lazy(fn() => create_temp_file()), // or use the lazy() helper function
];
}
// src/Story/DefaultBooksStory.php
final class DefaultBooksStory extends Story
{
public function build(): void
{
BookFactory::createMany(100);
}
}
}
// src/Story/DefaultReviewsStory.php
final class DefaultReviewsStory extends Story
{
public function build(): void
{
ReviewFactory::createMany(200);
}
}
//src/DataFixtures/AppFixtures.php
class AppFixtures extends Fixture
{
public function load(ObjectManager $manager): void
{
DefaultBooksStory::load();
DefaultReviewsStory::load();
}
}
sylius_fixtures:
suites:
default:
fixtures:
locale:
options:
locales:
- 'en_US'
- 'de_DE'
- 'fr_FR'
- 'pl_PL'
- 'es_ES'
- 'es_MX'
- 'pt_PT'
- 'zh_CN'
currency:
options:
currencies:
- 'EUR'
- 'USD'
- 'PLN'
- 'CAD'
- 'CNY'
- 'NZD'
- 'GBP'
- 'AUD'
- 'MXN'
geographical:
options:
countries:
- 'US'
- 'FR'
- 'DE'
- 'AU'
- 'CA'
- 'MX'
- 'NZ'
- 'PT'
- 'ES'
- 'CN'
- 'GB'
- 'PL'
// src/Foundry/Story/DefaultCurrenciesStory.php
declare(strict_types=1);
namespace App\Foundry\Story;
use Akawakaweb\ShopFixturesPlugin\Foundry\Factory\CurrencyFactory;
use Akawakaweb\ShopFixturesPlugin\Foundry\Story\DefaultCurrenciesStoryInterface;
use Zenstruck\Foundry\Factory;
use Zenstruck\Foundry\Story;
final class DefaultCurrenciesStory extends Story implements DefaultCurrenciesStoryInterface
{
public function build(): void
{
CurrencyFactory::new()->withCode('EUR')->create();
}
}
# config/services.yaml
when@dev: &fixtures_dev
services:
sylius.fixtures_plugin.story.default_currencies: '@App\Foundry\Story\DefaultCurrenciesStory'
// src/Entity/Customer/Customer.php
#[ORM\Entity]
#[ORM\Table(name: 'sylius_customer')]
class Customer extends BaseCustomer
{
#[ORM\Column]
private ?string $secondPhoneNumber = null;
public function getSecondPhoneNumber(): ?string
{
return $this->secondPhoneNumber;
}
public function setSecondPhoneNumber(?string $secondPhoneNumber): void
{
$this->secondPhoneNumber = $secondPhoneNumber;
}
}
// src/Foundry/DefaultValues/CustomerDefaultValues.php
final class CustomerDefaultValues implements DefaultValuesInterface
{
public function __construct(
private DefaultValuesInterface $decorated,
) {
}
public function __invoke(Generator $faker): array
{
return array_merge(($this->decorated)($faker), [
'secondPhoneNumber' => $faker->phoneNumber(),
]);
}
}
when@dev: &fixtures_dev
services:
App\Foundry\DefaultValues\CustomerDefaultValues:
decorates: 'sylius.fixtures_plugin.default_values.customer'
arguments:
$decorated: '@.inner'
Disponible depuis aujourd'hui en 0.1 !