Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Resource revolution

Resource revolution

Slides of my talk during the Sylius Con 2023 event.

Loïc Frémont

November 07, 2023
Tweet

More Decks by Loïc Frémont

Other Decks in Programming

Transcript

  1. Resource Revolution
    on the new Sylius Resource & Grid

    View full-size slide

  2. Loïc Frémont
    Technical Expert @Akawaka
    Core Team Member @Sylius
    Monofony creator

    View full-size slide

  3. 1. Resource Revolution
    2. Akawaka
    3. What is Monofony and Why
    use it?
    4. New Sylius Resource System
    5. Sylius Resource without driver
    6. What’s next?

    View full-size slide

  4. Akawaka
    Experts for your web
    projects & Sylius partner
    We help you design and improve your projects:

    View full-size slide

  5. Akawaka
    Experts for your web
    projects & Sylius partner
    We help you design and improve your projects:
    We use clean architecture techniques via
    DDD methodologies for projects that stand
    the test of time,

    View full-size slide

  6. Akawaka
    Experts for your web
    projects & Sylius partner
    We help you design and improve your projects:
    We use clean architecture techniques via
    DDD methodologies for projects that stand
    the test of time,
    A true quality approach through testing,

    View full-size slide

  7. Akawaka
    Experts for your web
    projects & Sylius partner
    We help you design and improve your projects:
    We use clean architecture techniques via
    DDD methodologies for projects that stand
    the test of time,
    A true quality approach through testing,
    Efficient industrialization adapted to your
    projects,

    View full-size slide

  8. Akawaka
    Experts for your web
    projects & Sylius partner
    We help you design and improve your projects:
    We use clean architecture techniques via
    DDD methodologies for projects that stand
    the test of time,
    A true quality approach through testing,
    Efficient industrialization adapted to your
    projects,
    On a contract or fixed-price basis, to create
    and/or integrate teams and work in
    complete collaboration.

    View full-size slide

  9. What is Monofony and
    Why use it?

    View full-size slide

  10. What is Monofony and
    Why use it?
    Bootstrapping a modern application on top
    of Symfony

    View full-size slide

  11. What is Monofony and
    Why use it?
    Bootstrapping a modern application on top
    of Symfony
    Leveraging Sylius bundles and components

    View full-size slide

  12. What is Monofony and
    Why use it?
    Bootstrapping a modern application on top
    of Symfony
    Leveraging Sylius bundles and components
    Helping you to focus more on what truly
    matters to your use-case

    View full-size slide

  13. Installation
    $ composer create-project monofony/skeleton project_name

    View full-size slide

  14. Monofony API Pack
    voir l’installation détaillée dans la doc.
    $ composer require monofony/api-pack "^0.10"

    View full-size slide

  15. Resource Bundle

    View full-size slide

  16. Past use cases

    View full-size slide

  17. Past use cases
    Making CRUD with Doctrine entities.

    View full-size slide

  18. Past use cases
    Making CRUD with Doctrine entities.
    Avoiding writing controllers that all do the same things.

    View full-size slide

  19. Today use cases

    View full-size slide

  20. Today use cases
    Better DX

    View full-size slide

  21. Today use cases
    Better DX
    Customize persistence layer : ERP, Elastic search…

    View full-size slide

  22. Today use cases
    Better DX
    Customize persistence layer : ERP, Elastic search…
    Allow to use in DDD projects

    View full-size slide

  23. New Sylius Resource System

    View full-size slide

  24. New Sylius Resource System
    API Platform for the inspiration

    View full-size slide

  25. New Sylius Resource System
    API Platform for the inspiration
    Akawaka & Commerce Weavers for sponsoring that development

    View full-size slide

  26. New Sylius Resource System
    API Platform for the inspiration
    Akawaka & Commerce Weavers for sponsoring that development
    Łukasz Chruściel at Commerce Weavers for the code reviews

    View full-size slide

  27. Configure your main templates dir
    # config/package/sylius_resource.yaml
    sylius_resource:
    settings:
    default_templates_dir: '@SyliusAdminUi/crud'

    View full-size slide

  28. Use the Resource attribute
    PHP attribute #[AsResource] configures your entity as a Sylius resource.
    ` `
    namespace App\Entity;
    use App\Repository\BookRepository;
    use Doctrine\ORM\Mapping as ORM;
    use Sylius\Resource\Metadata\AsResource;
    use Sylius\Resource\Model\ResourceInterface;
    #[ORM\Entity(repositoryClass: BookRepository::class)]
    #[AsResource]
    class Book implements ResourceInterface
    {
    }

    View full-size slide

  29. Use the Resource attribute
    PHP attribute #[AsResource] configures your entity as a Sylius resource.
    ` `
    #[AsResource]
    namespace App\Entity;
    use App\Repository\BookRepository;
    use Doctrine\ORM\Mapping as ORM;
    use Sylius\Resource\Metadata\AsResource;
    use Sylius\Resource\Model\ResourceInterface;
    #[ORM\Entity(repositoryClass: BookRepository::class)]
    class Book implements ResourceInterface
    {
    }

    View full-size slide

  30. Use the Resource attribute
    PHP attribute #[AsResource] configures your entity as a Sylius resource.
    ` `
    use Sylius\Resource\Metadata\AsResource;
    #[AsResource]
    namespace App\Entity;
    use App\Repository\BookRepository;
    use Doctrine\ORM\Mapping as ORM;
    use Sylius\Resource\Model\ResourceInterface;
    #[ORM\Entity(repositoryClass: BookRepository::class)]
    class Book implements ResourceInterface
    {
    }

    View full-size slide

  31. Debug command
    Output
    bin/console sylius:debug:resource 'app.book'
    New Resource Metadata
    ---------------------
    ------------------------ -------------------
    Option Value
    ------------------------ -------------------
    alias "app.book"
    section null
    formType null
    templatesDir null
    routePrefix null
    name "book"
    pluralName null
    applicationName "app"
    identifier null
    normalizationContext null
    denormalizationContext null
    validationContext null
    class "App\Entity\Book"
    ------------------------ -------------------

    View full-size slide

  32. Browsing books

    View full-size slide

  33. Browsing books
    We’ll use Index operation which allows to browse all items of your resource.
    ` `

    View full-size slide

  34. Browsing books
    We’ll use Index operation which allows to browse all items of your resource.
    ` `
    namespace App\Entity;
    use Sylius\Resource\Metadata\Index;
    use Sylius\Resource\Metadata\AsResource;
    use Sylius\Resource\Model\ResourceInterface;
    #[AsResource(
    operations: [
    new Index(),
    ],
    )]
    // OR
    #[AsResource]
    #[Index]
    class Book implements ResourceInterface
    {
    }

    View full-size slide

  35. Browsing books
    We’ll use Index operation which allows to browse all items of your resource.
    ` `
    operations: [
    new Index(),
    ],
    namespace App\Entity;
    use Sylius\Resource\Metadata\Index;
    use Sylius\Resource\Metadata\AsResource;
    use Sylius\Resource\Model\ResourceInterface;
    #[AsResource(
    )]
    // OR
    #[AsResource]
    #[Index]
    class Book implements ResourceInterface
    {
    }

    View full-size slide

  36. Browsing books
    We’ll use Index operation which allows to browse all items of your resource.
    ` `
    use Sylius\Resource\Metadata\Index;
    operations: [
    new Index(),
    ],
    namespace App\Entity;
    use Sylius\Resource\Metadata\AsResource;
    use Sylius\Resource\Model\ResourceInterface;
    #[AsResource(
    )]
    // OR
    #[AsResource]
    #[Index]
    class Book implements ResourceInterface
    {
    }

    View full-size slide

  37. Browsing books
    We’ll use Index operation which allows to browse all items of your resource.
    ` `
    // OR
    #[AsResource]
    #[Index]
    namespace App\Entity;
    use Sylius\Resource\Metadata\Index;
    use Sylius\Resource\Metadata\AsResource;
    use Sylius\Resource\Model\ResourceInterface;
    #[AsResource(
    operations: [
    new Index(),
    ],
    )]
    class Book implements ResourceInterface
    {
    }

    View full-size slide

  38. Route
    It will configure this route for your index operation.
    ` `

    View full-size slide

  39. Route
    It will configure this route for your index operation.
    Name Method Path
    app_book_index GET /books
    ` `

    View full-size slide

  40. Tips
    bin/console sylius:debug:resource app_book_index

    View full-size slide

  41. Tips
    bin/console sylius:debug:resource app_book_index
    Operation Metadata
    ------------------
    ------------------------ --------------------------------------------------------------------
    Option Value
    ------------------------ --------------------------------------------------------------------
    twigContextFactory "sylius.twig.context.factory.default"
    methods [
    "GET"
    ]
    path null
    routeName "app_book_index"
    routePrefix null
    redirectToRoute null
    redirectArguments null
    provider "Sylius\Resource\Symfony\Request\State\Provider"
    processor "Sylius\Resource\Doctrine\Common\State\PersistProcessor"
    responder "Sylius\Resource\Symfony\Request\State\Responder"
    repository "app.repository.book"
    template "@SyliusAdminUi/crud/index.html.twig"
    #[...]

    View full-size slide

  42. Create a grid
    You can use the Grid maker.
    $ bin/console make:grid

    View full-size slide

  43. final class BookGrid extends AbstractGrid implements ResourceAwareGridInterface
    {
    public static function getName(): string
    {
    return 'app_book';
    }
    public function buildGrid(GridBuilderInterface $gridBuilder): void
    {
    $gridBuilder
    ->orderBy('name', 'asc')
    ->addField(
    StringField::create('name')
    ->setLabel('sylius.ui.name')
    ->setSortable(true)
    )
    ->addField(
    StringField::create('author')
    ->setLabel('sylius.ui.author')
    ->setSortable(true)
    )
    ;
    }

    View full-size slide

  44. final class BookGrid extends AbstractGrid implements ResourceAwareGridInterface
    {
    public static function getName(): string
    {
    return 'app_book';
    }
    public function buildGrid(GridBuilderInterface $gridBuilder): void
    {
    $gridBuilder
    ->orderBy('name', 'asc')
    ->addField(
    StringField::create('name')
    ->setLabel('sylius.ui.name')
    ->setSortable(true)
    )
    ->addField(
    StringField::create('author')
    ->setLabel('sylius.ui.author')
    ->setSortable(true)
    )
    ;
    }

    View full-size slide

  45. public static function getName(): string
    {
    return 'app_book';
    }
    final class BookGrid extends AbstractGrid implements ResourceAwareGridInterface
    {
    public function buildGrid(GridBuilderInterface $gridBuilder): void
    {
    $gridBuilder
    ->orderBy('name', 'asc')
    ->addField(
    StringField::create('name')
    ->setLabel('sylius.ui.name')
    ->setSortable(true)
    )
    ->addField(
    StringField::create('author')
    ->setLabel('sylius.ui.author')
    ->setSortable(true)
    )
    ;
    }

    View full-size slide

  46. ->orderBy('name', 'asc')
    final class BookGrid extends AbstractGrid implements ResourceAwareGridInterface
    {
    public static function getName(): string
    {
    return 'app_book';
    }
    public function buildGrid(GridBuilderInterface $gridBuilder): void
    {
    $gridBuilder
    ->addField(
    StringField::create('name')
    ->setLabel('sylius.ui.name')
    ->setSortable(true)
    )
    ->addField(
    StringField::create('author')
    ->setLabel('sylius.ui.author')
    ->setSortable(true)
    )
    ;
    }

    View full-size slide

  47. ->addField(
    StringField::create('name')
    ->setLabel('sylius.ui.name')
    ->setSortable(true)
    )
    final class BookGrid extends AbstractGrid implements ResourceAwareGridInterface
    {
    public static function getName(): string
    {
    return 'app_book';
    }
    public function buildGrid(GridBuilderInterface $gridBuilder): void
    {
    $gridBuilder
    ->orderBy('name', 'asc')
    ->addField(
    StringField::create('author')
    ->setLabel('sylius.ui.author')
    ->setSortable(true)
    )
    ;
    }

    View full-size slide

  48. ->addField(
    StringField::create('author')
    ->setLabel('sylius.ui.author')
    ->setSortable(true)
    )
    final class BookGrid extends AbstractGrid implements ResourceAwareGridInterface
    {
    public static function getName(): string
    {
    return 'app_book';
    }
    public function buildGrid(GridBuilderInterface $gridBuilder): void
    {
    $gridBuilder
    ->orderBy('name', 'asc')
    ->addField(
    StringField::create('name')
    ->setLabel('sylius.ui.name')
    ->setSortable(true)
    )
    ;
    }

    View full-size slide

  49. Use this grid for your index operation

    View full-size slide

  50. Use this grid for your index operation
    To use a grid for you operation, you need to install the Sylius grid package

    View full-size slide

  51. Use this grid for your index operation
    To use a grid for you operation, you need to install the Sylius grid package
    namespace App\Entity;
    use App\Grid\BookGrid;
    use Sylius\Resource\Metadata\Index;
    use Sylius\Resource\Metadata\AsResource;
    use Sylius\Resource\Model\ResourceInterface;
    #[AsResource]
    // You can use either the FQCN of your grid
    #[Index(grid: BookGrid::class)]
    // Or you can use the grid name
    #[Index(grid: 'app_book')]
    class Book implements ResourceInterface
    {
    }

    View full-size slide

  52. Use this grid for your index operation
    To use a grid for you operation, you need to install the Sylius grid package
    // You can use either the FQCN of your grid
    #[Index(grid: BookGrid::class)]
    namespace App\Entity;
    use App\Grid\BookGrid;
    use Sylius\Resource\Metadata\Index;
    use Sylius\Resource\Metadata\AsResource;
    use Sylius\Resource\Model\ResourceInterface;
    #[AsResource]
    // Or you can use the grid name
    #[Index(grid: 'app_book')]
    class Book implements ResourceInterface
    {
    }

    View full-size slide

  53. Use this grid for your index operation
    To use a grid for you operation, you need to install the Sylius grid package
    use App\Grid\BookGrid;
    // You can use either the FQCN of your grid
    #[Index(grid: BookGrid::class)]
    namespace App\Entity;
    use Sylius\Resource\Metadata\Index;
    use Sylius\Resource\Metadata\AsResource;
    use Sylius\Resource\Model\ResourceInterface;
    #[AsResource]
    // Or you can use the grid name
    #[Index(grid: 'app_book')]
    class Book implements ResourceInterface
    {
    }

    View full-size slide

  54. Use this grid for your index operation
    To use a grid for you operation, you need to install the Sylius grid package
    // Or you can use the grid name
    #[Index(grid: 'app_book')]
    namespace App\Entity;
    use App\Grid\BookGrid;
    use Sylius\Resource\Metadata\Index;
    use Sylius\Resource\Metadata\AsResource;
    use Sylius\Resource\Model\ResourceInterface;
    #[AsResource]
    // You can use either the FQCN of your grid
    #[Index(grid: BookGrid::class)]
    class Book implements ResourceInterface
    {
    }

    View full-size slide

  55. Use this grid for your index operation
    To use a grid for you operation, you need to install the Sylius grid package
    // Or you can use the grid name
    #[Index(grid: 'app_book')]
    namespace App\Entity;
    use App\Grid\BookGrid;
    use Sylius\Resource\Metadata\Index;
    use Sylius\Resource\Metadata\AsResource;
    use Sylius\Resource\Model\ResourceInterface;
    #[AsResource]
    // You can use either the FQCN of your grid
    #[Index(grid: BookGrid::class)]
    class Book implements ResourceInterface
    {
    }

    View full-size slide

  56. bin/console sylius:debug:resource app_book_index
    Operation Metadata
    ------------------
    ------------------------ --------------------------------------------------------------------
    Option Value
    ------------------------ --------------------------------------------------------------------
    #[...]
    provider "Sylius\Resource\Grid\State\RequestGridProvider"
    #[...]

    View full-size slide

  57. Adding/Editing books

    View full-size slide

  58. Adding/Editing books
    We’ll use Create and Update operations which allows to add a new item of your resource.
    ` ` ` `

    View full-size slide

  59. Adding/Editing books
    We’ll use Create and Update operations which allows to add a new item of your resource.
    ` ` ` `
    namespace App\Entity;
    use App\Form\BookType;
    use Sylius\Resource\Metadata\Create;
    use Sylius\Resource\Metadata\Index;
    use Sylius\Resource\Metadata\Update;
    use Sylius\Resource\Metadata\AsResource;
    use Sylius\Resource\Model\ResourceInterface;
    #[AsResource(
    formType: BookType::class,
    operations: [
    new Index(grid: BookGrid::class),
    new Create(),
    new Update(),
    ],
    )]
    class Book implements ResourceInterface
    {
    }

    View full-size slide

  60. Adding/Editing books
    We’ll use Create and Update operations which allows to add a new item of your resource.
    ` ` ` `
    formType: BookType::class,
    namespace App\Entity;
    use App\Form\BookType;
    use Sylius\Resource\Metadata\Create;
    use Sylius\Resource\Metadata\Index;
    use Sylius\Resource\Metadata\Update;
    use Sylius\Resource\Metadata\AsResource;
    use Sylius\Resource\Model\ResourceInterface;
    #[AsResource(
    operations: [
    new Index(grid: BookGrid::class),
    new Create(),
    new Update(),
    ],
    )]
    class Book implements ResourceInterface
    {
    }

    View full-size slide

  61. Adding/Editing books
    We’ll use Create and Update operations which allows to add a new item of your resource.
    ` ` ` `
    use App\Form\BookType;
    formType: BookType::class,
    namespace App\Entity;
    use Sylius\Resource\Metadata\Create;
    use Sylius\Resource\Metadata\Index;
    use Sylius\Resource\Metadata\Update;
    use Sylius\Resource\Metadata\AsResource;
    use Sylius\Resource\Model\ResourceInterface;
    #[AsResource(
    operations: [
    new Index(grid: BookGrid::class),
    new Create(),
    new Update(),
    ],
    )]
    class Book implements ResourceInterface
    {
    }

    View full-size slide

  62. Adding/Editing books
    We’ll use Create and Update operations which allows to add a new item of your resource.
    ` ` ` `
    new Create(),
    new Update(),
    namespace App\Entity;
    use App\Form\BookType;
    use Sylius\Resource\Metadata\Create;
    use Sylius\Resource\Metadata\Index;
    use Sylius\Resource\Metadata\Update;
    use Sylius\Resource\Metadata\AsResource;
    use Sylius\Resource\Model\ResourceInterface;
    #[AsResource(
    formType: BookType::class,
    operations: [
    new Index(grid: BookGrid::class),
    ],
    )]
    class Book implements ResourceInterface
    {
    }

    View full-size slide

  63. Adding/Editing books
    We’ll use Create and Update operations which allows to add a new item of your resource.
    ` ` ` `
    use Sylius\Resource\Metadata\Create;
    use Sylius\Resource\Metadata\Update;
    new Create(),
    new Update(),
    namespace App\Entity;
    use App\Form\BookType;
    use Sylius\Resource\Metadata\Index;
    use Sylius\Resource\Metadata\AsResource;
    use Sylius\Resource\Model\ResourceInterface;
    #[AsResource(
    formType: BookType::class,
    operations: [
    new Index(grid: BookGrid::class),
    ],
    )]
    class Book implements ResourceInterface
    {
    }

    View full-size slide

  64. Route
    It will configure this route for your create and your update operations.
    ` ` ` `

    View full-size slide

  65. Route
    It will configure this route for your create and your update operations.
    Name Method Path
    app_book_create GET, POST /books/new
    app_book_update GET, PUT, PATCH, POST /books/{id}/edit
    ` ` ` `

    View full-size slide

  66. final class BookGrid extends AbstractGrid implements ResourceAwareGridInterface
    {
    // [...]
    public function buildGrid(GridBuilderInterface $gridBuilder): void
    {
    $gridBuilder
    ->orderBy('name', 'asc')
    ->addField(
    // [...]
    )
    ->addField(
    // [...]
    )
    ->addActionGroup(
    MainActionGroup::create(
    CreateAction::create(),
    )
    )
    ->addActionGroup(
    ItemActionGroup::create(
    UpdateAction::create(),
    )
    )
    ;
    }

    View full-size slide

  67. ->addActionGroup(
    MainActionGroup::create(
    CreateAction::create(),
    )
    )
    final class BookGrid extends AbstractGrid implements ResourceAwareGridInterface
    {
    // [...]
    public function buildGrid(GridBuilderInterface $gridBuilder): void
    {
    $gridBuilder
    ->orderBy('name', 'asc')
    ->addField(
    // [...]
    )
    ->addField(
    // [...]
    )
    ->addActionGroup(
    ItemActionGroup::create(
    UpdateAction::create(),
    )
    )
    ;
    }

    View full-size slide

  68. MainActionGroup::create(
    final class BookGrid extends AbstractGrid implements ResourceAwareGridInterface
    {
    // [...]
    public function buildGrid(GridBuilderInterface $gridBuilder): void
    {
    $gridBuilder
    ->orderBy('name', 'asc')
    ->addField(
    // [...]
    )
    ->addField(
    // [...]
    )
    ->addActionGroup(
    CreateAction::create(),
    )
    )
    ->addActionGroup(
    ItemActionGroup::create(
    UpdateAction::create(),
    )
    )
    ;
    }

    View full-size slide

  69. CreateAction::create(),
    final class BookGrid extends AbstractGrid implements ResourceAwareGridInterface
    {
    // [...]
    public function buildGrid(GridBuilderInterface $gridBuilder): void
    {
    $gridBuilder
    ->orderBy('name', 'asc')
    ->addField(
    // [...]
    )
    ->addField(
    // [...]
    )
    ->addActionGroup(
    MainActionGroup::create(
    )
    )
    ->addActionGroup(
    ItemActionGroup::create(
    UpdateAction::create(),
    )
    )
    ;
    }

    View full-size slide

  70. ->addActionGroup(
    ItemActionGroup::create(
    UpdateAction::create(),
    )
    )
    final class BookGrid extends AbstractGrid implements ResourceAwareGridInterface
    {
    // [...]
    public function buildGrid(GridBuilderInterface $gridBuilder): void
    {
    $gridBuilder
    ->orderBy('name', 'asc')
    ->addField(
    // [...]
    )
    ->addField(
    // [...]
    )
    ->addActionGroup(
    MainActionGroup::create(
    CreateAction::create(),
    )
    )
    ;
    }

    View full-size slide

  71. ItemActionGroup::create(
    final class BookGrid extends AbstractGrid implements ResourceAwareGridInterface
    {
    // [...]
    public function buildGrid(GridBuilderInterface $gridBuilder): void
    {
    $gridBuilder
    ->orderBy('name', 'asc')
    ->addField(
    // [...]
    )
    ->addField(
    // [...]
    )
    ->addActionGroup(
    MainActionGroup::create(
    CreateAction::create(),
    )
    )
    ->addActionGroup(
    UpdateAction::create(),
    )
    )
    ;
    }

    View full-size slide

  72. UpdateAction::create(),
    final class BookGrid extends AbstractGrid implements ResourceAwareGridInterface
    {
    // [...]
    public function buildGrid(GridBuilderInterface $gridBuilder): void
    {
    $gridBuilder
    ->orderBy('name', 'asc')
    ->addField(
    // [...]
    )
    ->addField(
    // [...]
    )
    ->addActionGroup(
    MainActionGroup::create(
    CreateAction::create(),
    )
    )
    ->addActionGroup(
    ItemActionGroup::create(
    )
    )
    ;
    }

    View full-size slide

  73. Removing books

    View full-size slide

  74. Removing books
    We’ll use Delete and Bulk delete operations which allows to remove existing items of your resource.
    ` ` ` `

    View full-size slide

  75. Removing books
    We’ll use Delete and Bulk delete operations which allows to remove existing items of your resource.
    ` ` ` `
    namespace App\Entity;
    use App\Form\BookType;
    use Sylius\Resource\Metadata\BulkDelete;
    use Sylius\Resource\Metadata\Create;
    use Sylius\Resource\Metadata\Delete;
    // [...]
    #[AsResource(
    formType: BookType::class,
    operations: [
    new Index(grid: BookGrid::class),
    new Create(),
    new Update(),
    new Delete(),
    new BulkDelete(),
    ],
    )]
    class Book implements ResourceInterface
    {
    }

    View full-size slide

  76. Removing books
    We’ll use Delete and Bulk delete operations which allows to remove existing items of your resource.
    ` ` ` `
    new Delete(),
    new BulkDelete(),
    namespace App\Entity;
    use App\Form\BookType;
    use Sylius\Resource\Metadata\BulkDelete;
    use Sylius\Resource\Metadata\Create;
    use Sylius\Resource\Metadata\Delete;
    // [...]
    #[AsResource(
    formType: BookType::class,
    operations: [
    new Index(grid: BookGrid::class),
    new Create(),
    new Update(),
    ],
    )]
    class Book implements ResourceInterface
    {
    }

    View full-size slide

  77. Removing books
    We’ll use Delete and Bulk delete operations which allows to remove existing items of your resource.
    ` ` ` `
    use Sylius\Resource\Metadata\BulkDelete;
    use Sylius\Resource\Metadata\Delete;
    new Delete(),
    new BulkDelete(),
    namespace App\Entity;
    use App\Form\BookType;
    use Sylius\Resource\Metadata\Create;
    // [...]
    #[AsResource(
    formType: BookType::class,
    operations: [
    new Index(grid: BookGrid::class),
    new Create(),
    new Update(),
    ],
    )]
    class Book implements ResourceInterface
    {
    }

    View full-size slide

  78. Route
    It will configure this route for your delete and bulk_delete operations.
    ` ` ` `

    View full-size slide

  79. Route
    It will configure this route for your delete and bulk_delete operations.
    Name Method Path
    app_book_delete DELETE, POST /books/{id}/delete
    app_book_bulk_delete DELETE, POST /books/bulk_delete
    ` ` ` `

    View full-size slide

  80. final class BookGrid extends AbstractGrid implements ResourceAwareGridInterface
    {
    // [...]
    public function buildGrid(GridBuilderInterface $gridBuilder): void
    {
    $gridBuilder
    // [...]
    ->addActionGroup(
    // [...]
    )
    ->addActionGroup(
    ItemActionGroup::create(
    UpdateAction::create(),
    DeleteAction::create(),
    )
    )
    ->addActionGroup(
    BulkActionGroup::create(
    DeleteAction::create()
    )
    )
    ;
    }
    public function getResourceClass(): string

    View full-size slide

  81. ->addActionGroup(
    ItemActionGroup::create(
    UpdateAction::create(),
    DeleteAction::create(),
    )
    )
    final class BookGrid extends AbstractGrid implements ResourceAwareGridInterface
    {
    // [...]
    public function buildGrid(GridBuilderInterface $gridBuilder): void
    {
    $gridBuilder
    // [...]
    ->addActionGroup(
    // [...]
    )
    ->addActionGroup(
    BulkActionGroup::create(
    DeleteAction::create()
    )
    )
    ;
    }
    public function getResourceClass(): string

    View full-size slide

  82. UpdateAction::create(),
    final class BookGrid extends AbstractGrid implements ResourceAwareGridInterface
    {
    // [...]
    public function buildGrid(GridBuilderInterface $gridBuilder): void
    {
    $gridBuilder
    // [...]
    ->addActionGroup(
    // [...]
    )
    ->addActionGroup(
    ItemActionGroup::create(
    DeleteAction::create(),
    )
    )
    ->addActionGroup(
    BulkActionGroup::create(
    DeleteAction::create()
    )
    )
    ;
    }
    public function getResourceClass(): string

    View full-size slide

  83. DeleteAction::create(),
    final class BookGrid extends AbstractGrid implements ResourceAwareGridInterface
    {
    // [...]
    public function buildGrid(GridBuilderInterface $gridBuilder): void
    {
    $gridBuilder
    // [...]
    ->addActionGroup(
    // [...]
    )
    ->addActionGroup(
    ItemActionGroup::create(
    UpdateAction::create(),
    )
    )
    ->addActionGroup(
    BulkActionGroup::create(
    DeleteAction::create()
    )
    )
    ;
    }
    public function getResourceClass(): string

    View full-size slide

  84. ->addActionGroup(
    BulkActionGroup::create(
    DeleteAction::create()
    )
    )
    final class BookGrid extends AbstractGrid implements ResourceAwareGridInterface
    {
    // [...]
    public function buildGrid(GridBuilderInterface $gridBuilder): void
    {
    $gridBuilder
    // [...]
    ->addActionGroup(
    // [...]
    )
    ->addActionGroup(
    ItemActionGroup::create(
    UpdateAction::create(),
    DeleteAction::create(),
    )
    )
    ;
    }
    public function getResourceClass(): string

    View full-size slide

  85. BulkActionGroup::create(
    final class BookGrid extends AbstractGrid implements ResourceAwareGridInterface
    {
    // [...]
    public function buildGrid(GridBuilderInterface $gridBuilder): void
    {
    $gridBuilder
    // [...]
    ->addActionGroup(
    // [...]
    )
    ->addActionGroup(
    ItemActionGroup::create(
    UpdateAction::create(),
    DeleteAction::create(),
    )
    )
    ->addActionGroup(
    DeleteAction::create()
    )
    )
    ;
    }
    public function getResourceClass(): string

    View full-size slide

  86. DeleteAction::create()
    final class BookGrid extends AbstractGrid implements ResourceAwareGridInterface
    {
    // [...]
    public function buildGrid(GridBuilderInterface $gridBuilder): void
    {
    $gridBuilder
    // [...]
    ->addActionGroup(
    // [...]
    )
    ->addActionGroup(
    ItemActionGroup::create(
    UpdateAction::create(),
    DeleteAction::create(),
    )
    )
    ->addActionGroup(
    BulkActionGroup::create(
    )
    )
    ;
    }
    public function getResourceClass(): string

    View full-size slide

  87. Publishing books

    View full-size slide

  88. Publishing books
    We’ll use apply_state_machine_transition operation which allows to apply a transition using a state
    machine.
    ` `

    View full-size slide

  89. Publishing books
    We’ll use apply_state_machine_transition operation which allows to apply a transition using a state
    machine.
    ` `
    namespace App\Entity;
    use Sylius\Resource\Metadata\ApplyStateMachineTransition;
    use Sylius\Resource\Model\ResourceInterface;
    // [...]
    #[ApplyStateMachineTransition(stateMachineTransition: 'publish')]
    class Book implements ResourceInterface
    {
    }

    View full-size slide

  90. Publishing books
    We’ll use apply_state_machine_transition operation which allows to apply a transition using a state
    machine.
    ` `
    #[ApplyStateMachineTransition(stateMachineTransition: 'publish')]
    namespace App\Entity;
    use Sylius\Resource\Metadata\ApplyStateMachineTransition;
    use Sylius\Resource\Model\ResourceInterface;
    // [...]
    class Book implements ResourceInterface
    {
    }

    View full-size slide

  91. Publishing books
    We’ll use apply_state_machine_transition operation which allows to apply a transition using a state
    machine.
    ` `
    use Sylius\Resource\Metadata\ApplyStateMachineTransition;
    #[ApplyStateMachineTransition(stateMachineTransition: 'publish')]
    namespace App\Entity;
    use Sylius\Resource\Model\ResourceInterface;
    // [...]
    class Book implements ResourceInterface
    {
    }

    View full-size slide

  92. Route
    It will configure this route for your apply_state_machine_transition operation.
    ` `

    View full-size slide

  93. Route
    It will configure this route for your apply_state_machine_transition operation.
    Name Method Path
    app_book_publish PUT, PATCH, POST /books/{id}/publish
    ` `

    View full-size slide

  94. final class BookGrid extends AbstractGrid implements ResourceAwareGridInterface
    {
    // [...]
    public function buildGrid(GridBuilderInterface $gridBuilder): void
    {
    $gridBuilder
    // [...]
    ->addField(
    StringField::create('author')
    ->setLabel('sylius.ui.author')
    ->setSortable(true)
    )
    ->addField(
    TwigField::create('state', '@SyliusUi/Grid/Field/state.html.twig')
    ->setLabel('sylius.ui.state')
    ->setOption('vars', ['labels' => 'admin/book/label/state']),
    )
    ;
    }
    public function getResourceClass(): string
    {
    return Book::class;
    }
    }

    View full-size slide

  95. ->addField(
    TwigField::create('state', '@SyliusUi/Grid/Field/state.html.twig')
    ->setLabel('sylius.ui.state')
    ->setOption('vars', ['labels' => 'admin/book/label/state']),
    )
    ;
    final class BookGrid extends AbstractGrid implements ResourceAwareGridInterface
    {
    // [...]
    public function buildGrid(GridBuilderInterface $gridBuilder): void
    {
    $gridBuilder
    // [...]
    ->addField(
    StringField::create('author')
    ->setLabel('sylius.ui.author')
    ->setSortable(true)
    )
    }
    public function getResourceClass(): string
    {
    return Book::class;
    }
    }

    View full-size slide

  96. TwigField::create('state', '@SyliusUi/Grid/Field/state.html.twig')
    final class BookGrid extends AbstractGrid implements ResourceAwareGridInterface
    {
    // [...]
    public function buildGrid(GridBuilderInterface $gridBuilder): void
    {
    $gridBuilder
    // [...]
    ->addField(
    StringField::create('author')
    ->setLabel('sylius.ui.author')
    ->setSortable(true)
    )
    ->addField(
    ->setLabel('sylius.ui.state')
    ->setOption('vars', ['labels' => 'admin/book/label/state']),
    )
    ;
    }
    public function getResourceClass(): string
    {
    return Book::class;
    }
    }

    View full-size slide

  97. ->setLabel('sylius.ui.state')
    final class BookGrid extends AbstractGrid implements ResourceAwareGridInterface
    {
    // [...]
    public function buildGrid(GridBuilderInterface $gridBuilder): void
    {
    $gridBuilder
    // [...]
    ->addField(
    StringField::create('author')
    ->setLabel('sylius.ui.author')
    ->setSortable(true)
    )
    ->addField(
    TwigField::create('state', '@SyliusUi/Grid/Field/state.html.twig')
    ->setOption('vars', ['labels' => 'admin/book/label/state']),
    )
    ;
    }
    public function getResourceClass(): string
    {
    return Book::class;
    }
    }

    View full-size slide

  98. ->setOption('vars', ['labels' => 'admin/book/label/state']),
    final class BookGrid extends AbstractGrid implements ResourceAwareGridInterface
    {
    // [...]
    public function buildGrid(GridBuilderInterface $gridBuilder): void
    {
    $gridBuilder
    // [...]
    ->addField(
    StringField::create('author')
    ->setLabel('sylius.ui.author')
    ->setSortable(true)
    )
    ->addField(
    TwigField::create('state', '@SyliusUi/Grid/Field/state.html.twig')
    ->setLabel('sylius.ui.state')
    )
    ;
    }
    public function getResourceClass(): string
    {
    return Book::class;
    }
    }

    View full-size slide




  99. {{ value|trans }}

    View full-size slide

  100. final class BookGrid extends AbstractGrid implements ResourceAwareGridInterface
    {
    // [...]
    public function buildGrid(GridBuilderInterface $gridBuilder): void
    {
    $gridBuilder
    // [...]
    ->addActionGroup(
    ItemActionGroup::create(
    UpdateAction::create(),
    ApplyTransitionAction::create(
    name: 'publish',
    route: 'app_admin_book_publish',
    routeParameters: ['id' => 'resource.id'],
    options: [
    'class' => 'green',
    ],
    )
    ->setLabel('app.ui.publish')
    ->setIcon('checkmark'),
    DeleteAction::create(),
    )
    )
    ;
    }

    View full-size slide

  101. ApplyTransitionAction::create(
    name: 'publish',
    route: 'app_admin_book_publish',
    routeParameters: ['id' => 'resource.id'],
    options: [
    'class' => 'green',
    ],
    )
    ->setLabel('app.ui.publish')
    ->setIcon('checkmark'),
    final class BookGrid extends AbstractGrid implements ResourceAwareGridInterface
    {
    // [...]
    public function buildGrid(GridBuilderInterface $gridBuilder): void
    {
    $gridBuilder
    // [...]
    ->addActionGroup(
    ItemActionGroup::create(
    UpdateAction::create(),
    DeleteAction::create(),
    )
    )
    ;
    }

    View full-size slide

  102. ApplyTransitionAction::create(
    final class BookGrid extends AbstractGrid implements ResourceAwareGridInterface
    {
    // [...]
    public function buildGrid(GridBuilderInterface $gridBuilder): void
    {
    $gridBuilder
    // [...]
    ->addActionGroup(
    ItemActionGroup::create(
    UpdateAction::create(),
    name: 'publish',
    route: 'app_admin_book_publish',
    routeParameters: ['id' => 'resource.id'],
    options: [
    'class' => 'green',
    ],
    )
    ->setLabel('app.ui.publish')
    ->setIcon('checkmark'),
    DeleteAction::create(),
    )
    )
    ;
    }

    View full-size slide

  103. name: 'publish',
    final class BookGrid extends AbstractGrid implements ResourceAwareGridInterface
    {
    // [...]
    public function buildGrid(GridBuilderInterface $gridBuilder): void
    {
    $gridBuilder
    // [...]
    ->addActionGroup(
    ItemActionGroup::create(
    UpdateAction::create(),
    ApplyTransitionAction::create(
    route: 'app_admin_book_publish',
    routeParameters: ['id' => 'resource.id'],
    options: [
    'class' => 'green',
    ],
    )
    ->setLabel('app.ui.publish')
    ->setIcon('checkmark'),
    DeleteAction::create(),
    )
    )
    ;
    }

    View full-size slide

  104. route: 'app_admin_book_publish',
    final class BookGrid extends AbstractGrid implements ResourceAwareGridInterface
    {
    // [...]
    public function buildGrid(GridBuilderInterface $gridBuilder): void
    {
    $gridBuilder
    // [...]
    ->addActionGroup(
    ItemActionGroup::create(
    UpdateAction::create(),
    ApplyTransitionAction::create(
    name: 'publish',
    routeParameters: ['id' => 'resource.id'],
    options: [
    'class' => 'green',
    ],
    )
    ->setLabel('app.ui.publish')
    ->setIcon('checkmark'),
    DeleteAction::create(),
    )
    )
    ;
    }

    View full-size slide

  105. routeParameters: ['id' => 'resource.id'],
    final class BookGrid extends AbstractGrid implements ResourceAwareGridInterface
    {
    // [...]
    public function buildGrid(GridBuilderInterface $gridBuilder): void
    {
    $gridBuilder
    // [...]
    ->addActionGroup(
    ItemActionGroup::create(
    UpdateAction::create(),
    ApplyTransitionAction::create(
    name: 'publish',
    route: 'app_admin_book_publish',
    options: [
    'class' => 'green',
    ],
    )
    ->setLabel('app.ui.publish')
    ->setIcon('checkmark'),
    DeleteAction::create(),
    )
    )
    ;
    }

    View full-size slide

  106. options: [
    'class' => 'green',
    ],
    final class BookGrid extends AbstractGrid implements ResourceAwareGridInterface
    {
    // [...]
    public function buildGrid(GridBuilderInterface $gridBuilder): void
    {
    $gridBuilder
    // [...]
    ->addActionGroup(
    ItemActionGroup::create(
    UpdateAction::create(),
    ApplyTransitionAction::create(
    name: 'publish',
    route: 'app_admin_book_publish',
    routeParameters: ['id' => 'resource.id'],
    )
    ->setLabel('app.ui.publish')
    ->setIcon('checkmark'),
    DeleteAction::create(),
    )
    )
    ;
    }

    View full-size slide

  107. ->setLabel('app.ui.publish')
    final class BookGrid extends AbstractGrid implements ResourceAwareGridInterface
    {
    // [...]
    public function buildGrid(GridBuilderInterface $gridBuilder): void
    {
    $gridBuilder
    // [...]
    ->addActionGroup(
    ItemActionGroup::create(
    UpdateAction::create(),
    ApplyTransitionAction::create(
    name: 'publish',
    route: 'app_admin_book_publish',
    routeParameters: ['id' => 'resource.id'],
    options: [
    'class' => 'green',
    ],
    )
    ->setIcon('checkmark'),
    DeleteAction::create(),
    )
    )
    ;
    }

    View full-size slide

  108. ->setIcon('checkmark'),
    final class BookGrid extends AbstractGrid implements ResourceAwareGridInterface
    {
    // [...]
    public function buildGrid(GridBuilderInterface $gridBuilder): void
    {
    $gridBuilder
    // [...]
    ->addActionGroup(
    ItemActionGroup::create(
    UpdateAction::create(),
    ApplyTransitionAction::create(
    name: 'publish',
    route: 'app_admin_book_publish',
    routeParameters: ['id' => 'resource.id'],
    options: [
    'class' => 'green',
    ],
    )
    ->setLabel('app.ui.publish')
    DeleteAction::create(),
    )
    )
    ;
    }

    View full-size slide

  109. Publishing books with custom processor

    View full-size slide

  110. Publishing books with custom processor
    We configure an update operation.
    ` `

    View full-size slide

  111. Publishing books with custom processor
    We configure an update operation.
    ` `
    namespace App\Entity;
    use App\State\Processor\PublishBookProcessor;
    use Sylius\Resource\Metadata\Update;
    use Sylius\Resource\Model\ResourceInterface;
    // [...]
    #[Update(
    methods: ['PUT', 'PATCH', 'POST'],
    shortName: 'publish',
    processor: PublishBookProcessor::class,
    validate: false,
    )]
    class Book implements ResourceInterface
    {
    }

    View full-size slide

  112. Publishing books with custom processor
    We configure an update operation.
    ` `
    #[Update(
    methods: ['PUT', 'PATCH', 'POST'],
    shortName: 'publish',
    processor: PublishBookProcessor::class,
    validate: false,
    )]
    namespace App\Entity;
    use App\State\Processor\PublishBookProcessor;
    use Sylius\Resource\Metadata\Update;
    use Sylius\Resource\Model\ResourceInterface;
    // [...]
    class Book implements ResourceInterface
    {
    }

    View full-size slide

  113. Publishing books with custom processor
    We configure an update operation.
    ` `
    use Sylius\Resource\Metadata\Update;
    #[Update(
    methods: ['PUT', 'PATCH', 'POST'],
    shortName: 'publish',
    processor: PublishBookProcessor::class,
    validate: false,
    )]
    namespace App\Entity;
    use App\State\Processor\PublishBookProcessor;
    use Sylius\Resource\Model\ResourceInterface;
    // [...]
    class Book implements ResourceInterface
    {
    }

    View full-size slide

  114. Publishing books with custom processor
    We configure an update operation.
    ` `
    methods: ['PUT', 'PATCH', 'POST'],
    namespace App\Entity;
    use App\State\Processor\PublishBookProcessor;
    use Sylius\Resource\Metadata\Update;
    use Sylius\Resource\Model\ResourceInterface;
    // [...]
    #[Update(
    shortName: 'publish',
    processor: PublishBookProcessor::class,
    validate: false,
    )]
    class Book implements ResourceInterface
    {
    }

    View full-size slide

  115. Publishing books with custom processor
    We configure an update operation.
    ` `
    shortName: 'publish',
    namespace App\Entity;
    use App\State\Processor\PublishBookProcessor;
    use Sylius\Resource\Metadata\Update;
    use Sylius\Resource\Model\ResourceInterface;
    // [...]
    #[Update(
    methods: ['PUT', 'PATCH', 'POST'],
    processor: PublishBookProcessor::class,
    validate: false,
    )]
    class Book implements ResourceInterface
    {
    }

    View full-size slide

  116. Publishing books with custom processor
    We configure an update operation.
    ` `
    processor: PublishBookProcessor::class,
    namespace App\Entity;
    use App\State\Processor\PublishBookProcessor;
    use Sylius\Resource\Metadata\Update;
    use Sylius\Resource\Model\ResourceInterface;
    // [...]
    #[Update(
    methods: ['PUT', 'PATCH', 'POST'],
    shortName: 'publish',
    validate: false,
    )]
    class Book implements ResourceInterface
    {
    }

    View full-size slide

  117. Publishing books with custom processor
    We configure an update operation.
    ` `
    use App\State\Processor\PublishBookProcessor;
    processor: PublishBookProcessor::class,
    namespace App\Entity;
    use Sylius\Resource\Metadata\Update;
    use Sylius\Resource\Model\ResourceInterface;
    // [...]
    #[Update(
    methods: ['PUT', 'PATCH', 'POST'],
    shortName: 'publish',
    validate: false,
    )]
    class Book implements ResourceInterface
    {
    }

    View full-size slide

  118. Publishing books with custom processor
    We configure an update operation.
    ` `
    validate: false,
    namespace App\Entity;
    use App\State\Processor\PublishBookProcessor;
    use Sylius\Resource\Metadata\Update;
    use Sylius\Resource\Model\ResourceInterface;
    // [...]
    #[Update(
    methods: ['PUT', 'PATCH', 'POST'],
    shortName: 'publish',
    processor: PublishBookProcessor::class,
    )]
    class Book implements ResourceInterface
    {
    }

    View full-size slide

  119. Route
    It will configure this route for your publish operation.
    ` `

    View full-size slide

  120. Route
    It will configure this route for your publish operation.
    Name Method Path
    app_book_publish PUT, PATCH, POST /books/{id}/publish
    ` `

    View full-size slide

  121. // src/State/Processor/PublishBookProcessor.php
    use Sylius\Resource\Context\Context;
    use Sylius\Resource\Doctrine\Common\State\PersistProcessor;
    use Sylius\Resource\Metadata\Operation;
    use Sylius\Resource\State\ProcessorInterface;
    use Symfony\\Workflow\WorkflowInterface;
    final class PublishBookProcessor implements ProcessorInterface
    {
    public function __construct(
    private readonly WorkflowInterface $bookPublishingStateMachine,
    private readonly PersistProcessor $persistProcessor,
    ) {
    }
    public function process(mixed $data, Operation $operation, Context $context): mixed
    {
    if ($this->bookPublishingStateMachine->can($data, 'publish')) {
    $this->bookPublishingStateMachine->apply($data, 'publish');
    }
    return $this->persistProcessor->process($data, $operation, $context);
    }
    }

    View full-size slide

  122. final class PublishBookProcessor implements ProcessorInterface
    // src/State/Processor/PublishBookProcessor.php
    use Sylius\Resource\Context\Context;
    use Sylius\Resource\Doctrine\Common\State\PersistProcessor;
    use Sylius\Resource\Metadata\Operation;
    use Sylius\Resource\State\ProcessorInterface;
    use Symfony\\Workflow\WorkflowInterface;
    {
    public function __construct(
    private readonly WorkflowInterface $bookPublishingStateMachine,
    private readonly PersistProcessor $persistProcessor,
    ) {
    }
    public function process(mixed $data, Operation $operation, Context $context): mixed
    {
    if ($this->bookPublishingStateMachine->can($data, 'publish')) {
    $this->bookPublishingStateMachine->apply($data, 'publish');
    }
    return $this->persistProcessor->process($data, $operation, $context);
    }
    }

    View full-size slide

  123. use Sylius\Resource\State\ProcessorInterface;
    final class PublishBookProcessor implements ProcessorInterface
    // src/State/Processor/PublishBookProcessor.php
    use Sylius\Resource\Context\Context;
    use Sylius\Resource\Doctrine\Common\State\PersistProcessor;
    use Sylius\Resource\Metadata\Operation;
    use Symfony\\Workflow\WorkflowInterface;
    {
    public function __construct(
    private readonly WorkflowInterface $bookPublishingStateMachine,
    private readonly PersistProcessor $persistProcessor,
    ) {
    }
    public function process(mixed $data, Operation $operation, Context $context): mixed
    {
    if ($this->bookPublishingStateMachine->can($data, 'publish')) {
    $this->bookPublishingStateMachine->apply($data, 'publish');
    }
    return $this->persistProcessor->process($data, $operation, $context);
    }
    }

    View full-size slide

  124. private readonly WorkflowInterface $bookPublishingStateMachine,
    // src/State/Processor/PublishBookProcessor.php
    use Sylius\Resource\Context\Context;
    use Sylius\Resource\Doctrine\Common\State\PersistProcessor;
    use Sylius\Resource\Metadata\Operation;
    use Sylius\Resource\State\ProcessorInterface;
    use Symfony\\Workflow\WorkflowInterface;
    final class PublishBookProcessor implements ProcessorInterface
    {
    public function __construct(
    private readonly PersistProcessor $persistProcessor,
    ) {
    }
    public function process(mixed $data, Operation $operation, Context $context): mixed
    {
    if ($this->bookPublishingStateMachine->can($data, 'publish')) {
    $this->bookPublishingStateMachine->apply($data, 'publish');
    }
    return $this->persistProcessor->process($data, $operation, $context);
    }
    }

    View full-size slide

  125. use Symfony\\Workflow\WorkflowInterface;
    private readonly WorkflowInterface $bookPublishingStateMachine,
    // src/State/Processor/PublishBookProcessor.php
    use Sylius\Resource\Context\Context;
    use Sylius\Resource\Doctrine\Common\State\PersistProcessor;
    use Sylius\Resource\Metadata\Operation;
    use Sylius\Resource\State\ProcessorInterface;
    final class PublishBookProcessor implements ProcessorInterface
    {
    public function __construct(
    private readonly PersistProcessor $persistProcessor,
    ) {
    }
    public function process(mixed $data, Operation $operation, Context $context): mixed
    {
    if ($this->bookPublishingStateMachine->can($data, 'publish')) {
    $this->bookPublishingStateMachine->apply($data, 'publish');
    }
    return $this->persistProcessor->process($data, $operation, $context);
    }
    }

    View full-size slide

  126. private readonly PersistProcessor $persistProcessor,
    // src/State/Processor/PublishBookProcessor.php
    use Sylius\Resource\Context\Context;
    use Sylius\Resource\Doctrine\Common\State\PersistProcessor;
    use Sylius\Resource\Metadata\Operation;
    use Sylius\Resource\State\ProcessorInterface;
    use Symfony\\Workflow\WorkflowInterface;
    final class PublishBookProcessor implements ProcessorInterface
    {
    public function __construct(
    private readonly WorkflowInterface $bookPublishingStateMachine,
    ) {
    }
    public function process(mixed $data, Operation $operation, Context $context): mixed
    {
    if ($this->bookPublishingStateMachine->can($data, 'publish')) {
    $this->bookPublishingStateMachine->apply($data, 'publish');
    }
    return $this->persistProcessor->process($data, $operation, $context);
    }
    }

    View full-size slide

  127. use Sylius\Resource\Doctrine\Common\State\PersistProcessor;
    private readonly PersistProcessor $persistProcessor,
    // src/State/Processor/PublishBookProcessor.php
    use Sylius\Resource\Context\Context;
    use Sylius\Resource\Metadata\Operation;
    use Sylius\Resource\State\ProcessorInterface;
    use Symfony\\Workflow\WorkflowInterface;
    final class PublishBookProcessor implements ProcessorInterface
    {
    public function __construct(
    private readonly WorkflowInterface $bookPublishingStateMachine,
    ) {
    }
    public function process(mixed $data, Operation $operation, Context $context): mixed
    {
    if ($this->bookPublishingStateMachine->can($data, 'publish')) {
    $this->bookPublishingStateMachine->apply($data, 'publish');
    }
    return $this->persistProcessor->process($data, $operation, $context);
    }
    }

    View full-size slide

  128. public function process(mixed $data, Operation $operation, Context $context): mixed
    // src/State/Processor/PublishBookProcessor.php
    use Sylius\Resource\Context\Context;
    use Sylius\Resource\Doctrine\Common\State\PersistProcessor;
    use Sylius\Resource\Metadata\Operation;
    use Sylius\Resource\State\ProcessorInterface;
    use Symfony\\Workflow\WorkflowInterface;
    final class PublishBookProcessor implements ProcessorInterface
    {
    public function __construct(
    private readonly WorkflowInterface $bookPublishingStateMachine,
    private readonly PersistProcessor $persistProcessor,
    ) {
    }
    {
    if ($this->bookPublishingStateMachine->can($data, 'publish')) {
    $this->bookPublishingStateMachine->apply($data, 'publish');
    }
    return $this->persistProcessor->process($data, $operation, $context);
    }
    }

    View full-size slide

  129. if ($this->bookPublishingStateMachine->can($data, 'publish')) {
    // src/State/Processor/PublishBookProcessor.php
    use Sylius\Resource\Context\Context;
    use Sylius\Resource\Doctrine\Common\State\PersistProcessor;
    use Sylius\Resource\Metadata\Operation;
    use Sylius\Resource\State\ProcessorInterface;
    use Symfony\\Workflow\WorkflowInterface;
    final class PublishBookProcessor implements ProcessorInterface
    {
    public function __construct(
    private readonly WorkflowInterface $bookPublishingStateMachine,
    private readonly PersistProcessor $persistProcessor,
    ) {
    }
    public function process(mixed $data, Operation $operation, Context $context): mixed
    {
    $this->bookPublishingStateMachine->apply($data, 'publish');
    }
    return $this->persistProcessor->process($data, $operation, $context);
    }
    }

    View full-size slide

  130. $this->bookPublishingStateMachine->apply($data, 'publish');
    // src/State/Processor/PublishBookProcessor.php
    use Sylius\Resource\Context\Context;
    use Sylius\Resource\Doctrine\Common\State\PersistProcessor;
    use Sylius\Resource\Metadata\Operation;
    use Sylius\Resource\State\ProcessorInterface;
    use Symfony\\Workflow\WorkflowInterface;
    final class PublishBookProcessor implements ProcessorInterface
    {
    public function __construct(
    private readonly WorkflowInterface $bookPublishingStateMachine,
    private readonly PersistProcessor $persistProcessor,
    ) {
    }
    public function process(mixed $data, Operation $operation, Context $context): mixed
    {
    if ($this->bookPublishingStateMachine->can($data, 'publish')) {
    }
    return $this->persistProcessor->process($data, $operation, $context);
    }
    }

    View full-size slide

  131. return $this->persistProcessor->process($data, $operation, $context);
    // src/State/Processor/PublishBookProcessor.php
    use Sylius\Resource\Context\Context;
    use Sylius\Resource\Doctrine\Common\State\PersistProcessor;
    use Sylius\Resource\Metadata\Operation;
    use Sylius\Resource\State\ProcessorInterface;
    use Symfony\\Workflow\WorkflowInterface;
    final class PublishBookProcessor implements ProcessorInterface
    {
    public function __construct(
    private readonly WorkflowInterface $bookPublishingStateMachine,
    private readonly PersistProcessor $persistProcessor,
    ) {
    }
    public function process(mixed $data, Operation $operation, Context $context): mixed
    {
    if ($this->bookPublishingStateMachine->can($data, 'publish')) {
    $this->bookPublishingStateMachine->apply($data, 'publish');
    }
    }
    }

    View full-size slide

  132. Publishing many books

    View full-size slide

  133. Publishing many books
    We’ll use Bulk Update operation which allows to update several items of your resource at the same time.
    ` `

    View full-size slide

  134. Publishing many books
    We’ll use Bulk Update operation which allows to update several items of your resource at the same time.
    ` `
    namespace App\Entity;
    use App\State\Processor\PublishBookProcessor;
    use Sylius\Resource\Metadata\BulkUpdate;
    // [...]
    #[BulkUpdate(
    shortName: 'bulk_publish',
    processor: PublishBookProcessor::class,
    validate: false,
    )]
    class Book implements ResourceInterface
    {
    }

    View full-size slide

  135. Publishing many books
    We’ll use Bulk Update operation which allows to update several items of your resource at the same time.
    ` `
    #[BulkUpdate(
    namespace App\Entity;
    use App\State\Processor\PublishBookProcessor;
    use Sylius\Resource\Metadata\BulkUpdate;
    // [...]
    shortName: 'bulk_publish',
    processor: PublishBookProcessor::class,
    validate: false,
    )]
    class Book implements ResourceInterface
    {
    }

    View full-size slide

  136. Publishing many books
    We’ll use Bulk Update operation which allows to update several items of your resource at the same time.
    ` `
    use Sylius\Resource\Metadata\BulkUpdate;
    #[BulkUpdate(
    namespace App\Entity;
    use App\State\Processor\PublishBookProcessor;
    // [...]
    shortName: 'bulk_publish',
    processor: PublishBookProcessor::class,
    validate: false,
    )]
    class Book implements ResourceInterface
    {
    }

    View full-size slide

  137. Publishing many books
    We’ll use Bulk Update operation which allows to update several items of your resource at the same time.
    ` `
    shortName: 'bulk_publish',
    namespace App\Entity;
    use App\State\Processor\PublishBookProcessor;
    use Sylius\Resource\Metadata\BulkUpdate;
    // [...]
    #[BulkUpdate(
    processor: PublishBookProcessor::class,
    validate: false,
    )]
    class Book implements ResourceInterface
    {
    }

    View full-size slide

  138. Publishing many books
    We’ll use Bulk Update operation which allows to update several items of your resource at the same time.
    ` `
    processor: PublishBookProcessor::class,
    namespace App\Entity;
    use App\State\Processor\PublishBookProcessor;
    use Sylius\Resource\Metadata\BulkUpdate;
    // [...]
    #[BulkUpdate(
    shortName: 'bulk_publish',
    validate: false,
    )]
    class Book implements ResourceInterface
    {
    }

    View full-size slide

  139. Publishing many books
    We’ll use Bulk Update operation which allows to update several items of your resource at the same time.
    ` `
    validate: false,
    namespace App\Entity;
    use App\State\Processor\PublishBookProcessor;
    use Sylius\Resource\Metadata\BulkUpdate;
    // [...]
    #[BulkUpdate(
    shortName: 'bulk_publish',
    processor: PublishBookProcessor::class,
    )]
    class Book implements ResourceInterface
    {
    }

    View full-size slide

  140. Route
    It will configure this route for your bulk_publish operation.
    ` `

    View full-size slide

  141. Route
    It will configure this route for your bulk_publish operation.
    Name Method Path
    app_book_bulk_publish PUT, PATCH, POST /books/bulk_publish
    ` `

    View full-size slide

  142. final class BookGrid extends AbstractGrid implements ResourceAwareGridInterface
    {
    // [...]
    public function buildGrid(GridBuilderInterface $gridBuilder): void
    {
    $gridBuilder
    // [...]
    ->addActionGroup(
    BulkActionGroup::create(
    DeleteAction::create(),
    Action::create('publish', 'apply_transition')
    ->setLabel('app.ui.publish')
    ->setIcon('icon: checkmark')
    ->setOptions([
    'link' => [
    'route' => 'app_admin_book_bulk_publish',
    ],
    'class' => 'green',
    ]),
    )
    )
    ;
    }
    public function getResourceClass(): string

    View full-size slide

  143. Action::create('publish', 'apply_transition')
    ->setLabel('app.ui.publish')
    ->setIcon('icon: checkmark')
    ->setOptions([
    'link' => [
    'route' => 'app_admin_book_bulk_publish',
    ],
    'class' => 'green',
    ]),
    )
    )
    ;
    }
    final class BookGrid extends AbstractGrid implements ResourceAwareGridInterface
    {
    // [...]
    public function buildGrid(GridBuilderInterface $gridBuilder): void
    {
    $gridBuilder
    // [...]
    ->addActionGroup(
    BulkActionGroup::create(
    DeleteAction::create(),
    public function getResourceClass(): string

    View full-size slide

  144. Action::create('publish', 'apply_transition')
    final class BookGrid extends AbstractGrid implements ResourceAwareGridInterface
    {
    // [...]
    public function buildGrid(GridBuilderInterface $gridBuilder): void
    {
    $gridBuilder
    // [...]
    ->addActionGroup(
    BulkActionGroup::create(
    DeleteAction::create(),
    ->setLabel('app.ui.publish')
    ->setIcon('icon: checkmark')
    ->setOptions([
    'link' => [
    'route' => 'app_admin_book_bulk_publish',
    ],
    'class' => 'green',
    ]),
    )
    )
    ;
    }
    public function getResourceClass(): string

    View full-size slide

  145. ->setOptions([
    'link' => [
    'route' => 'app_admin_book_bulk_publish',
    ],
    'class' => 'green',
    ]),
    )
    final class BookGrid extends AbstractGrid implements ResourceAwareGridInterface
    {
    // [...]
    public function buildGrid(GridBuilderInterface $gridBuilder): void
    {
    $gridBuilder
    // [...]
    ->addActionGroup(
    BulkActionGroup::create(
    DeleteAction::create(),
    Action::create('publish', 'apply_transition')
    ->setLabel('app.ui.publish')
    ->setIcon('icon: checkmark')
    )
    ;
    }
    public function getResourceClass(): string

    View full-size slide

  146. 'link' => [
    'route' => 'app_admin_book_bulk_publish',
    ],
    final class BookGrid extends AbstractGrid implements ResourceAwareGridInterface
    {
    // [...]
    public function buildGrid(GridBuilderInterface $gridBuilder): void
    {
    $gridBuilder
    // [...]
    ->addActionGroup(
    BulkActionGroup::create(
    DeleteAction::create(),
    Action::create('publish', 'apply_transition')
    ->setLabel('app.ui.publish')
    ->setIcon('icon: checkmark')
    ->setOptions([
    'class' => 'green',
    ]),
    )
    )
    ;
    }
    public function getResourceClass(): string

    View full-size slide

  147. Sylius Resource without driver

    View full-size slide

  148. Sylius Resource without driver
    No Doctrine driver

    View full-size slide

  149. Sylius Resource without driver
    No Doctrine driver
    Retrieve data with providers

    View full-size slide

  150. Sylius Resource without driver
    No Doctrine driver
    Retrieve data with providers
    Persist data with processors

    View full-size slide

  151. Resource with data from a CSV file
    config/data/board_games.csv
    6b2fff2b-0b43-489b-8c48-9c0427a1c4c7,Stone Age,"Travel to the time of hunters and gatherers in this classic game of tool
    d068029a-0c32-4728-95bf-5f614d53440b,Ticket to Ride,"Build your railroad across North America to connect cities and comp
    5ba27cbd-e230-48b7-86ae-ad2eaa05ebc0,7 Wonders,"Draft cards to develop your ancient civilization and build its Wonder of
    2919785a-66e7-44da-b356-b98e07fbb9e8,Puerto Rico,"Ship goods, construct buildings, and choose roles that benefit you mor
    c4856c93-3a41-40eb-9994-f58a1035d99b,Azul, "Artfully embellish the walls of your palace by drafting the most beautiful t
    a953d760-07d5-4add-b87e-b483ebb194df,Die Fürsten von Florenz,"Attract artisans and scholars to your palazzo by building
    ce461300-25c3-4a72-aa7d-6ec7021d7b80,The Voyages of Marco Polo,"Using unique abilities, fulfill contracts and reach your
    599dcd98-8167-4f4a-b181-efd7c24bb434,Tigris & Euphrates,"Keep your Mesopotamian civilisation in perfect balance through
    305749e6-7a05-468a-b1aa-46ee6c1a89ac,Patchwork,"Piece together a quilt and leave no holes to become the button master."
    5e980dad-d8ef-4e47-910a-4dc9f91b38fd,Cartagena,"Groups of pirates race to reach their ship and escape from Cartegena."
    6a47110b-b65d-4e94-9ce0-a06cd7823ecc,Village,"Send your villagers to work, travel, pray, and... die when it brings the m
    e75c5edd-bf6a-49f8-a409-90183aea660c,Splendor,"Renaissance merchants race to grab gems, acquire property, and please nob
    74c0d7ba-8acf-46eb-a6a7-c8f04c1860ff,Ra,"Bid to acquire the most valuable sets of Egyptian artifacts and resources."
    44bc972e-6a0e-423a-9140-caae3f7f8710,Carcassonne,"Shape the medieval landscape of France, claiming cities, monasteries a
    0a937d58-24dd-4dae-894a-b600dfa334d4,Modern Art,"Four types of auctions challenge players in this classic game of art sp
    c3661009-d024-4c83-8ef6-a967a0a21e6f,Goa,"Run a spice trade business in colonial-era India in this closed-loop economy g
    edbb5801-f1ae-4c14-b86a-130a7c3a1c4a,Shogun,"Gain control of medieval Japan by amassing troops and sending them out to b
    1dec8957-3203-4f84-95c5-ceb3754e4b50,Samurai,"Dispute the favor of three different castes in order to unite Japan under
    110c7432-c754-4174-9d0b-7402dc57a500,Kingdomino,"Build a kingdom with varied terrains on domino-shaped tiles in this fas

    View full-size slide

  152. namespace App\BoardGameBlog\Infrastructure\Sylius\Resource;
    use Sylius\Resource\Metadata\AsResource;
    use Sylius\Resource\Model\ResourceInterface;
    use Symfony\\Validator\Constraints\NotBlank;
    #[AsResource(driver: false)]
    final class BoardGameResource implements ResourceInterface
    {
    public function __construct(
    public ?string $id = null,
    #[NotBlank] public ?string $name = null,
    public ?string $shortDescription = null,
    ) {
    }
    public function getId(): string
    {
    return $this->id;
    }
    }

    View full-size slide

  153. #[AsResource(driver: false)]
    namespace App\BoardGameBlog\Infrastructure\Sylius\Resource;
    use Sylius\Resource\Metadata\AsResource;
    use Sylius\Resource\Model\ResourceInterface;
    use Symfony\\Validator\Constraints\NotBlank;
    final class BoardGameResource implements ResourceInterface
    {
    public function __construct(
    public ?string $id = null,
    #[NotBlank] public ?string $name = null,
    public ?string $shortDescription = null,
    ) {
    }
    public function getId(): string
    {
    return $this->id;
    }
    }

    View full-size slide

  154. use Sylius\Resource\Metadata\AsResource;
    #[AsResource(driver: false)]
    namespace App\BoardGameBlog\Infrastructure\Sylius\Resource;
    use Sylius\Resource\Model\ResourceInterface;
    use Symfony\\Validator\Constraints\NotBlank;
    final class BoardGameResource implements ResourceInterface
    {
    public function __construct(
    public ?string $id = null,
    #[NotBlank] public ?string $name = null,
    public ?string $shortDescription = null,
    ) {
    }
    public function getId(): string
    {
    return $this->id;
    }
    }

    View full-size slide

  155. final class BoardGameResource implements ResourceInterface
    namespace App\BoardGameBlog\Infrastructure\Sylius\Resource;
    use Sylius\Resource\Metadata\AsResource;
    use Sylius\Resource\Model\ResourceInterface;
    use Symfony\\Validator\Constraints\NotBlank;
    #[AsResource(driver: false)]
    {
    public function __construct(
    public ?string $id = null,
    #[NotBlank] public ?string $name = null,
    public ?string $shortDescription = null,
    ) {
    }
    public function getId(): string
    {
    return $this->id;
    }
    }

    View full-size slide

  156. use Sylius\Resource\Model\ResourceInterface;
    final class BoardGameResource implements ResourceInterface
    namespace App\BoardGameBlog\Infrastructure\Sylius\Resource;
    use Sylius\Resource\Metadata\AsResource;
    use Symfony\\Validator\Constraints\NotBlank;
    #[AsResource(driver: false)]
    {
    public function __construct(
    public ?string $id = null,
    #[NotBlank] public ?string $name = null,
    public ?string $shortDescription = null,
    ) {
    }
    public function getId(): string
    {
    return $this->id;
    }
    }

    View full-size slide

  157. use Sylius\Resource\Model\ResourceInterface;
    final class BoardGameResource implements ResourceInterface
    public function getId(): string
    {
    return $this->id;
    }
    namespace App\BoardGameBlog\Infrastructure\Sylius\Resource;
    use Sylius\Resource\Metadata\AsResource;
    use Symfony\\Validator\Constraints\NotBlank;
    #[AsResource(driver: false)]
    {
    public function __construct(
    public ?string $id = null,
    #[NotBlank] public ?string $name = null,
    public ?string $shortDescription = null,
    ) {
    }
    }

    View full-size slide

  158. Browsing board games

    View full-size slide

  159. Browsing board games
    namespace App\BoardGameBlog\Infrastructure\Sylius\Resource;
    use Sylius\Resource\Metadata\Index;
    use Sylius\Resource\Metadata\AsResource;
    use Sylius\Resource\Model\ResourceInterface;
    use Symfony\\Validator\Constraints\NotBlank;
    #[AsResource(driver: false)]
    #[Index(
    grid: 'app_board_game'
    )]
    final class BoardGameResource implements ResourceInterface
    {
    // [...]
    }

    View full-size slide

  160. Browsing board games
    #[Index(
    grid: 'app_board_game'
    )]
    final class BoardGameResource implements ResourceInterface
    {
    namespace App\BoardGameBlog\Infrastructure\Sylius\Resource;
    use Sylius\Resource\Metadata\Index;
    use Sylius\Resource\Metadata\AsResource;
    use Sylius\Resource\Model\ResourceInterface;
    use Symfony\\Validator\Constraints\NotBlank;
    #[AsResource(driver: false)]
    // [...]
    }

    View full-size slide

  161. Browsing board games
    use Sylius\Resource\Metadata\Index;
    #[Index(
    grid: 'app_board_game'
    )]
    final class BoardGameResource implements ResourceInterface
    {
    namespace App\BoardGameBlog\Infrastructure\Sylius\Resource;
    use Sylius\Resource\Metadata\AsResource;
    use Sylius\Resource\Model\ResourceInterface;
    use Symfony\\Validator\Constraints\NotBlank;
    #[AsResource(driver: false)]
    // [...]
    }

    View full-size slide

  162. // src/BoardGameBlog/Infrastructure/Sylius/Grid/BoardGameGrid.php
    final class BoardGameGrid extends AbstractGrid implements ResourceAwareGridInterface
    {
    public static function getName(): string { return 'app_board_game'; }
    public function buildGrid(GridBuilderInterface $gridBuilder): void
    {
    $gridBuilder
    ->setProvider(BoardGameGridProvider::class)
    ->addField(
    StringField::create('name')
    ->setLabel('Name')
    )
    ->addField(
    StringField::create('shortDescription')
    ->setLabel('Short Description'),
    )
    ;
    }
    public function getResourceClass(): string
    {
    return BoardGameResource::class;
    }
    }

    View full-size slide

  163. public function getResourceClass(): string
    {
    return BoardGameResource::class;
    }
    // src/BoardGameBlog/Infrastructure/Sylius/Grid/BoardGameGrid.php
    final class BoardGameGrid extends AbstractGrid implements ResourceAwareGridInterface
    {
    public static function getName(): string { return 'app_board_game'; }
    public function buildGrid(GridBuilderInterface $gridBuilder): void
    {
    $gridBuilder
    ->setProvider(BoardGameGridProvider::class)
    ->addField(
    StringField::create('name')
    ->setLabel('Name')
    )
    ->addField(
    StringField::create('shortDescription')
    ->setLabel('Short Description'),
    )
    ;
    }
    }

    View full-size slide

  164. ->setProvider(BoardGameGridProvider::class)
    // src/BoardGameBlog/Infrastructure/Sylius/Grid/BoardGameGrid.php
    final class BoardGameGrid extends AbstractGrid implements ResourceAwareGridInterface
    {
    public static function getName(): string { return 'app_board_game'; }
    public function buildGrid(GridBuilderInterface $gridBuilder): void
    {
    $gridBuilder
    ->addField(
    StringField::create('name')
    ->setLabel('Name')
    )
    ->addField(
    StringField::create('shortDescription')
    ->setLabel('Short Description'),
    )
    ;
    }
    public function getResourceClass(): string
    {
    return BoardGameResource::class;
    }
    }

    View full-size slide

  165. namespace App\BoardGameBlog\Infrastructure\Sylius\Grid\DataProvider;
    // [...]
    use Sylius\Grid\Data\DataProviderInterface;
    final class BoardGameGridProvider implements DataProviderInterface
    {
    public function __construct(private readonly string $dataDir) {}
    public function getData(Grid $grid, Parameters $parameters): Pagerfanta
    {
    $data = [];
    foreach ($this->getFileData() as $row) {
    [$id, $name, $shortDescription] = $row;
    $data[] = new BoardGameResource(
    id: $id,
    name: $name,
    shortDescription: $shortDescription,
    );
    }
    return new Pagerfanta(new ArrayAdapter($data));
    }

    View full-size slide

  166. namespace App\BoardGameBlog\Infrastructure\Sylius\Grid\DataProvider;
    // [...]
    use Sylius\Grid\Data\DataProviderInterface;
    final class BoardGameGridProvider implements DataProviderInterface
    {
    public function __construct(private readonly string $dataDir) {}
    public function getData(Grid $grid, Parameters $parameters): Pagerfanta
    {
    $data = [];
    foreach ($this->getFileData() as $row) {
    [$id, $name, $shortDescription] = $row;
    $data[] = new BoardGameResource(
    id: $id,
    name: $name,
    shortDescription: $shortDescription,
    );
    }
    return new Pagerfanta(new ArrayAdapter($data));
    }

    View full-size slide

  167. use Sylius\Grid\Data\DataProviderInterface;
    final class BoardGameGridProvider implements DataProviderInterface
    namespace App\BoardGameBlog\Infrastructure\Sylius\Grid\DataProvider;
    // [...]
    {
    public function __construct(private readonly string $dataDir) {}
    public function getData(Grid $grid, Parameters $parameters): Pagerfanta
    {
    $data = [];
    foreach ($this->getFileData() as $row) {
    [$id, $name, $shortDescription] = $row;
    $data[] = new BoardGameResource(
    id: $id,
    name: $name,
    shortDescription: $shortDescription,
    );
    }
    return new Pagerfanta(new ArrayAdapter($data));
    }

    View full-size slide

  168. use Sylius\Grid\Data\DataProviderInterface;
    final class BoardGameGridProvider implements DataProviderInterface
    public function getData(Grid $grid, Parameters $parameters): Pagerfanta
    namespace App\BoardGameBlog\Infrastructure\Sylius\Grid\DataProvider;
    // [...]
    {
    public function __construct(private readonly string $dataDir) {}
    {
    $data = [];
    foreach ($this->getFileData() as $row) {
    [$id, $name, $shortDescription] = $row;
    $data[] = new BoardGameResource(
    id: $id,
    name: $name,
    shortDescription: $shortDescription,
    );
    }
    return new Pagerfanta(new ArrayAdapter($data));
    }

    View full-size slide

  169. $data = [];
    namespace App\BoardGameBlog\Infrastructure\Sylius\Grid\DataProvider;
    // [...]
    use Sylius\Grid\Data\DataProviderInterface;
    final class BoardGameGridProvider implements DataProviderInterface
    {
    public function __construct(private readonly string $dataDir) {}
    public function getData(Grid $grid, Parameters $parameters): Pagerfanta
    {
    foreach ($this->getFileData() as $row) {
    [$id, $name, $shortDescription] = $row;
    $data[] = new BoardGameResource(
    id: $id,
    name: $name,
    shortDescription: $shortDescription,
    );
    }
    return new Pagerfanta(new ArrayAdapter($data));
    }

    View full-size slide

  170. foreach ($this->getFileData() as $row) {
    namespace App\BoardGameBlog\Infrastructure\Sylius\Grid\DataProvider;
    // [...]
    use Sylius\Grid\Data\DataProviderInterface;
    final class BoardGameGridProvider implements DataProviderInterface
    {
    public function __construct(private readonly string $dataDir) {}
    public function getData(Grid $grid, Parameters $parameters): Pagerfanta
    {
    $data = [];
    [$id, $name, $shortDescription] = $row;
    $data[] = new BoardGameResource(
    id: $id,
    name: $name,
    shortDescription: $shortDescription,
    );
    }
    return new Pagerfanta(new ArrayAdapter($data));
    }

    View full-size slide

  171. [$id, $name, $shortDescription] = $row;
    namespace App\BoardGameBlog\Infrastructure\Sylius\Grid\DataProvider;
    // [...]
    use Sylius\Grid\Data\DataProviderInterface;
    final class BoardGameGridProvider implements DataProviderInterface
    {
    public function __construct(private readonly string $dataDir) {}
    public function getData(Grid $grid, Parameters $parameters): Pagerfanta
    {
    $data = [];
    foreach ($this->getFileData() as $row) {
    $data[] = new BoardGameResource(
    id: $id,
    name: $name,
    shortDescription: $shortDescription,
    );
    }
    return new Pagerfanta(new ArrayAdapter($data));
    }

    View full-size slide

  172. $data[] = new BoardGameResource(
    id: $id,
    name: $name,
    shortDescription: $shortDescription,
    );
    namespace App\BoardGameBlog\Infrastructure\Sylius\Grid\DataProvider;
    // [...]
    use Sylius\Grid\Data\DataProviderInterface;
    final class BoardGameGridProvider implements DataProviderInterface
    {
    public function __construct(private readonly string $dataDir) {}
    public function getData(Grid $grid, Parameters $parameters): Pagerfanta
    {
    $data = [];
    foreach ($this->getFileData() as $row) {
    [$id, $name, $shortDescription] = $row;
    }
    return new Pagerfanta(new ArrayAdapter($data));
    }

    View full-size slide

  173. return new Pagerfanta(new ArrayAdapter($data));
    namespace App\BoardGameBlog\Infrastructure\Sylius\Grid\DataProvider;
    // [...]
    use Sylius\Grid\Data\DataProviderInterface;
    final class BoardGameGridProvider implements DataProviderInterface
    {
    public function __construct(private readonly string $dataDir) {}
    public function getData(Grid $grid, Parameters $parameters): Pagerfanta
    {
    $data = [];
    foreach ($this->getFileData() as $row) {
    [$id, $name, $shortDescription] = $row;
    $data[] = new BoardGameResource(
    id: $id,
    name: $name,
    shortDescription: $shortDescription,
    );
    }
    }

    View full-size slide

  174. namespace App\BoardGameBlog\Infrastructure\Sylius\Grid\DataProvider;
    // [...]
    use Sylius\Grid\Data\DataProviderInterface;
    final class BoardGameGridProvider implements DataProviderInterface
    {
    // [...]
    private function getFileData(): array
    {
    return array_map('str_getcsv', file($this->dataDir . '/board_games.csv'));
    }
    }

    View full-size slide

  175. private function getFileData(): array
    {
    return array_map('str_getcsv', file($this->dataDir . '/board_games.csv'));
    }
    namespace App\BoardGameBlog\Infrastructure\Sylius\Grid\DataProvider;
    // [...]
    use Sylius\Grid\Data\DataProviderInterface;
    final class BoardGameGridProvider implements DataProviderInterface
    {
    // [...]
    }

    View full-size slide

  176. Adding board games

    View full-size slide

  177. Adding board games
    namespace App\BoardGameBlog\Infrastructure\Sylius\Resource;
    // [...]
    use App\BoardGameBlog\Infrastructure\Sylius\State\Processor\CreateBoardGameProcessor;
    use App\BoardGameBlog\Infrastructure\Symfony\Form\Type\BoardGameType;
    use Sylius\Resource\Metadata\Index;
    use Sylius\Resource\Metadata\Create;
    #[AsResource(
    driver: false,
    formType: BoardGameType::class),
    ]
    #[Index(
    grid: 'app_board_game'
    )]
    #[Create(
    processor: CreateBoardGameProcessor::class,
    )]
    final class BoardGameResource implements ResourceInterface
    {
    // [...]
    }

    View full-size slide

  178. Adding board games
    formType: BoardGameType::class),
    namespace App\BoardGameBlog\Infrastructure\Sylius\Resource;
    // [...]
    use App\BoardGameBlog\Infrastructure\Sylius\State\Processor\CreateBoardGameProcessor;
    use App\BoardGameBlog\Infrastructure\Symfony\Form\Type\BoardGameType;
    use Sylius\Resource\Metadata\Index;
    use Sylius\Resource\Metadata\Create;
    #[AsResource(
    driver: false,
    ]
    #[Index(
    grid: 'app_board_game'
    )]
    #[Create(
    processor: CreateBoardGameProcessor::class,
    )]
    final class BoardGameResource implements ResourceInterface
    {
    // [...]
    }

    View full-size slide

  179. Adding board games
    use App\BoardGameBlog\Infrastructure\Symfony\Form\Type\BoardGameType;
    formType: BoardGameType::class),
    namespace App\BoardGameBlog\Infrastructure\Sylius\Resource;
    // [...]
    use App\BoardGameBlog\Infrastructure\Sylius\State\Processor\CreateBoardGameProcessor;
    use Sylius\Resource\Metadata\Index;
    use Sylius\Resource\Metadata\Create;
    #[AsResource(
    driver: false,
    ]
    #[Index(
    grid: 'app_board_game'
    )]
    #[Create(
    processor: CreateBoardGameProcessor::class,
    )]
    final class BoardGameResource implements ResourceInterface
    {
    // [...]
    }

    View full-size slide

  180. Adding board games
    processor: CreateBoardGameProcessor::class,
    namespace App\BoardGameBlog\Infrastructure\Sylius\Resource;
    // [...]
    use App\BoardGameBlog\Infrastructure\Sylius\State\Processor\CreateBoardGameProcessor;
    use App\BoardGameBlog\Infrastructure\Symfony\Form\Type\BoardGameType;
    use Sylius\Resource\Metadata\Index;
    use Sylius\Resource\Metadata\Create;
    #[AsResource(
    driver: false,
    formType: BoardGameType::class),
    ]
    #[Index(
    grid: 'app_board_game'
    )]
    #[Create(
    )]
    final class BoardGameResource implements ResourceInterface
    {
    // [...]
    }

    View full-size slide

  181. Adding board games
    use App\BoardGameBlog\Infrastructure\Sylius\State\Processor\CreateBoardGameProcessor;
    processor: CreateBoardGameProcessor::class,
    namespace App\BoardGameBlog\Infrastructure\Sylius\Resource;
    // [...]
    use App\BoardGameBlog\Infrastructure\Symfony\Form\Type\BoardGameType;
    use Sylius\Resource\Metadata\Index;
    use Sylius\Resource\Metadata\Create;
    #[AsResource(
    driver: false,
    formType: BoardGameType::class),
    ]
    #[Index(
    grid: 'app_board_game'
    )]
    #[Create(
    )]
    final class BoardGameResource implements ResourceInterface
    {
    // [...]
    }

    View full-size slide

  182. namespace App\BoardGameBlog\Infrastructure\Sylius\State\Processor;
    // [...]
    use Sylius\Resource\State\ProcessorInterface;
    final class CreateBoardGameProcessor implements ProcessorInterface
    {
    public function __construct(private readonly string $dataDir) {}
    public function process(mixed $data, Operation $operation, Context $context): mixed
    {
    Assert::isInstanceOf($data, BoardGameResource::class);
    $this->createBoardGame($data);
    return null;
    }
    private function createBoardGame(BoardGameResource $boardGameResource): void
    {
    $handle = fopen($this->dataDir . '/board_games.csv', 'a');
    fputcsv($handle, [(string) Uuid::v4(), $boardGameResource->name, $boardGameResource->shortDescription]);
    fclose($handle);
    }
    }

    View full-size slide

  183. final class CreateBoardGameProcessor implements ProcessorInterface
    namespace App\BoardGameBlog\Infrastructure\Sylius\State\Processor;
    // [...]
    use Sylius\Resource\State\ProcessorInterface;
    {
    public function __construct(private readonly string $dataDir) {}
    public function process(mixed $data, Operation $operation, Context $context): mixed
    {
    Assert::isInstanceOf($data, BoardGameResource::class);
    $this->createBoardGame($data);
    return null;
    }
    private function createBoardGame(BoardGameResource $boardGameResource): void
    {
    $handle = fopen($this->dataDir . '/board_games.csv', 'a');
    fputcsv($handle, [(string) Uuid::v4(), $boardGameResource->name, $boardGameResource->shortDescription]);
    fclose($handle);
    }
    }

    View full-size slide

  184. use Sylius\Resource\State\ProcessorInterface;
    final class CreateBoardGameProcessor implements ProcessorInterface
    namespace App\BoardGameBlog\Infrastructure\Sylius\State\Processor;
    // [...]
    {
    public function __construct(private readonly string $dataDir) {}
    public function process(mixed $data, Operation $operation, Context $context): mixed
    {
    Assert::isInstanceOf($data, BoardGameResource::class);
    $this->createBoardGame($data);
    return null;
    }
    private function createBoardGame(BoardGameResource $boardGameResource): void
    {
    $handle = fopen($this->dataDir . '/board_games.csv', 'a');
    fputcsv($handle, [(string) Uuid::v4(), $boardGameResource->name, $boardGameResource->shortDescription]);
    fclose($handle);
    }
    }

    View full-size slide

  185. use Sylius\Resource\State\ProcessorInterface;
    final class CreateBoardGameProcessor implements ProcessorInterface
    public function process(mixed $data, Operation $operation, Context $context): mixed
    namespace App\BoardGameBlog\Infrastructure\Sylius\State\Processor;
    // [...]
    {
    public function __construct(private readonly string $dataDir) {}
    {
    Assert::isInstanceOf($data, BoardGameResource::class);
    $this->createBoardGame($data);
    return null;
    }
    private function createBoardGame(BoardGameResource $boardGameResource): void
    {
    $handle = fopen($this->dataDir . '/board_games.csv', 'a');
    fputcsv($handle, [(string) Uuid::v4(), $boardGameResource->name, $boardGameResource->shortDescription]);
    fclose($handle);
    }
    }

    View full-size slide

  186. Assert::isInstanceOf($data, BoardGameResource::class);
    namespace App\BoardGameBlog\Infrastructure\Sylius\State\Processor;
    // [...]
    use Sylius\Resource\State\ProcessorInterface;
    final class CreateBoardGameProcessor implements ProcessorInterface
    {
    public function __construct(private readonly string $dataDir) {}
    public function process(mixed $data, Operation $operation, Context $context): mixed
    {
    $this->createBoardGame($data);
    return null;
    }
    private function createBoardGame(BoardGameResource $boardGameResource): void
    {
    $handle = fopen($this->dataDir . '/board_games.csv', 'a');
    fputcsv($handle, [(string) Uuid::v4(), $boardGameResource->name, $boardGameResource->shortDescription]);
    fclose($handle);
    }
    }

    View full-size slide

  187. $this->createBoardGame($data);
    namespace App\BoardGameBlog\Infrastructure\Sylius\State\Processor;
    // [...]
    use Sylius\Resource\State\ProcessorInterface;
    final class CreateBoardGameProcessor implements ProcessorInterface
    {
    public function __construct(private readonly string $dataDir) {}
    public function process(mixed $data, Operation $operation, Context $context): mixed
    {
    Assert::isInstanceOf($data, BoardGameResource::class);
    return null;
    }
    private function createBoardGame(BoardGameResource $boardGameResource): void
    {
    $handle = fopen($this->dataDir . '/board_games.csv', 'a');
    fputcsv($handle, [(string) Uuid::v4(), $boardGameResource->name, $boardGameResource->shortDescription]);
    fclose($handle);
    }
    }

    View full-size slide

  188. $this->createBoardGame($data);
    private function createBoardGame(BoardGameResource $boardGameResource): void
    {
    $handle = fopen($this->dataDir . '/board_games.csv', 'a');
    fputcsv($handle, [(string) Uuid::v4(), $boardGameResource->name, $boardGameResource->shortDescription]);
    fclose($handle);
    }
    namespace App\BoardGameBlog\Infrastructure\Sylius\State\Processor;
    // [...]
    use Sylius\Resource\State\ProcessorInterface;
    final class CreateBoardGameProcessor implements ProcessorInterface
    {
    public function __construct(private readonly string $dataDir) {}
    public function process(mixed $data, Operation $operation, Context $context): mixed
    {
    Assert::isInstanceOf($data, BoardGameResource::class);
    return null;
    }
    }

    View full-size slide

  189. $handle = fopen($this->dataDir . '/board_games.csv', 'a');
    namespace App\BoardGameBlog\Infrastructure\Sylius\State\Processor;
    // [...]
    use Sylius\Resource\State\ProcessorInterface;
    final class CreateBoardGameProcessor implements ProcessorInterface
    {
    public function __construct(private readonly string $dataDir) {}
    public function process(mixed $data, Operation $operation, Context $context): mixed
    {
    Assert::isInstanceOf($data, BoardGameResource::class);
    $this->createBoardGame($data);
    return null;
    }
    private function createBoardGame(BoardGameResource $boardGameResource): void
    {
    fputcsv($handle, [(string) Uuid::v4(), $boardGameResource->name, $boardGameResource->shortDescription]);
    fclose($handle);
    }
    }

    View full-size slide

  190. fputcsv($handle, [(string) Uuid::v4(), $boardGameResource->name, $boardGameResource->shortDescription]);
    namespace App\BoardGameBlog\Infrastructure\Sylius\State\Processor;
    // [...]
    use Sylius\Resource\State\ProcessorInterface;
    final class CreateBoardGameProcessor implements ProcessorInterface
    {
    public function __construct(private readonly string $dataDir) {}
    public function process(mixed $data, Operation $operation, Context $context): mixed
    {
    Assert::isInstanceOf($data, BoardGameResource::class);
    $this->createBoardGame($data);
    return null;
    }
    private function createBoardGame(BoardGameResource $boardGameResource): void
    {
    $handle = fopen($this->dataDir . '/board_games.csv', 'a');
    fclose($handle);
    }
    }

    View full-size slide

  191. fclose($handle);
    namespace App\BoardGameBlog\Infrastructure\Sylius\State\Processor;
    // [...]
    use Sylius\Resource\State\ProcessorInterface;
    final class CreateBoardGameProcessor implements ProcessorInterface
    {
    public function __construct(private readonly string $dataDir) {}
    public function process(mixed $data, Operation $operation, Context $context): mixed
    {
    Assert::isInstanceOf($data, BoardGameResource::class);
    $this->createBoardGame($data);
    return null;
    }
    private function createBoardGame(BoardGameResource $boardGameResource): void
    {
    $handle = fopen($this->dataDir . '/board_games.csv', 'a');
    fputcsv($handle, [(string) Uuid::v4(), $boardGameResource->name, $boardGameResource->shortDescription]);
    }
    }

    View full-size slide

  192. return null;
    namespace App\BoardGameBlog\Infrastructure\Sylius\State\Processor;
    // [...]
    use Sylius\Resource\State\ProcessorInterface;
    final class CreateBoardGameProcessor implements ProcessorInterface
    {
    public function __construct(private readonly string $dataDir) {}
    public function process(mixed $data, Operation $operation, Context $context): mixed
    {
    Assert::isInstanceOf($data, BoardGameResource::class);
    $this->createBoardGame($data);
    }
    private function createBoardGame(BoardGameResource $boardGameResource): void
    {
    $handle = fopen($this->dataDir . '/board_games.csv', 'a');
    fputcsv($handle, [(string) Uuid::v4(), $boardGameResource->name, $boardGameResource->shortDescription]);
    fclose($handle);
    }
    }

    View full-size slide

  193. Editing board games

    View full-size slide

  194. Editing board games
    namespace App\BoardGameBlog\Infrastructure\Sylius\Resource;
    // [...]
    use App\BoardGameBlog\Infrastructure\Sylius\State\Processor\UpdateBoardGameProcessor;
    use App\BoardGameBlog\Infrastructure\Sylius\State\Provider\BoardGameItemProvider;
    use Sylius\Resource\Metadata\Update;
    // [...]
    #[Update(
    provider: BoardGameItemProvider::class,
    processor: UpdateBoardGameProcessor::class,
    )]
    final class BoardGameResource implements ResourceInterface
    {
    // [...]
    }

    View full-size slide

  195. Editing board games
    #[Update(
    namespace App\BoardGameBlog\Infrastructure\Sylius\Resource;
    // [...]
    use App\BoardGameBlog\Infrastructure\Sylius\State\Processor\UpdateBoardGameProcessor;
    use App\BoardGameBlog\Infrastructure\Sylius\State\Provider\BoardGameItemProvider;
    use Sylius\Resource\Metadata\Update;
    // [...]
    provider: BoardGameItemProvider::class,
    processor: UpdateBoardGameProcessor::class,
    )]
    final class BoardGameResource implements ResourceInterface
    {
    // [...]
    }

    View full-size slide

  196. Editing board games
    use Sylius\Resource\Metadata\Update;
    #[Update(
    namespace App\BoardGameBlog\Infrastructure\Sylius\Resource;
    // [...]
    use App\BoardGameBlog\Infrastructure\Sylius\State\Processor\UpdateBoardGameProcessor;
    use App\BoardGameBlog\Infrastructure\Sylius\State\Provider\BoardGameItemProvider;
    // [...]
    provider: BoardGameItemProvider::class,
    processor: UpdateBoardGameProcessor::class,
    )]
    final class BoardGameResource implements ResourceInterface
    {
    // [...]
    }

    View full-size slide

  197. Editing board games
    provider: BoardGameItemProvider::class,
    namespace App\BoardGameBlog\Infrastructure\Sylius\Resource;
    // [...]
    use App\BoardGameBlog\Infrastructure\Sylius\State\Processor\UpdateBoardGameProcessor;
    use App\BoardGameBlog\Infrastructure\Sylius\State\Provider\BoardGameItemProvider;
    use Sylius\Resource\Metadata\Update;
    // [...]
    #[Update(
    processor: UpdateBoardGameProcessor::class,
    )]
    final class BoardGameResource implements ResourceInterface
    {
    // [...]
    }

    View full-size slide

  198. Editing board games
    use App\BoardGameBlog\Infrastructure\Sylius\State\Provider\BoardGameItemProvider;
    provider: BoardGameItemProvider::class,
    namespace App\BoardGameBlog\Infrastructure\Sylius\Resource;
    // [...]
    use App\BoardGameBlog\Infrastructure\Sylius\State\Processor\UpdateBoardGameProcessor;
    use Sylius\Resource\Metadata\Update;
    // [...]
    #[Update(
    processor: UpdateBoardGameProcessor::class,
    )]
    final class BoardGameResource implements ResourceInterface
    {
    // [...]
    }

    View full-size slide

  199. Editing board games
    processor: UpdateBoardGameProcessor::class,
    namespace App\BoardGameBlog\Infrastructure\Sylius\Resource;
    // [...]
    use App\BoardGameBlog\Infrastructure\Sylius\State\Processor\UpdateBoardGameProcessor;
    use App\BoardGameBlog\Infrastructure\Sylius\State\Provider\BoardGameItemProvider;
    use Sylius\Resource\Metadata\Update;
    // [...]
    #[Update(
    provider: BoardGameItemProvider::class,
    )]
    final class BoardGameResource implements ResourceInterface
    {
    // [...]
    }

    View full-size slide

  200. Editing board games
    use App\BoardGameBlog\Infrastructure\Sylius\State\Processor\UpdateBoardGameProcessor;
    processor: UpdateBoardGameProcessor::class,
    namespace App\BoardGameBlog\Infrastructure\Sylius\Resource;
    // [...]
    use App\BoardGameBlog\Infrastructure\Sylius\State\Provider\BoardGameItemProvider;
    use Sylius\Resource\Metadata\Update;
    // [...]
    #[Update(
    provider: BoardGameItemProvider::class,
    )]
    final class BoardGameResource implements ResourceInterface
    {
    // [...]
    }

    View full-size slide

  201. // [...]
    use Sylius\Resource\State\ProviderInterface;
    final class BoardGameItemProvider implements ProviderInterface
    {
    public function __construct(private readonly string $dataDir) {}
    public function provide(Operation $operation, Context $context): ?BoardGameResource
    {
    $request = $context->get(RequestOption::class)?->request();
    Assert::notNull($request);
    $id = $request->attributes->get('id');
    Assert::nullOrString($id);
    [$id, $name, $shortDescription] = $this->getFileData()[$id] ?? null;
    if (null === $id) {
    return null;
    }
    return new BoardGameResource(
    id: $id,
    name: $name,
    shortDescription: $shortDescription,
    );

    View full-size slide

  202. final class BoardGameItemProvider implements ProviderInterface
    // [...]
    use Sylius\Resource\State\ProviderInterface;
    {
    public function __construct(private readonly string $dataDir) {}
    public function provide(Operation $operation, Context $context): ?BoardGameResource
    {
    $request = $context->get(RequestOption::class)?->request();
    Assert::notNull($request);
    $id = $request->attributes->get('id');
    Assert::nullOrString($id);
    [$id, $name, $shortDescription] = $this->getFileData()[$id] ?? null;
    if (null === $id) {
    return null;
    }
    return new BoardGameResource(
    id: $id,
    name: $name,
    shortDescription: $shortDescription,
    );

    View full-size slide

  203. use Sylius\Resource\State\ProviderInterface;
    final class BoardGameItemProvider implements ProviderInterface
    // [...]
    {
    public function __construct(private readonly string $dataDir) {}
    public function provide(Operation $operation, Context $context): ?BoardGameResource
    {
    $request = $context->get(RequestOption::class)?->request();
    Assert::notNull($request);
    $id = $request->attributes->get('id');
    Assert::nullOrString($id);
    [$id, $name, $shortDescription] = $this->getFileData()[$id] ?? null;
    if (null === $id) {
    return null;
    }
    return new BoardGameResource(
    id: $id,
    name: $name,
    shortDescription: $shortDescription,
    );

    View full-size slide

  204. public function provide(Operation $operation, Context $context): ?BoardGameResource
    // [...]
    use Sylius\Resource\State\ProviderInterface;
    final class BoardGameItemProvider implements ProviderInterface
    {
    public function __construct(private readonly string $dataDir) {}
    {
    $request = $context->get(RequestOption::class)?->request();
    Assert::notNull($request);
    $id = $request->attributes->get('id');
    Assert::nullOrString($id);
    [$id, $name, $shortDescription] = $this->getFileData()[$id] ?? null;
    if (null === $id) {
    return null;
    }
    return new BoardGameResource(
    id: $id,
    name: $name,
    shortDescription: $shortDescription,
    );

    View full-size slide

  205. $request = $context->get(RequestOption::class)?->request();
    Assert::notNull($request);
    // [...]
    use Sylius\Resource\State\ProviderInterface;
    final class BoardGameItemProvider implements ProviderInterface
    {
    public function __construct(private readonly string $dataDir) {}
    public function provide(Operation $operation, Context $context): ?BoardGameResource
    {
    $id = $request->attributes->get('id');
    Assert::nullOrString($id);
    [$id, $name, $shortDescription] = $this->getFileData()[$id] ?? null;
    if (null === $id) {
    return null;
    }
    return new BoardGameResource(
    id: $id,
    name: $name,
    shortDescription: $shortDescription,
    );

    View full-size slide

  206. $id = $request->attributes->get('id');
    Assert::nullOrString($id);
    // [...]
    use Sylius\Resource\State\ProviderInterface;
    final class BoardGameItemProvider implements ProviderInterface
    {
    public function __construct(private readonly string $dataDir) {}
    public function provide(Operation $operation, Context $context): ?BoardGameResource
    {
    $request = $context->get(RequestOption::class)?->request();
    Assert::notNull($request);
    [$id, $name, $shortDescription] = $this->getFileData()[$id] ?? null;
    if (null === $id) {
    return null;
    }
    return new BoardGameResource(
    id: $id,
    name: $name,
    shortDescription: $shortDescription,
    );

    View full-size slide

  207. [$id, $name, $shortDescription] = $this->getFileData()[$id] ?? null;
    // [...]
    use Sylius\Resource\State\ProviderInterface;
    final class BoardGameItemProvider implements ProviderInterface
    {
    public function __construct(private readonly string $dataDir) {}
    public function provide(Operation $operation, Context $context): ?BoardGameResource
    {
    $request = $context->get(RequestOption::class)?->request();
    Assert::notNull($request);
    $id = $request->attributes->get('id');
    Assert::nullOrString($id);
    if (null === $id) {
    return null;
    }
    return new BoardGameResource(
    id: $id,
    name: $name,
    shortDescription: $shortDescription,
    );

    View full-size slide

  208. if (null === $id) {
    return null;
    }
    // [...]
    use Sylius\Resource\State\ProviderInterface;
    final class BoardGameItemProvider implements ProviderInterface
    {
    public function __construct(private readonly string $dataDir) {}
    public function provide(Operation $operation, Context $context): ?BoardGameResource
    {
    $request = $context->get(RequestOption::class)?->request();
    Assert::notNull($request);
    $id = $request->attributes->get('id');
    Assert::nullOrString($id);
    [$id, $name, $shortDescription] = $this->getFileData()[$id] ?? null;
    return new BoardGameResource(
    id: $id,
    name: $name,
    shortDescription: $shortDescription,
    );

    View full-size slide

  209. return new BoardGameResource(
    id: $id,
    name: $name,
    shortDescription: $shortDescription,
    );
    // [...]
    use Sylius\Resource\State\ProviderInterface;
    final class BoardGameItemProvider implements ProviderInterface
    {
    public function __construct(private readonly string $dataDir) {}
    public function provide(Operation $operation, Context $context): ?BoardGameResource
    {
    $request = $context->get(RequestOption::class)?->request();
    Assert::notNull($request);
    $id = $request->attributes->get('id');
    Assert::nullOrString($id);
    [$id, $name, $shortDescription] = $this->getFileData()[$id] ?? null;
    if (null === $id) {
    return null;
    }

    View full-size slide

  210. // [...]
    use Sylius\Resource\State\ProcessorInterface;
    final class UpdateBoardGameProcessor implements ProcessorInterface
    {
    public function process(mixed $data, Operation $operation, Context $context): mixed
    {
    Assert::isInstanceOf($data, BoardGameResource::class);
    $this->updateBoardGame($data);
    return null;
    }
    private function updateBoardGame(BoardGameResource $boardGameResource): void
    {
    $fileData = $this->getFileData();
    $row = &$fileData[$boardGameResource->id];
    $row[1] = $boardGameResource->name;
    $row[2] = $boardGameResource->shortDescription;
    $handle = fopen($this->dataDir . '/board_games.csv', 'wb');
    foreach ($fileData as $data) {
    fputcsv($handle, $data);

    View full-size slide

  211. final class UpdateBoardGameProcessor implements ProcessorInterface
    // [...]
    use Sylius\Resource\State\ProcessorInterface;
    {
    public function process(mixed $data, Operation $operation, Context $context): mixed
    {
    Assert::isInstanceOf($data, BoardGameResource::class);
    $this->updateBoardGame($data);
    return null;
    }
    private function updateBoardGame(BoardGameResource $boardGameResource): void
    {
    $fileData = $this->getFileData();
    $row = &$fileData[$boardGameResource->id];
    $row[1] = $boardGameResource->name;
    $row[2] = $boardGameResource->shortDescription;
    $handle = fopen($this->dataDir . '/board_games.csv', 'wb');
    foreach ($fileData as $data) {
    fputcsv($handle, $data);

    View full-size slide

  212. use Sylius\Resource\State\ProcessorInterface;
    final class UpdateBoardGameProcessor implements ProcessorInterface
    // [...]
    {
    public function process(mixed $data, Operation $operation, Context $context): mixed
    {
    Assert::isInstanceOf($data, BoardGameResource::class);
    $this->updateBoardGame($data);
    return null;
    }
    private function updateBoardGame(BoardGameResource $boardGameResource): void
    {
    $fileData = $this->getFileData();
    $row = &$fileData[$boardGameResource->id];
    $row[1] = $boardGameResource->name;
    $row[2] = $boardGameResource->shortDescription;
    $handle = fopen($this->dataDir . '/board_games.csv', 'wb');
    foreach ($fileData as $data) {
    fputcsv($handle, $data);

    View full-size slide

  213. public function process(mixed $data, Operation $operation, Context $context): mixed
    // [...]
    use Sylius\Resource\State\ProcessorInterface;
    final class UpdateBoardGameProcessor implements ProcessorInterface
    {
    {
    Assert::isInstanceOf($data, BoardGameResource::class);
    $this->updateBoardGame($data);
    return null;
    }
    private function updateBoardGame(BoardGameResource $boardGameResource): void
    {
    $fileData = $this->getFileData();
    $row = &$fileData[$boardGameResource->id];
    $row[1] = $boardGameResource->name;
    $row[2] = $boardGameResource->shortDescription;
    $handle = fopen($this->dataDir . '/board_games.csv', 'wb');
    foreach ($fileData as $data) {
    fputcsv($handle, $data);

    View full-size slide

  214. Assert::isInstanceOf($data, BoardGameResource::class);
    // [...]
    use Sylius\Resource\State\ProcessorInterface;
    final class UpdateBoardGameProcessor implements ProcessorInterface
    {
    public function process(mixed $data, Operation $operation, Context $context): mixed
    {
    $this->updateBoardGame($data);
    return null;
    }
    private function updateBoardGame(BoardGameResource $boardGameResource): void
    {
    $fileData = $this->getFileData();
    $row = &$fileData[$boardGameResource->id];
    $row[1] = $boardGameResource->name;
    $row[2] = $boardGameResource->shortDescription;
    $handle = fopen($this->dataDir . '/board_games.csv', 'wb');
    foreach ($fileData as $data) {
    fputcsv($handle, $data);

    View full-size slide

  215. $this->updateBoardGame($data);
    // [...]
    use Sylius\Resource\State\ProcessorInterface;
    final class UpdateBoardGameProcessor implements ProcessorInterface
    {
    public function process(mixed $data, Operation $operation, Context $context): mixed
    {
    Assert::isInstanceOf($data, BoardGameResource::class);
    return null;
    }
    private function updateBoardGame(BoardGameResource $boardGameResource): void
    {
    $fileData = $this->getFileData();
    $row = &$fileData[$boardGameResource->id];
    $row[1] = $boardGameResource->name;
    $row[2] = $boardGameResource->shortDescription;
    $handle = fopen($this->dataDir . '/board_games.csv', 'wb');
    foreach ($fileData as $data) {
    fputcsv($handle, $data);

    View full-size slide

  216. $this->updateBoardGame($data);
    private function updateBoardGame(BoardGameResource $boardGameResource): void
    {
    $fileData = $this->getFileData();
    $row = &$fileData[$boardGameResource->id];
    $row[1] = $boardGameResource->name;
    $row[2] = $boardGameResource->shortDescription;
    $handle = fopen($this->dataDir . '/board_games.csv', 'wb');
    foreach ($fileData as $data) {
    fputcsv($handle, $data);
    // [...]
    use Sylius\Resource\State\ProcessorInterface;
    final class UpdateBoardGameProcessor implements ProcessorInterface
    {
    public function process(mixed $data, Operation $operation, Context $context): mixed
    {
    Assert::isInstanceOf($data, BoardGameResource::class);
    return null;
    }

    View full-size slide

  217. $fileData = $this->getFileData();
    $row = &$fileData[$boardGameResource->id];
    // [...]
    use Sylius\Resource\State\ProcessorInterface;
    final class UpdateBoardGameProcessor implements ProcessorInterface
    {
    public function process(mixed $data, Operation $operation, Context $context): mixed
    {
    Assert::isInstanceOf($data, BoardGameResource::class);
    $this->updateBoardGame($data);
    return null;
    }
    private function updateBoardGame(BoardGameResource $boardGameResource): void
    {
    $row[1] = $boardGameResource->name;
    $row[2] = $boardGameResource->shortDescription;
    $handle = fopen($this->dataDir . '/board_games.csv', 'wb');
    foreach ($fileData as $data) {
    fputcsv($handle, $data);

    View full-size slide

  218. $row[1] = $boardGameResource->name;
    $row[2] = $boardGameResource->shortDescription;
    // [...]
    use Sylius\Resource\State\ProcessorInterface;
    final class UpdateBoardGameProcessor implements ProcessorInterface
    {
    public function process(mixed $data, Operation $operation, Context $context): mixed
    {
    Assert::isInstanceOf($data, BoardGameResource::class);
    $this->updateBoardGame($data);
    return null;
    }
    private function updateBoardGame(BoardGameResource $boardGameResource): void
    {
    $fileData = $this->getFileData();
    $row = &$fileData[$boardGameResource->id];
    $handle = fopen($this->dataDir . '/board_games.csv', 'wb');
    foreach ($fileData as $data) {
    fputcsv($handle, $data);

    View full-size slide

  219. $handle = fopen($this->dataDir . '/board_games.csv', 'wb');
    foreach ($fileData as $data) {
    fputcsv($handle, $data);
    // [...]
    use Sylius\Resource\State\ProcessorInterface;
    final class UpdateBoardGameProcessor implements ProcessorInterface
    {
    public function process(mixed $data, Operation $operation, Context $context): mixed
    {
    Assert::isInstanceOf($data, BoardGameResource::class);
    $this->updateBoardGame($data);
    return null;
    }
    private function updateBoardGame(BoardGameResource $boardGameResource): void
    {
    $fileData = $this->getFileData();
    $row = &$fileData[$boardGameResource->id];
    $row[1] = $boardGameResource->name;
    $row[2] = $boardGameResource->shortDescription;

    View full-size slide

  220. Deleting board games

    View full-size slide

  221. Deleting board games
    namespace App\BoardGameBlog\Infrastructure\Sylius\Resource;
    // [...]
    use App\BoardGameBlog\Infrastructure\Sylius\State\Processor\DeleteBoardGameProcessor;
    use App\BoardGameBlog\Infrastructure\Sylius\State\Provider\BoardGameItemProvider;
    use Sylius\Resource\Metadata\Delete;
    // [...]
    #[Delete(
    provider: BoardGameItemProvider::class,
    processor: DeleteBoardGameProcessor::class,
    )]
    final class BoardGameResource implements ResourceInterface
    {
    // [...]
    }

    View full-size slide

  222. Deleting board games
    #[Delete(
    namespace App\BoardGameBlog\Infrastructure\Sylius\Resource;
    // [...]
    use App\BoardGameBlog\Infrastructure\Sylius\State\Processor\DeleteBoardGameProcessor;
    use App\BoardGameBlog\Infrastructure\Sylius\State\Provider\BoardGameItemProvider;
    use Sylius\Resource\Metadata\Delete;
    // [...]
    provider: BoardGameItemProvider::class,
    processor: DeleteBoardGameProcessor::class,
    )]
    final class BoardGameResource implements ResourceInterface
    {
    // [...]
    }

    View full-size slide

  223. Deleting board games
    use Sylius\Resource\Metadata\Delete;
    #[Delete(
    namespace App\BoardGameBlog\Infrastructure\Sylius\Resource;
    // [...]
    use App\BoardGameBlog\Infrastructure\Sylius\State\Processor\DeleteBoardGameProcessor;
    use App\BoardGameBlog\Infrastructure\Sylius\State\Provider\BoardGameItemProvider;
    // [...]
    provider: BoardGameItemProvider::class,
    processor: DeleteBoardGameProcessor::class,
    )]
    final class BoardGameResource implements ResourceInterface
    {
    // [...]
    }

    View full-size slide

  224. Deleting board games
    provider: BoardGameItemProvider::class,
    namespace App\BoardGameBlog\Infrastructure\Sylius\Resource;
    // [...]
    use App\BoardGameBlog\Infrastructure\Sylius\State\Processor\DeleteBoardGameProcessor;
    use App\BoardGameBlog\Infrastructure\Sylius\State\Provider\BoardGameItemProvider;
    use Sylius\Resource\Metadata\Delete;
    // [...]
    #[Delete(
    processor: DeleteBoardGameProcessor::class,
    )]
    final class BoardGameResource implements ResourceInterface
    {
    // [...]
    }

    View full-size slide

  225. Deleting board games
    use App\BoardGameBlog\Infrastructure\Sylius\State\Provider\BoardGameItemProvider;
    provider: BoardGameItemProvider::class,
    namespace App\BoardGameBlog\Infrastructure\Sylius\Resource;
    // [...]
    use App\BoardGameBlog\Infrastructure\Sylius\State\Processor\DeleteBoardGameProcessor;
    use Sylius\Resource\Metadata\Delete;
    // [...]
    #[Delete(
    processor: DeleteBoardGameProcessor::class,
    )]
    final class BoardGameResource implements ResourceInterface
    {
    // [...]
    }

    View full-size slide

  226. Deleting board games
    processor: DeleteBoardGameProcessor::class,
    namespace App\BoardGameBlog\Infrastructure\Sylius\Resource;
    // [...]
    use App\BoardGameBlog\Infrastructure\Sylius\State\Processor\DeleteBoardGameProcessor;
    use App\BoardGameBlog\Infrastructure\Sylius\State\Provider\BoardGameItemProvider;
    use Sylius\Resource\Metadata\Delete;
    // [...]
    #[Delete(
    provider: BoardGameItemProvider::class,
    )]
    final class BoardGameResource implements ResourceInterface
    {
    // [...]
    }

    View full-size slide

  227. Deleting board games
    use App\BoardGameBlog\Infrastructure\Sylius\State\Processor\DeleteBoardGameProcessor;
    processor: DeleteBoardGameProcessor::class,
    namespace App\BoardGameBlog\Infrastructure\Sylius\Resource;
    // [...]
    use App\BoardGameBlog\Infrastructure\Sylius\State\Provider\BoardGameItemProvider;
    use Sylius\Resource\Metadata\Delete;
    // [...]
    #[Delete(
    provider: BoardGameItemProvider::class,
    )]
    final class BoardGameResource implements ResourceInterface
    {
    // [...]
    }

    View full-size slide

  228. // [...]
    use Sylius\Resource\State\ProcessorInterface;
    final class DeleteBoardGameProcessor implements ProcessorInterface
    {
    public function process(mixed $data, Operation $operation, Context $context): mixed
    {
    Assert::isInstanceOf($data, BoardGameResource::class);
    $this->deleteBoardGame($data);
    return null;
    }
    private function deleteBoardGame(BoardGameResource $boardGameResource): void
    {
    $fileData = $this->getFileData();
    unset($fileData[$boardGameResource->id]);
    $handle = fopen($this->dataDir . '/board_games.csv', 'wb');
    foreach ($fileData as $data) {
    fputcsv($handle, $data);
    }
    fclose($handle);

    View full-size slide

  229. final class DeleteBoardGameProcessor implements ProcessorInterface
    // [...]
    use Sylius\Resource\State\ProcessorInterface;
    {
    public function process(mixed $data, Operation $operation, Context $context): mixed
    {
    Assert::isInstanceOf($data, BoardGameResource::class);
    $this->deleteBoardGame($data);
    return null;
    }
    private function deleteBoardGame(BoardGameResource $boardGameResource): void
    {
    $fileData = $this->getFileData();
    unset($fileData[$boardGameResource->id]);
    $handle = fopen($this->dataDir . '/board_games.csv', 'wb');
    foreach ($fileData as $data) {
    fputcsv($handle, $data);
    }
    fclose($handle);

    View full-size slide

  230. use Sylius\Resource\State\ProcessorInterface;
    final class DeleteBoardGameProcessor implements ProcessorInterface
    // [...]
    {
    public function process(mixed $data, Operation $operation, Context $context): mixed
    {
    Assert::isInstanceOf($data, BoardGameResource::class);
    $this->deleteBoardGame($data);
    return null;
    }
    private function deleteBoardGame(BoardGameResource $boardGameResource): void
    {
    $fileData = $this->getFileData();
    unset($fileData[$boardGameResource->id]);
    $handle = fopen($this->dataDir . '/board_games.csv', 'wb');
    foreach ($fileData as $data) {
    fputcsv($handle, $data);
    }
    fclose($handle);

    View full-size slide

  231. public function process(mixed $data, Operation $operation, Context $context): mixed
    // [...]
    use Sylius\Resource\State\ProcessorInterface;
    final class DeleteBoardGameProcessor implements ProcessorInterface
    {
    {
    Assert::isInstanceOf($data, BoardGameResource::class);
    $this->deleteBoardGame($data);
    return null;
    }
    private function deleteBoardGame(BoardGameResource $boardGameResource): void
    {
    $fileData = $this->getFileData();
    unset($fileData[$boardGameResource->id]);
    $handle = fopen($this->dataDir . '/board_games.csv', 'wb');
    foreach ($fileData as $data) {
    fputcsv($handle, $data);
    }
    fclose($handle);

    View full-size slide

  232. Assert::isInstanceOf($data, BoardGameResource::class);
    // [...]
    use Sylius\Resource\State\ProcessorInterface;
    final class DeleteBoardGameProcessor implements ProcessorInterface
    {
    public function process(mixed $data, Operation $operation, Context $context): mixed
    {
    $this->deleteBoardGame($data);
    return null;
    }
    private function deleteBoardGame(BoardGameResource $boardGameResource): void
    {
    $fileData = $this->getFileData();
    unset($fileData[$boardGameResource->id]);
    $handle = fopen($this->dataDir . '/board_games.csv', 'wb');
    foreach ($fileData as $data) {
    fputcsv($handle, $data);
    }
    fclose($handle);

    View full-size slide

  233. $this->deleteBoardGame($data);
    // [...]
    use Sylius\Resource\State\ProcessorInterface;
    final class DeleteBoardGameProcessor implements ProcessorInterface
    {
    public function process(mixed $data, Operation $operation, Context $context): mixed
    {
    Assert::isInstanceOf($data, BoardGameResource::class);
    return null;
    }
    private function deleteBoardGame(BoardGameResource $boardGameResource): void
    {
    $fileData = $this->getFileData();
    unset($fileData[$boardGameResource->id]);
    $handle = fopen($this->dataDir . '/board_games.csv', 'wb');
    foreach ($fileData as $data) {
    fputcsv($handle, $data);
    }
    fclose($handle);

    View full-size slide

  234. $this->deleteBoardGame($data);
    private function deleteBoardGame(BoardGameResource $boardGameResource): void
    {
    $fileData = $this->getFileData();
    unset($fileData[$boardGameResource->id]);
    $handle = fopen($this->dataDir . '/board_games.csv', 'wb');
    foreach ($fileData as $data) {
    fputcsv($handle, $data);
    }
    fclose($handle);
    // [...]
    use Sylius\Resource\State\ProcessorInterface;
    final class DeleteBoardGameProcessor implements ProcessorInterface
    {
    public function process(mixed $data, Operation $operation, Context $context): mixed
    {
    Assert::isInstanceOf($data, BoardGameResource::class);
    return null;
    }

    View full-size slide

  235. $fileData = $this->getFileData();
    unset($fileData[$boardGameResource->id]);
    // [...]
    use Sylius\Resource\State\ProcessorInterface;
    final class DeleteBoardGameProcessor implements ProcessorInterface
    {
    public function process(mixed $data, Operation $operation, Context $context): mixed
    {
    Assert::isInstanceOf($data, BoardGameResource::class);
    $this->deleteBoardGame($data);
    return null;
    }
    private function deleteBoardGame(BoardGameResource $boardGameResource): void
    {
    $handle = fopen($this->dataDir . '/board_games.csv', 'wb');
    foreach ($fileData as $data) {
    fputcsv($handle, $data);
    }
    fclose($handle);

    View full-size slide

  236. $handle = fopen($this->dataDir . '/board_games.csv', 'wb');
    foreach ($fileData as $data) {
    fputcsv($handle, $data);
    }
    fclose($handle);
    // [...]
    use Sylius\Resource\State\ProcessorInterface;
    final class DeleteBoardGameProcessor implements ProcessorInterface
    {
    public function process(mixed $data, Operation $operation, Context $context): mixed
    {
    Assert::isInstanceOf($data, BoardGameResource::class);
    $this->deleteBoardGame($data);
    return null;
    }
    private function deleteBoardGame(BoardGameResource $boardGameResource): void
    {
    $fileData = $this->getFileData();
    unset($fileData[$boardGameResource->id]);

    View full-size slide

  237. Sorting board games

    View full-size slide

  238. Sorting board games
    final class BoardGameGrid extends AbstractGrid implements ResourceAwareGridInterface
    {
    // [...]
    public function buildGrid(GridBuilderInterface $gridBuilder): void
    {
    $gridBuilder
    ->setProvider(BoardGameGridProvider::class)
    ->orderBy('name', 'asc')
    ->addField(
    StringField::create('name')
    ->setLabel('Name')
    ->setSortable(true),
    )
    // [...]
    ;
    }
    // [...]
    }

    View full-size slide

  239. Sorting board games
    ->addField(
    StringField::create('name')
    ->setLabel('Name')
    ->setSortable(true),
    )
    final class BoardGameGrid extends AbstractGrid implements ResourceAwareGridInterface
    {
    // [...]
    public function buildGrid(GridBuilderInterface $gridBuilder): void
    {
    $gridBuilder
    ->setProvider(BoardGameGridProvider::class)
    ->orderBy('name', 'asc')
    // [...]
    ;
    }
    // [...]
    }

    View full-size slide

  240. Sorting board games
    ->setSortable(true),
    final class BoardGameGrid extends AbstractGrid implements ResourceAwareGridInterface
    {
    // [...]
    public function buildGrid(GridBuilderInterface $gridBuilder): void
    {
    $gridBuilder
    ->setProvider(BoardGameGridProvider::class)
    ->orderBy('name', 'asc')
    ->addField(
    StringField::create('name')
    ->setLabel('Name')
    )
    // [...]
    ;
    }
    // [...]
    }

    View full-size slide

  241. Sorting board games
    ->orderBy('name', 'asc')
    final class BoardGameGrid extends AbstractGrid implements ResourceAwareGridInterface
    {
    // [...]
    public function buildGrid(GridBuilderInterface $gridBuilder): void
    {
    $gridBuilder
    ->setProvider(BoardGameGridProvider::class)
    ->addField(
    StringField::create('name')
    ->setLabel('Name')
    ->setSortable(true),
    )
    // [...]
    ;
    }
    // [...]
    }

    View full-size slide

  242. final class BoardGameGridProvider implements DataProviderInterface
    {
    public function getData(Grid $grid, Parameters $parameters): Pagerfanta
    {
    $data = [];
    $fileData = $this->getFileData();
    $sorting = $parameters->get('sorting') ?? [];
    $fileData = $this->sortData($fileData, $sorting);
    // [...]
    }
    private function sortData(array $data, array $sorting): array
    {
    if ('asc' === ($sorting['name'] ?? null)) {
    usort($data, [$this, 'sortByNameAsc']);
    }
    if ('desc' === ($sorting['name'] ?? null)) {
    usort($data, [$this, 'sortByNameDesc']);
    }
    return $data;

    View full-size slide

  243. $sorting = $parameters->get('sorting') ?? [];
    final class BoardGameGridProvider implements DataProviderInterface
    {
    public function getData(Grid $grid, Parameters $parameters): Pagerfanta
    {
    $data = [];
    $fileData = $this->getFileData();
    $fileData = $this->sortData($fileData, $sorting);
    // [...]
    }
    private function sortData(array $data, array $sorting): array
    {
    if ('asc' === ($sorting['name'] ?? null)) {
    usort($data, [$this, 'sortByNameAsc']);
    }
    if ('desc' === ($sorting['name'] ?? null)) {
    usort($data, [$this, 'sortByNameDesc']);
    }
    return $data;

    View full-size slide

  244. $fileData = $this->sortData($fileData, $sorting);
    final class BoardGameGridProvider implements DataProviderInterface
    {
    public function getData(Grid $grid, Parameters $parameters): Pagerfanta
    {
    $data = [];
    $fileData = $this->getFileData();
    $sorting = $parameters->get('sorting') ?? [];
    // [...]
    }
    private function sortData(array $data, array $sorting): array
    {
    if ('asc' === ($sorting['name'] ?? null)) {
    usort($data, [$this, 'sortByNameAsc']);
    }
    if ('desc' === ($sorting['name'] ?? null)) {
    usort($data, [$this, 'sortByNameDesc']);
    }
    return $data;

    View full-size slide

  245. $fileData = $this->sortData($fileData, $sorting);
    private function sortData(array $data, array $sorting): array
    {
    if ('asc' === ($sorting['name'] ?? null)) {
    usort($data, [$this, 'sortByNameAsc']);
    }
    if ('desc' === ($sorting['name'] ?? null)) {
    usort($data, [$this, 'sortByNameDesc']);
    }
    return $data;
    final class BoardGameGridProvider implements DataProviderInterface
    {
    public function getData(Grid $grid, Parameters $parameters): Pagerfanta
    {
    $data = [];
    $fileData = $this->getFileData();
    $sorting = $parameters->get('sorting') ?? [];
    // [...]
    }

    View full-size slide

  246. final class BoardGameGridProvider implements DataProviderInterface
    {
    // [...]
    private function sortData(array $data, array $sorting): array
    {
    if ('asc' === ($sorting['name'] ?? null)) {
    usort($data, [$this, 'sortByNameAsc']);
    }
    if ('desc' === ($sorting['name'] ?? null)) {
    usort($data, [$this, 'sortByNameDesc']);
    }
    return $data;
    }
    private function sortByNameAsc($a, $b): int
    {
    return strcmp($a[1], $b[1]);
    }
    private function sortByNameDesc($a, $b): int
    {
    return strcmp($b[1], $a[1]);
    }

    View full-size slide

  247. if ('asc' === ($sorting['name'] ?? null)) {
    usort($data, [$this, 'sortByNameAsc']);
    }
    final class BoardGameGridProvider implements DataProviderInterface
    {
    // [...]
    private function sortData(array $data, array $sorting): array
    {
    if ('desc' === ($sorting['name'] ?? null)) {
    usort($data, [$this, 'sortByNameDesc']);
    }
    return $data;
    }
    private function sortByNameAsc($a, $b): int
    {
    return strcmp($a[1], $b[1]);
    }
    private function sortByNameDesc($a, $b): int
    {
    return strcmp($b[1], $a[1]);
    }

    View full-size slide

  248. if ('asc' === ($sorting['name'] ?? null)) {
    usort($data, [$this, 'sortByNameAsc']);
    }
    private function sortByNameAsc($a, $b): int
    {
    return strcmp($a[1], $b[1]);
    }
    final class BoardGameGridProvider implements DataProviderInterface
    {
    // [...]
    private function sortData(array $data, array $sorting): array
    {
    if ('desc' === ($sorting['name'] ?? null)) {
    usort($data, [$this, 'sortByNameDesc']);
    }
    return $data;
    }
    private function sortByNameDesc($a, $b): int
    {
    return strcmp($b[1], $a[1]);
    }

    View full-size slide

  249. if ('desc' === ($sorting['name'] ?? null)) {
    usort($data, [$this, 'sortByNameDesc']);
    }
    final class BoardGameGridProvider implements DataProviderInterface
    {
    // [...]
    private function sortData(array $data, array $sorting): array
    {
    if ('asc' === ($sorting['name'] ?? null)) {
    usort($data, [$this, 'sortByNameAsc']);
    }
    return $data;
    }
    private function sortByNameAsc($a, $b): int
    {
    return strcmp($a[1], $b[1]);
    }
    private function sortByNameDesc($a, $b): int
    {
    return strcmp($b[1], $a[1]);
    }

    View full-size slide

  250. if ('desc' === ($sorting['name'] ?? null)) {
    usort($data, [$this, 'sortByNameDesc']);
    }
    private function sortByNameDesc($a, $b): int
    {
    return strcmp($b[1], $a[1]);
    }
    final class BoardGameGridProvider implements DataProviderInterface
    {
    // [...]
    private function sortData(array $data, array $sorting): array
    {
    if ('asc' === ($sorting['name'] ?? null)) {
    usort($data, [$this, 'sortByNameAsc']);
    }
    return $data;
    }
    private function sortByNameAsc($a, $b): int
    {
    return strcmp($a[1], $b[1]);
    }

    View full-size slide

  251. Default sorting
    Is it sorted by name?

    View full-size slide

  252. final class BoardGameGridProvider implements DataProviderInterface
    {
    public function getData(Grid $grid, Parameters $parameters): Pagerfanta
    {
    $data = [];
    $fileData = $this->getFileData();
    $sorting = $parameters->get('sorting') ?? [];
    $fileData = $this->sortData($fileData, $sorting);
    // [...]
    }
    // [...]
    }

    View full-size slide

  253. $sorting = $parameters->get('sorting') ?? [];
    final class BoardGameGridProvider implements DataProviderInterface
    {
    public function getData(Grid $grid, Parameters $parameters): Pagerfanta
    {
    $data = [];
    $fileData = $this->getFileData();
    $fileData = $this->sortData($fileData, $sorting);
    // [...]
    }
    // [...]
    }

    View full-size slide

  254. $sorting = $parameters->get('sorting') ?? $grid->getSorting();
    final class BoardGameGridProvider implements DataProviderInterface
    {
    public function getData(Grid $grid, Parameters $parameters): Pagerfanta
    {
    $data = [];
    $fileData = $this->getFileData();
    $fileData = $this->sortData($fileData, $sorting);
    // [...]
    }
    // [...]
    }

    View full-size slide

  255. What’s next?

    View full-size slide

  256. What’s next?
    Stable release

    View full-size slide

  257. What’s next?
    Stable release
    Reunite bundle & component into same package

    View full-size slide

  258. What’s next?
    Stable release
    Reunite bundle & component into same package
    Remove Kernel events (WIP thx to Vasilvestre)

    View full-size slide

  259. What’s next?
    Stable release
    Reunite bundle & component into same package
    Remove Kernel events (WIP thx to Vasilvestre)
    PHP configuration

    View full-size slide

  260. Reunite bundle & component into same package
    Before
    After
    Sylius\Bundle\ResourceBundle
    Sylius\Component\Resource
    Sylius\Resource

    View full-size slide

  261. Kernel Events

    View full-size slide

  262. PHP configuration
    Open-source cooperation

    View full-size slide

  263. Resource configurator
    // config/package/sylius_resource.php
    namespace Symfony\Component\DependencyInjection\Loader\Configurator;
    use App\Entity\Book;
    use App\Entity\Subscription;
    use Sylius\Component\Resource\Metadata\ResourceMetadata;
    use Sylius\Component\Loader\Configuration\ResourceConfigurator;
    return static function (ResourceConfigurator $resourceConfigurator): void {
    $resourceConfigurator
    ->withResource((new ResourceMetadata(Book::class))
    ->withRoutePrefix('admin')
    ->withOperations([
    new Index(),
    new Create(),
    new Update(),
    new Delete(),
    ])
    )
    ->withResource((new ResourceMetadata(Subscription::class))
    ->withOperations([
    new Index(),
    ])
    )

    View full-size slide

  264. Resource configurator
    return static function (ResourceConfigurator $resourceConfigurator): void {
    // config/package/sylius_resource.php
    namespace Symfony\Component\DependencyInjection\Loader\Configurator;
    use App\Entity\Book;
    use App\Entity\Subscription;
    use Sylius\Component\Resource\Metadata\ResourceMetadata;
    use Sylius\Component\Loader\Configuration\ResourceConfigurator;
    $resourceConfigurator
    ->withResource((new ResourceMetadata(Book::class))
    ->withRoutePrefix('admin')
    ->withOperations([
    new Index(),
    new Create(),
    new Update(),
    new Delete(),
    ])
    )
    ->withResource((new ResourceMetadata(Subscription::class))
    ->withOperations([
    new Index(),
    ])
    )

    View full-size slide

  265. Resource configurator
    use Sylius\Component\Loader\Configuration\ResourceConfigurator;
    return static function (ResourceConfigurator $resourceConfigurator): void {
    // config/package/sylius_resource.php
    namespace Symfony\Component\DependencyInjection\Loader\Configurator;
    use App\Entity\Book;
    use App\Entity\Subscription;
    use Sylius\Component\Resource\Metadata\ResourceMetadata;
    $resourceConfigurator
    ->withResource((new ResourceMetadata(Book::class))
    ->withRoutePrefix('admin')
    ->withOperations([
    new Index(),
    new Create(),
    new Update(),
    new Delete(),
    ])
    )
    ->withResource((new ResourceMetadata(Subscription::class))
    ->withOperations([
    new Index(),
    ])
    )

    View full-size slide

  266. Resource configurator
    ->withResource((new ResourceMetadata(Book::class))
    // config/package/sylius_resource.php
    namespace Symfony\Component\DependencyInjection\Loader\Configurator;
    use App\Entity\Book;
    use App\Entity\Subscription;
    use Sylius\Component\Resource\Metadata\ResourceMetadata;
    use Sylius\Component\Loader\Configuration\ResourceConfigurator;
    return static function (ResourceConfigurator $resourceConfigurator): void {
    $resourceConfigurator
    ->withRoutePrefix('admin')
    ->withOperations([
    new Index(),
    new Create(),
    new Update(),
    new Delete(),
    ])
    )
    ->withResource((new ResourceMetadata(Subscription::class))
    ->withOperations([
    new Index(),
    ])
    )

    View full-size slide

  267. Resource configurator
    use Sylius\Component\Resource\Metadata\ResourceMetadata;
    ->withResource((new ResourceMetadata(Book::class))
    // config/package/sylius_resource.php
    namespace Symfony\Component\DependencyInjection\Loader\Configurator;
    use App\Entity\Book;
    use App\Entity\Subscription;
    use Sylius\Component\Loader\Configuration\ResourceConfigurator;
    return static function (ResourceConfigurator $resourceConfigurator): void {
    $resourceConfigurator
    ->withRoutePrefix('admin')
    ->withOperations([
    new Index(),
    new Create(),
    new Update(),
    new Delete(),
    ])
    )
    ->withResource((new ResourceMetadata(Subscription::class))
    ->withOperations([
    new Index(),
    ])
    )

    View full-size slide

  268. Resource configurator
    ->withRoutePrefix('admin')
    // config/package/sylius_resource.php
    namespace Symfony\Component\DependencyInjection\Loader\Configurator;
    use App\Entity\Book;
    use App\Entity\Subscription;
    use Sylius\Component\Resource\Metadata\ResourceMetadata;
    use Sylius\Component\Loader\Configuration\ResourceConfigurator;
    return static function (ResourceConfigurator $resourceConfigurator): void {
    $resourceConfigurator
    ->withResource((new ResourceMetadata(Book::class))
    ->withOperations([
    new Index(),
    new Create(),
    new Update(),
    new Delete(),
    ])
    )
    ->withResource((new ResourceMetadata(Subscription::class))
    ->withOperations([
    new Index(),
    ])
    )

    View full-size slide

  269. Resource configurator
    ->withOperations([
    new Index(),
    new Create(),
    new Update(),
    new Delete(),
    ])
    // config/package/sylius_resource.php
    namespace Symfony\Component\DependencyInjection\Loader\Configurator;
    use App\Entity\Book;
    use App\Entity\Subscription;
    use Sylius\Component\Resource\Metadata\ResourceMetadata;
    use Sylius\Component\Loader\Configuration\ResourceConfigurator;
    return static function (ResourceConfigurator $resourceConfigurator): void {
    $resourceConfigurator
    ->withResource((new ResourceMetadata(Book::class))
    ->withRoutePrefix('admin')
    )
    ->withResource((new ResourceMetadata(Subscription::class))
    ->withOperations([
    new Index(),
    ])
    )

    View full-size slide

  270. Resource configurator
    ->withResource((new ResourceMetadata(Subscription::class))
    // config/package/sylius_resource.php
    namespace Symfony\Component\DependencyInjection\Loader\Configurator;
    use App\Entity\Book;
    use App\Entity\Subscription;
    use Sylius\Component\Resource\Metadata\ResourceMetadata;
    use Sylius\Component\Loader\Configuration\ResourceConfigurator;
    return static function (ResourceConfigurator $resourceConfigurator): void {
    $resourceConfigurator
    ->withResource((new ResourceMetadata(Book::class))
    ->withRoutePrefix('admin')
    ->withOperations([
    new Index(),
    new Create(),
    new Update(),
    new Delete(),
    ])
    )
    ->withOperations([
    new Index(),
    ])
    )

    View full-size slide

  271. Resource configurator
    ->withOperations([
    new Index(),
    ])
    // config/package/sylius_resource.php
    namespace Symfony\Component\DependencyInjection\Loader\Configurator;
    use App\Entity\Book;
    use App\Entity\Subscription;
    use Sylius\Component\Resource\Metadata\ResourceMetadata;
    use Sylius\Component\Loader\Configuration\ResourceConfigurator;
    return static function (ResourceConfigurator $resourceConfigurator): void {
    $resourceConfigurator
    ->withResource((new ResourceMetadata(Book::class))
    ->withRoutePrefix('admin')
    ->withOperations([
    new Index(),
    new Create(),
    new Update(),
    new Delete(),
    ])
    )
    ->withResource((new ResourceMetadata(Subscription::class))
    )

    View full-size slide

  272. // config/api_platform/resources.php
    namespace Symfony\Component\DependencyInjection\Loader\Configurator;
    use App\Entity\Book;
    use App\Entity\Subscription;
    use ApiPlatform\Metadata\ApiResource;
    use ApiPlatorm\Loader\Configuration\ApiResourceConfigurator;
    return static function (ApiResourceConfigurator $resourceConfigurator): void {
    $resourceConfigurator
    ->withResource((new ApiResource(Book::class))
    ->withRoutePrefix('admin')
    ->withOperations([
    new GetCollection(),
    new Post(),
    new Put(),
    new Delete(),
    ])
    )
    ;
    };

    View full-size slide

  273. return static function (ApiResourceConfigurator $resourceConfigurator): void {
    // config/api_platform/resources.php
    namespace Symfony\Component\DependencyInjection\Loader\Configurator;
    use App\Entity\Book;
    use App\Entity\Subscription;
    use ApiPlatform\Metadata\ApiResource;
    use ApiPlatorm\Loader\Configuration\ApiResourceConfigurator;
    $resourceConfigurator
    ->withResource((new ApiResource(Book::class))
    ->withRoutePrefix('admin')
    ->withOperations([
    new GetCollection(),
    new Post(),
    new Put(),
    new Delete(),
    ])
    )
    ;
    };

    View full-size slide

  274. use ApiPlatorm\Loader\Configuration\ApiResourceConfigurator;
    return static function (ApiResourceConfigurator $resourceConfigurator): void {
    // config/api_platform/resources.php
    namespace Symfony\Component\DependencyInjection\Loader\Configurator;
    use App\Entity\Book;
    use App\Entity\Subscription;
    use ApiPlatform\Metadata\ApiResource;
    $resourceConfigurator
    ->withResource((new ApiResource(Book::class))
    ->withRoutePrefix('admin')
    ->withOperations([
    new GetCollection(),
    new Post(),
    new Put(),
    new Delete(),
    ])
    )
    ;
    };

    View full-size slide

  275. ->withResource((new ApiResource(Book::class))
    // config/api_platform/resources.php
    namespace Symfony\Component\DependencyInjection\Loader\Configurator;
    use App\Entity\Book;
    use App\Entity\Subscription;
    use ApiPlatform\Metadata\ApiResource;
    use ApiPlatorm\Loader\Configuration\ApiResourceConfigurator;
    return static function (ApiResourceConfigurator $resourceConfigurator): void {
    $resourceConfigurator
    ->withRoutePrefix('admin')
    ->withOperations([
    new GetCollection(),
    new Post(),
    new Put(),
    new Delete(),
    ])
    )
    ;
    };

    View full-size slide

  276. use ApiPlatform\Metadata\ApiResource;
    ->withResource((new ApiResource(Book::class))
    // config/api_platform/resources.php
    namespace Symfony\Component\DependencyInjection\Loader\Configurator;
    use App\Entity\Book;
    use App\Entity\Subscription;
    use ApiPlatorm\Loader\Configuration\ApiResourceConfigurator;
    return static function (ApiResourceConfigurator $resourceConfigurator): void {
    $resourceConfigurator
    ->withRoutePrefix('admin')
    ->withOperations([
    new GetCollection(),
    new Post(),
    new Put(),
    new Delete(),
    ])
    )
    ;
    };

    View full-size slide

  277. ->withRoutePrefix('admin')
    // config/api_platform/resources.php
    namespace Symfony\Component\DependencyInjection\Loader\Configurator;
    use App\Entity\Book;
    use App\Entity\Subscription;
    use ApiPlatform\Metadata\ApiResource;
    use ApiPlatorm\Loader\Configuration\ApiResourceConfigurator;
    return static function (ApiResourceConfigurator $resourceConfigurator): void {
    $resourceConfigurator
    ->withResource((new ApiResource(Book::class))
    ->withOperations([
    new GetCollection(),
    new Post(),
    new Put(),
    new Delete(),
    ])
    )
    ;
    };

    View full-size slide

  278. ->withOperations([
    new GetCollection(),
    new Post(),
    new Put(),
    new Delete(),
    ])
    // config/api_platform/resources.php
    namespace Symfony\Component\DependencyInjection\Loader\Configurator;
    use App\Entity\Book;
    use App\Entity\Subscription;
    use ApiPlatform\Metadata\ApiResource;
    use ApiPlatorm\Loader\Configuration\ApiResourceConfigurator;
    return static function (ApiResourceConfigurator $resourceConfigurator): void {
    $resourceConfigurator
    ->withResource((new ApiResource(Book::class))
    ->withRoutePrefix('admin')
    )
    ;
    };

    View full-size slide

  279. Now, it's your turn to give it a try.
    @loic_425 @loic425

    View full-size slide