$30 off During Our Annual Pro Sale. View Details »

The impact of API Platform on Open-Source

The impact of API Platform on Open-Source

From API Con 2023

Loïc Frémont

September 23, 2023
Tweet

More Decks by Loïc Frémont

Other Decks in Programming

Transcript

  1. The impact of API Platform on
    Open-Source
    on the new Sylius Resource & Grid bundles

    View Slide

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

    View Slide

  3. 1. The impact of API Platform on
    Open-Source
    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 Slide

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

    View Slide

  5. Akawaka
    Experts for your web
    projects
    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 Slide

  6. Akawaka
    Experts for your web
    projects
    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 Slide

  7. Akawaka
    Experts for your web
    projects
    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 Slide

  8. Akawaka
    Experts for your web
    projects
    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 Slide

  9. What is Monofony and
    Why use it?

    View Slide

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

    View Slide

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

    View 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 Slide

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

    View Slide

  14. View Slide

  15. View Slide

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

    View Slide

  17. View Slide

  18. Resource Bundle

    View Slide

  19. New Sylius Resource System

    View Slide

  20. New Sylius Resource System
    We can thank:

    View Slide

  21. 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
    We can thank:

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  26. Browsing books

    View Slide

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

    View Slide

  28. Browsing books
    We’ll use Index operation which allows to browse all items of your resource.
    ` `
    namespace App\Entity;
    use Sylius\Component\Resource\Metadata\Index;
    use Sylius\Component\Resource\Metadata\Resource;
    use Sylius\Component\Resource\Model\ResourceInterface;
    #[Resource]
    #[Index]
    class Book implements ResourceInterface
    {
    }

    View Slide

  29. Browsing books
    We’ll use Index operation which allows to browse all items of your resource.
    ` `
    #[Index]
    namespace App\Entity;
    use Sylius\Component\Resource\Metadata\Index;
    use Sylius\Component\Resource\Metadata\Resource;
    use Sylius\Component\Resource\Model\ResourceInterface;
    #[Resource]
    class Book implements ResourceInterface
    {
    }

    View Slide

  30. Browsing books
    We’ll use Index operation which allows to browse all items of your resource.
    ` `
    use Sylius\Component\Resource\Metadata\Index;
    #[Index]
    namespace App\Entity;
    use Sylius\Component\Resource\Metadata\Resource;
    use Sylius\Component\Resource\Model\ResourceInterface;
    #[Resource]
    class Book implements ResourceInterface
    {
    }

    View Slide

  31. Route

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  35. 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 Slide

  36. 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 Slide

  37. 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 Slide

  38. ->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 Slide

  39. ->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 Slide

  40. ->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 Slide

  41. Use this grid for your index operation

    View Slide

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

    View Slide

  43. 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\Component\Resource\Metadata\Index;
    use Sylius\Component\Resource\Metadata\Resource;
    use Sylius\Component\Resource\Model\ResourceInterface;
    #[Resource]
    // 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 Slide

  44. 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\Component\Resource\Metadata\Index;
    use Sylius\Component\Resource\Metadata\Resource;
    use Sylius\Component\Resource\Model\ResourceInterface;
    #[Resource]
    // Or you can use the grid name
    #[Index(grid: 'app_book')]
    class Book implements ResourceInterface
    {
    }

    View Slide

  45. 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\Component\Resource\Metadata\Index;
    use Sylius\Component\Resource\Metadata\Resource;
    use Sylius\Component\Resource\Model\ResourceInterface;
    #[Resource]
    // Or you can use the grid name
    #[Index(grid: 'app_book')]
    class Book implements ResourceInterface
    {
    }

    View Slide

  46. 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\Component\Resource\Metadata\Index;
    use Sylius\Component\Resource\Metadata\Resource;
    use Sylius\Component\Resource\Model\ResourceInterface;
    #[Resource]
    // You can use either the FQCN of your grid
    #[Index(grid: BookGrid::class)]
    class Book implements ResourceInterface
    {
    }

    View Slide

  47. 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\Component\Resource\Metadata\Index;
    use Sylius\Component\Resource\Metadata\Resource;
    use Sylius\Component\Resource\Model\ResourceInterface;
    #[Resource]
    // You can use either the FQCN of your grid
    #[Index(grid: BookGrid::class)]
    class Book implements ResourceInterface
    {
    }

    View Slide

  48. View Slide

  49. Adding books

    View Slide

  50. Adding books
    We’ll use Create operation which allows to add a new item of your resource.
    ` `

    View Slide

  51. Adding books
    We’ll use Create operation which allows to add a new item of your resource.
    ` `
    namespace App\Entity;
    use App\Form\BookType;
    use Sylius\Component\Resource\Metadata\Index;
    use Sylius\Component\Resource\Metadata\Create;
    use Sylius\Component\Resource\Metadata\Resource;
    use Sylius\Component\Resource\Model\ResourceInterface;
    #[Resource(formType: BookType::class)]
    #[Create]
    #[Index]
    class Book implements ResourceInterface
    {
    }

    View Slide

  52. Adding books
    We’ll use Create operation which allows to add a new item of your resource.
    ` `
    #[Resource(formType: BookType::class)]
    namespace App\Entity;
    use App\Form\BookType;
    use Sylius\Component\Resource\Metadata\Index;
    use Sylius\Component\Resource\Metadata\Create;
    use Sylius\Component\Resource\Metadata\Resource;
    use Sylius\Component\Resource\Model\ResourceInterface;
    #[Create]
    #[Index]
    class Book implements ResourceInterface
    {
    }

    View Slide

  53. Adding books
    We’ll use Create operation which allows to add a new item of your resource.
    ` `
    use App\Form\BookType;
    #[Resource(formType: BookType::class)]
    namespace App\Entity;
    use Sylius\Component\Resource\Metadata\Index;
    use Sylius\Component\Resource\Metadata\Create;
    use Sylius\Component\Resource\Metadata\Resource;
    use Sylius\Component\Resource\Model\ResourceInterface;
    #[Create]
    #[Index]
    class Book implements ResourceInterface
    {
    }

    View Slide

  54. Adding books
    We’ll use Create operation which allows to add a new item of your resource.
    ` `
    #[Create]
    namespace App\Entity;
    use App\Form\BookType;
    use Sylius\Component\Resource\Metadata\Index;
    use Sylius\Component\Resource\Metadata\Create;
    use Sylius\Component\Resource\Metadata\Resource;
    use Sylius\Component\Resource\Model\ResourceInterface;
    #[Resource(formType: BookType::class)]
    #[Index]
    class Book implements ResourceInterface
    {
    }

    View Slide

  55. Adding books
    We’ll use Create operation which allows to add a new item of your resource.
    ` `
    use Sylius\Component\Resource\Metadata\Create;
    #[Create]
    namespace App\Entity;
    use App\Form\BookType;
    use Sylius\Component\Resource\Metadata\Index;
    use Sylius\Component\Resource\Metadata\Resource;
    use Sylius\Component\Resource\Model\ResourceInterface;
    #[Resource(formType: BookType::class)]
    #[Index]
    class Book implements ResourceInterface
    {
    }

    View Slide

  56. Route

    View Slide

  57. Route
    It will configure this route for your create operation.
    ` `

    View Slide

  58. Route
    It will configure this route for your create operation.
    Name Method Path
    app_book_create GET, POST /books/new
    ` `

    View Slide

  59. 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)
    )
    ->addActionGroup(
    MainActionGroup::create(
    CreateAction::create(),
    )
    )
    ;
    }

    View Slide

  60. ->addActionGroup(
    MainActionGroup::create(
    CreateAction::create(),
    )
    )
    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 Slide

  61. ->addActionGroup(
    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)
    )
    MainActionGroup::create(
    CreateAction::create(),
    )
    )
    ;
    }

    View Slide

  62. MainActionGroup::create(
    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)
    )
    ->addActionGroup(
    CreateAction::create(),
    )
    )
    ;
    }

    View Slide

  63. CreateAction::create(),
    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)
    )
    ->addActionGroup(
    MainActionGroup::create(
    )
    )
    ;
    }

    View Slide

  64. View Slide

  65. View Slide

  66. View Slide

  67. View Slide

  68. View Slide

  69. Editing books

    View Slide

  70. Editing books
    We’ll use Update operation which allows to edit an existing item of your resource.
    ` `

    View Slide

  71. Editing books
    We’ll use Update operation which allows to edit an existing item of your resource.
    ` `
    namespace App\Entity;
    use App\Form\BookType;
    use Sylius\Component\Resource\Metadata\Index;
    use Sylius\Component\Resource\Metadata\Create;
    use Sylius\Component\Resource\Metadata\Resource;
    use Sylius\Component\Resource\Metadata\Update;
    use Sylius\Component\Resource\Model\ResourceInterface;
    #[Resource(formType: BookType::class)]
    #[Create]
    #[Update]
    #[Index]
    class Book implements ResourceInterface
    {
    }

    View Slide

  72. Editing books
    We’ll use Update operation which allows to edit an existing item of your resource.
    ` `
    #[Update]
    namespace App\Entity;
    use App\Form\BookType;
    use Sylius\Component\Resource\Metadata\Index;
    use Sylius\Component\Resource\Metadata\Create;
    use Sylius\Component\Resource\Metadata\Resource;
    use Sylius\Component\Resource\Metadata\Update;
    use Sylius\Component\Resource\Model\ResourceInterface;
    #[Resource(formType: BookType::class)]
    #[Create]
    #[Index]
    class Book implements ResourceInterface
    {
    }

    View Slide

  73. Editing books
    We’ll use Update operation which allows to edit an existing item of your resource.
    ` `
    use Sylius\Component\Resource\Metadata\Update;
    #[Update]
    namespace App\Entity;
    use App\Form\BookType;
    use Sylius\Component\Resource\Metadata\Index;
    use Sylius\Component\Resource\Metadata\Create;
    use Sylius\Component\Resource\Metadata\Resource;
    use Sylius\Component\Resource\Model\ResourceInterface;
    #[Resource(formType: BookType::class)]
    #[Create]
    #[Index]
    class Book implements ResourceInterface
    {
    }

    View Slide

  74. Route

    View Slide

  75. Route
    It will configure this route for your update operation.
    ` `

    View Slide

  76. Route
    It will configure this route for your update operation.
    Name Method Path
    app_book_update GET, PUT, PATCH, POST /books/{id}/edit
    ` `

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  82. View Slide

  83. View Slide

  84. View Slide

  85. View Slide

  86. Removing books

    View Slide

  87. Removing books
    We’ll use Delete operation which allows to remove an existing item of your resource.
    ` `

    View Slide

  88. Removing books
    We’ll use Delete operation which allows to remove an existing item of your resource.
    ` `
    namespace App\Entity;
    use App\Form\BookType;
    use Sylius\Component\Resource\Metadata\Index;
    use Sylius\Component\Resource\Metadata\Create;
    use Sylius\Component\Resource\Metadata\Delete;
    use Sylius\Component\Resource\Metadata\Resource;
    use Sylius\Component\Resource\Metadata\Update;
    use Sylius\Component\Resource\Model\ResourceInterface;
    #[Resource(formType: BookType::class)]
    #[Create]
    #[Update]
    #[Delete]
    #[Index]
    class Book implements ResourceInterface
    {
    }

    View Slide

  89. Removing books
    We’ll use Delete operation which allows to remove an existing item of your resource.
    ` `
    #[Delete]
    namespace App\Entity;
    use App\Form\BookType;
    use Sylius\Component\Resource\Metadata\Index;
    use Sylius\Component\Resource\Metadata\Create;
    use Sylius\Component\Resource\Metadata\Delete;
    use Sylius\Component\Resource\Metadata\Resource;
    use Sylius\Component\Resource\Metadata\Update;
    use Sylius\Component\Resource\Model\ResourceInterface;
    #[Resource(formType: BookType::class)]
    #[Create]
    #[Update]
    #[Index]
    class Book implements ResourceInterface
    {
    }

    View Slide

  90. Removing books
    We’ll use Delete operation which allows to remove an existing item of your resource.
    ` `
    use Sylius\Component\Resource\Metadata\Delete;
    #[Delete]
    namespace App\Entity;
    use App\Form\BookType;
    use Sylius\Component\Resource\Metadata\Index;
    use Sylius\Component\Resource\Metadata\Create;
    use Sylius\Component\Resource\Metadata\Resource;
    use Sylius\Component\Resource\Metadata\Update;
    use Sylius\Component\Resource\Model\ResourceInterface;
    #[Resource(formType: BookType::class)]
    #[Create]
    #[Update]
    #[Index]
    class Book implements ResourceInterface
    {
    }

    View Slide

  91. Route

    View Slide

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

    View Slide

  93. Route
    It will configure this route for your delete operation.
    Name Method Path
    app_book_delete DELETE, POST /books/{id}/delete
    ` `

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  98. View Slide

  99. View Slide

  100. View Slide

  101. Removing many books

    View Slide

  102. Removing many books
    We’ll use Bulk Delete operation which allows to remove several items of your resource at the same time.
    ` `

    View Slide

  103. Removing many books
    We’ll use Bulk Delete operation which allows to remove several items of your resource at the same time.
    ` `
    namespace App\Entity;
    use App\Form\BookType;
    use Sylius\Component\Resource\Metadata\BulkDelete;
    use Sylius\Component\Resource\Metadata\Index;
    use Sylius\Component\Resource\Metadata\Create;
    use Sylius\Component\Resource\Metadata\Delete;
    use Sylius\Component\Resource\Metadata\Resource;
    use Sylius\Component\Resource\Metadata\Update;
    use Sylius\Component\Resource\Model\ResourceInterface;
    #[Resource(formType: BookType::class)]
    #[Create]
    #[Update]
    #[BulkDelete]
    #[Delete]
    #[Index]
    class Book implements ResourceInterface
    {
    }

    View Slide

  104. Removing many books
    We’ll use Bulk Delete operation which allows to remove several items of your resource at the same time.
    ` `
    #[BulkDelete]
    namespace App\Entity;
    use App\Form\BookType;
    use Sylius\Component\Resource\Metadata\BulkDelete;
    use Sylius\Component\Resource\Metadata\Index;
    use Sylius\Component\Resource\Metadata\Create;
    use Sylius\Component\Resource\Metadata\Delete;
    use Sylius\Component\Resource\Metadata\Resource;
    use Sylius\Component\Resource\Metadata\Update;
    use Sylius\Component\Resource\Model\ResourceInterface;
    #[Resource(formType: BookType::class)]
    #[Create]
    #[Update]
    #[Delete]
    #[Index]
    class Book implements ResourceInterface
    {
    }

    View Slide

  105. Removing many books
    We’ll use Bulk Delete operation which allows to remove several items of your resource at the same time.
    ` `
    use Sylius\Component\Resource\Metadata\BulkDelete;
    #[BulkDelete]
    namespace App\Entity;
    use App\Form\BookType;
    use Sylius\Component\Resource\Metadata\Index;
    use Sylius\Component\Resource\Metadata\Create;
    use Sylius\Component\Resource\Metadata\Delete;
    use Sylius\Component\Resource\Metadata\Resource;
    use Sylius\Component\Resource\Metadata\Update;
    use Sylius\Component\Resource\Model\ResourceInterface;
    #[Resource(formType: BookType::class)]
    #[Create]
    #[Update]
    #[Delete]
    #[Index]
    class Book implements ResourceInterface
    {
    }

    View Slide

  106. Route

    View Slide

  107. Route
    It will configure this route for your bulk_delete operation.
    ` `

    View Slide

  108. Route
    It will configure this route for your bulk_delete operation.
    Name Method Path
    app_book_bulk_delete DELETE, POST /books/bulk_delete
    ` `

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  114. View Slide

  115. View Slide

  116. View Slide

  117. View Slide

  118. Publishing books

    View Slide

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

    View Slide

  120. 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\Component\Resource\Metadata\ApplyStateMachineTransition;
    use Sylius\Component\Resource\Model\ResourceInterface;
    // [...]
    #[ApplyStateMachineTransition(stateMachineTransition: 'publish')]
    class Book implements ResourceInterface
    {
    }

    View Slide

  121. 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\Component\Resource\Metadata\ApplyStateMachineTransition;
    use Sylius\Component\Resource\Model\ResourceInterface;
    // [...]
    class Book implements ResourceInterface
    {
    }

    View Slide

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

    View Slide

  123. Route

    View Slide

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

    View Slide

  125. 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 Slide

  126. 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 Slide

  127. ->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 Slide

  128. 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 Slide

  129. ->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 Slide

  130. ->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 Slide




  131. {{ value|trans }}

    View Slide

  132. View Slide

  133. 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 Slide

  134. 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 Slide

  135. 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 Slide

  136. 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 Slide

  137. 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 Slide

  138. 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 Slide

  139. 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 Slide

  140. ->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 Slide

  141. ->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 Slide

  142. View Slide

  143. View Slide

  144. View Slide

  145. Publishing books with custom processor

    View Slide

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

    View Slide

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

    View Slide

  148. 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\Component\Resource\Metadata\Update;
    use Sylius\Component\Resource\Model\ResourceInterface;
    // [...]
    class Book implements ResourceInterface
    {
    }

    View Slide

  149. Publishing books with custom processor
    We configure an update operation.
    ` `
    use Sylius\Component\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\Component\Resource\Model\ResourceInterface;
    // [...]
    class Book implements ResourceInterface
    {
    }

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  155. Route

    View Slide

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

    View Slide

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

    View Slide

  158. // src/State/Processor/PublishBookProcessor.php
    use Sylius\Component\Resource\Context\Context;
    use Sylius\Component\Resource\Doctrine\Common\State\PersistProcessor;
    use Sylius\Component\Resource\Metadata\Operation;
    use Sylius\Component\Resource\State\ProcessorInterface;
    use Symfony\Component\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 Slide

  159. final class PublishBookProcessor implements ProcessorInterface
    // src/State/Processor/PublishBookProcessor.php
    use Sylius\Component\Resource\Context\Context;
    use Sylius\Component\Resource\Doctrine\Common\State\PersistProcessor;
    use Sylius\Component\Resource\Metadata\Operation;
    use Sylius\Component\Resource\State\ProcessorInterface;
    use Symfony\Component\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 Slide

  160. use Sylius\Component\Resource\State\ProcessorInterface;
    final class PublishBookProcessor implements ProcessorInterface
    // src/State/Processor/PublishBookProcessor.php
    use Sylius\Component\Resource\Context\Context;
    use Sylius\Component\Resource\Doctrine\Common\State\PersistProcessor;
    use Sylius\Component\Resource\Metadata\Operation;
    use Symfony\Component\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 Slide

  161. private readonly WorkflowInterface $bookPublishingStateMachine,
    // src/State/Processor/PublishBookProcessor.php
    use Sylius\Component\Resource\Context\Context;
    use Sylius\Component\Resource\Doctrine\Common\State\PersistProcessor;
    use Sylius\Component\Resource\Metadata\Operation;
    use Sylius\Component\Resource\State\ProcessorInterface;
    use Symfony\Component\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 Slide

  162. use Symfony\Component\Workflow\WorkflowInterface;
    private readonly WorkflowInterface $bookPublishingStateMachine,
    // src/State/Processor/PublishBookProcessor.php
    use Sylius\Component\Resource\Context\Context;
    use Sylius\Component\Resource\Doctrine\Common\State\PersistProcessor;
    use Sylius\Component\Resource\Metadata\Operation;
    use Sylius\Component\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 Slide

  163. private readonly PersistProcessor $persistProcessor,
    // src/State/Processor/PublishBookProcessor.php
    use Sylius\Component\Resource\Context\Context;
    use Sylius\Component\Resource\Doctrine\Common\State\PersistProcessor;
    use Sylius\Component\Resource\Metadata\Operation;
    use Sylius\Component\Resource\State\ProcessorInterface;
    use Symfony\Component\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 Slide

  164. use Sylius\Component\Resource\Doctrine\Common\State\PersistProcessor;
    private readonly PersistProcessor $persistProcessor,
    // src/State/Processor/PublishBookProcessor.php
    use Sylius\Component\Resource\Context\Context;
    use Sylius\Component\Resource\Metadata\Operation;
    use Sylius\Component\Resource\State\ProcessorInterface;
    use Symfony\Component\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 Slide

  165. public function process(mixed $data, Operation $operation, Context $context): mixed
    // src/State/Processor/PublishBookProcessor.php
    use Sylius\Component\Resource\Context\Context;
    use Sylius\Component\Resource\Doctrine\Common\State\PersistProcessor;
    use Sylius\Component\Resource\Metadata\Operation;
    use Sylius\Component\Resource\State\ProcessorInterface;
    use Symfony\Component\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 Slide

  166. if ($this->bookPublishingStateMachine->can($data, 'publish')) {
    // src/State/Processor/PublishBookProcessor.php
    use Sylius\Component\Resource\Context\Context;
    use Sylius\Component\Resource\Doctrine\Common\State\PersistProcessor;
    use Sylius\Component\Resource\Metadata\Operation;
    use Sylius\Component\Resource\State\ProcessorInterface;
    use Symfony\Component\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 Slide

  167. $this->bookPublishingStateMachine->apply($data, 'publish');
    // src/State/Processor/PublishBookProcessor.php
    use Sylius\Component\Resource\Context\Context;
    use Sylius\Component\Resource\Doctrine\Common\State\PersistProcessor;
    use Sylius\Component\Resource\Metadata\Operation;
    use Sylius\Component\Resource\State\ProcessorInterface;
    use Symfony\Component\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 Slide

  168. return $this->persistProcessor->process($data, $operation, $context);
    // src/State/Processor/PublishBookProcessor.php
    use Sylius\Component\Resource\Context\Context;
    use Sylius\Component\Resource\Doctrine\Common\State\PersistProcessor;
    use Sylius\Component\Resource\Metadata\Operation;
    use Sylius\Component\Resource\State\ProcessorInterface;
    use Symfony\Component\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 Slide

  169. View Slide

  170. View Slide

  171. View Slide

  172. Publishing many books

    View Slide

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

    View Slide

  174. 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\Component\Resource\Metadata\BulkUpdate;
    // [...]
    #[BulkUpdate(
    shortName: 'bulk_publish',
    processor: PublishBookProcessor::class,
    validate: false,
    )]
    class Book implements ResourceInterface
    {
    }

    View Slide

  175. 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\Component\Resource\Metadata\BulkUpdate;
    // [...]
    shortName: 'bulk_publish',
    processor: PublishBookProcessor::class,
    validate: false,
    )]
    class Book implements ResourceInterface
    {
    }

    View Slide

  176. Publishing many books
    We’ll use Bulk Update operation which allows to update several items of your resource at the same time.
    ` `
    use Sylius\Component\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 Slide

  177. 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\Component\Resource\Metadata\BulkUpdate;
    // [...]
    #[BulkUpdate(
    processor: PublishBookProcessor::class,
    validate: false,
    )]
    class Book implements ResourceInterface
    {
    }

    View Slide

  178. 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\Component\Resource\Metadata\BulkUpdate;
    // [...]
    #[BulkUpdate(
    shortName: 'bulk_publish',
    validate: false,
    )]
    class Book implements ResourceInterface
    {
    }

    View Slide

  179. 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\Component\Resource\Metadata\BulkUpdate;
    // [...]
    #[BulkUpdate(
    shortName: 'bulk_publish',
    processor: PublishBookProcessor::class,
    )]
    class Book implements ResourceInterface
    {
    }

    View Slide

  180. Route

    View Slide

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

    View Slide

  182. 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 Slide

  183. 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 Slide

  184. 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 Slide

  185. 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 Slide

  186. ->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 Slide

  187. '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 Slide

  188. View Slide

  189. View Slide

  190. View Slide

  191. View Slide

  192. Sylius Resource without driver

    View Slide

  193. Sylius Resource without driver
    No Doctrine driver

    View Slide

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

    View Slide

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

    View Slide

  196. 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 Slide

  197. namespace App\BoardGameBlog\Infrastructure\Sylius\Resource;
    use Sylius\Component\Resource\Metadata\Resource;
    use Sylius\Component\Resource\Model\ResourceInterface;
    use Symfony\Component\Validator\Constraints\NotBlank;
    #[Resource(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 Slide

  198. #[Resource(driver: false)]
    namespace App\BoardGameBlog\Infrastructure\Sylius\Resource;
    use Sylius\Component\Resource\Metadata\Resource;
    use Sylius\Component\Resource\Model\ResourceInterface;
    use Symfony\Component\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 Slide

  199. use Sylius\Component\Resource\Metadata\Resource;
    #[Resource(driver: false)]
    namespace App\BoardGameBlog\Infrastructure\Sylius\Resource;
    use Sylius\Component\Resource\Model\ResourceInterface;
    use Symfony\Component\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 Slide

  200. final class BoardGameResource implements ResourceInterface
    namespace App\BoardGameBlog\Infrastructure\Sylius\Resource;
    use Sylius\Component\Resource\Metadata\Resource;
    use Sylius\Component\Resource\Model\ResourceInterface;
    use Symfony\Component\Validator\Constraints\NotBlank;
    #[Resource(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 Slide

  201. use Sylius\Component\Resource\Model\ResourceInterface;
    final class BoardGameResource implements ResourceInterface
    namespace App\BoardGameBlog\Infrastructure\Sylius\Resource;
    use Sylius\Component\Resource\Metadata\Resource;
    use Symfony\Component\Validator\Constraints\NotBlank;
    #[Resource(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 Slide

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

    View Slide

  203. Browsing board games

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  207. // 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 Slide

  208. 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 Slide

  209. ->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 Slide

  210. namespace App\BoardGameBlog\Infrastructure\Sylius\Grid\DataProvider;
    // [...]
    use Sylius\Component\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 Slide

  211. namespace App\BoardGameBlog\Infrastructure\Sylius\Grid\DataProvider;
    // [...]
    use Sylius\Component\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 Slide

  212. use Sylius\Component\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 Slide

  213. use Sylius\Component\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 Slide

  214. $data = [];
    namespace App\BoardGameBlog\Infrastructure\Sylius\Grid\DataProvider;
    // [...]
    use Sylius\Component\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 Slide

  215. foreach ($this->getFileData() as $row) {
    namespace App\BoardGameBlog\Infrastructure\Sylius\Grid\DataProvider;
    // [...]
    use Sylius\Component\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 Slide

  216. [$id, $name, $shortDescription] = $row;
    namespace App\BoardGameBlog\Infrastructure\Sylius\Grid\DataProvider;
    // [...]
    use Sylius\Component\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 Slide

  217. $data[] = new BoardGameResource(
    id: $id,
    name: $name,
    shortDescription: $shortDescription,
    );
    namespace App\BoardGameBlog\Infrastructure\Sylius\Grid\DataProvider;
    // [...]
    use Sylius\Component\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 Slide

  218. return new Pagerfanta(new ArrayAdapter($data));
    namespace App\BoardGameBlog\Infrastructure\Sylius\Grid\DataProvider;
    // [...]
    use Sylius\Component\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 Slide

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

    View Slide

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

    View Slide

  221. View Slide

  222. View Slide

  223. Adding board games

    View Slide

  224. 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\Component\Resource\Metadata\Index;
    use Sylius\Component\Resource\Metadata\Create;
    #[Resource(
    driver: false,
    formType: BoardGameType::class),
    ]
    #[Index(
    grid: 'app_board_game'
    )]
    #[Create(
    processor: CreateBoardGameProcessor::class,
    )]
    final class BoardGameResource implements ResourceInterface
    {
    // [...]
    }

    View Slide

  225. 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\Component\Resource\Metadata\Index;
    use Sylius\Component\Resource\Metadata\Create;
    #[Resource(
    driver: false,
    ]
    #[Index(
    grid: 'app_board_game'
    )]
    #[Create(
    processor: CreateBoardGameProcessor::class,
    )]
    final class BoardGameResource implements ResourceInterface
    {
    // [...]
    }

    View Slide

  226. 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\Component\Resource\Metadata\Index;
    use Sylius\Component\Resource\Metadata\Create;
    #[Resource(
    driver: false,
    ]
    #[Index(
    grid: 'app_board_game'
    )]
    #[Create(
    processor: CreateBoardGameProcessor::class,
    )]
    final class BoardGameResource implements ResourceInterface
    {
    // [...]
    }

    View Slide

  227. 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\Component\Resource\Metadata\Index;
    use Sylius\Component\Resource\Metadata\Create;
    #[Resource(
    driver: false,
    formType: BoardGameType::class),
    ]
    #[Index(
    grid: 'app_board_game'
    )]
    #[Create(
    )]
    final class BoardGameResource implements ResourceInterface
    {
    // [...]
    }

    View Slide

  228. 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\Component\Resource\Metadata\Index;
    use Sylius\Component\Resource\Metadata\Create;
    #[Resource(
    driver: false,
    formType: BoardGameType::class),
    ]
    #[Index(
    grid: 'app_board_game'
    )]
    #[Create(
    )]
    final class BoardGameResource implements ResourceInterface
    {
    // [...]
    }

    View Slide

  229. namespace App\BoardGameBlog\Infrastructure\Sylius\State\Processor;
    // [...]
    use Sylius\Component\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 Slide

  230. final class CreateBoardGameProcessor implements ProcessorInterface
    namespace App\BoardGameBlog\Infrastructure\Sylius\State\Processor;
    // [...]
    use Sylius\Component\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 Slide

  231. use Sylius\Component\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 Slide

  232. use Sylius\Component\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 Slide

  233. Assert::isInstanceOf($data, BoardGameResource::class);
    namespace App\BoardGameBlog\Infrastructure\Sylius\State\Processor;
    // [...]
    use Sylius\Component\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 Slide

  234. $this->createBoardGame($data);
    namespace App\BoardGameBlog\Infrastructure\Sylius\State\Processor;
    // [...]
    use Sylius\Component\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 Slide

  235. $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\Component\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 Slide

  236. $handle = fopen($this->dataDir . '/board_games.csv', 'a');
    namespace App\BoardGameBlog\Infrastructure\Sylius\State\Processor;
    // [...]
    use Sylius\Component\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 Slide

  237. fputcsv($handle, [(string) Uuid::v4(), $boardGameResource->name, $boardGameResource->shortDescription]);
    namespace App\BoardGameBlog\Infrastructure\Sylius\State\Processor;
    // [...]
    use Sylius\Component\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 Slide

  238. fclose($handle);
    namespace App\BoardGameBlog\Infrastructure\Sylius\State\Processor;
    // [...]
    use Sylius\Component\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 Slide

  239. return null;
    namespace App\BoardGameBlog\Infrastructure\Sylius\State\Processor;
    // [...]
    use Sylius\Component\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 Slide

  240. View Slide

  241. View Slide

  242. View Slide

  243. View Slide

  244. View Slide

  245. View Slide

  246. Editing board games

    View Slide

  247. 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\Component\Resource\Metadata\Update;
    // [...]
    #[Update(
    provider: BoardGameItemProvider::class,
    processor: UpdateBoardGameProcessor::class,
    )]
    final class BoardGameResource implements ResourceInterface
    {
    // [...]
    }

    View Slide

  248. 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\Component\Resource\Metadata\Update;
    // [...]
    provider: BoardGameItemProvider::class,
    processor: UpdateBoardGameProcessor::class,
    )]
    final class BoardGameResource implements ResourceInterface
    {
    // [...]
    }

    View Slide

  249. Editing board games
    use Sylius\Component\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 Slide

  250. 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\Component\Resource\Metadata\Update;
    // [...]
    #[Update(
    processor: UpdateBoardGameProcessor::class,
    )]
    final class BoardGameResource implements ResourceInterface
    {
    // [...]
    }

    View Slide

  251. 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\Component\Resource\Metadata\Update;
    // [...]
    #[Update(
    processor: UpdateBoardGameProcessor::class,
    )]
    final class BoardGameResource implements ResourceInterface
    {
    // [...]
    }

    View Slide

  252. 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\Component\Resource\Metadata\Update;
    // [...]
    #[Update(
    provider: BoardGameItemProvider::class,
    )]
    final class BoardGameResource implements ResourceInterface
    {
    // [...]
    }

    View Slide

  253. 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\Component\Resource\Metadata\Update;
    // [...]
    #[Update(
    provider: BoardGameItemProvider::class,
    )]
    final class BoardGameResource implements ResourceInterface
    {
    // [...]
    }

    View Slide

  254. // [...]
    use Sylius\Component\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 Slide

  255. final class BoardGameItemProvider implements ProviderInterface
    // [...]
    use Sylius\Component\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 Slide

  256. use Sylius\Component\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 Slide

  257. public function provide(Operation $operation, Context $context): ?BoardGameResource
    // [...]
    use Sylius\Component\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 Slide

  258. $request = $context->get(RequestOption::class)?->request();
    Assert::notNull($request);
    // [...]
    use Sylius\Component\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 Slide

  259. $id = $request->attributes->get('id');
    Assert::nullOrString($id);
    // [...]
    use Sylius\Component\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 Slide

  260. [$id, $name, $shortDescription] = $this->getFileData()[$id] ?? null;
    // [...]
    use Sylius\Component\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 Slide

  261. if (null === $id) {
    return null;
    }
    // [...]
    use Sylius\Component\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 Slide

  262. return new BoardGameResource(
    id: $id,
    name: $name,
    shortDescription: $shortDescription,
    );
    // [...]
    use Sylius\Component\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 Slide

  263. // [...]
    use Sylius\Component\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 Slide

  264. final class UpdateBoardGameProcessor implements ProcessorInterface
    // [...]
    use Sylius\Component\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 Slide

  265. use Sylius\Component\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 Slide

  266. public function process(mixed $data, Operation $operation, Context $context): mixed
    // [...]
    use Sylius\Component\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 Slide

  267. Assert::isInstanceOf($data, BoardGameResource::class);
    // [...]
    use Sylius\Component\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 Slide

  268. $this->updateBoardGame($data);
    // [...]
    use Sylius\Component\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 Slide

  269. $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\Component\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 Slide

  270. return null;
    // [...]
    use Sylius\Component\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);
    }
    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 Slide

  271. View Slide

  272. View Slide

  273. View Slide

  274. View Slide

  275. Deleting board games

    View Slide

  276. 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\Component\Resource\Metadata\Delete;
    // [...]
    #[Delete(
    provider: BoardGameItemProvider::class,
    processor: DeleteBoardGameProcessor::class,
    )]
    final class BoardGameResource implements ResourceInterface
    {
    // [...]
    }

    View Slide

  277. 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\Component\Resource\Metadata\Delete;
    // [...]
    provider: BoardGameItemProvider::class,
    processor: DeleteBoardGameProcessor::class,
    )]
    final class BoardGameResource implements ResourceInterface
    {
    // [...]
    }

    View Slide

  278. Deleting board games
    use Sylius\Component\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 Slide

  279. 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\Component\Resource\Metadata\Delete;
    // [...]
    #[Delete(
    processor: DeleteBoardGameProcessor::class,
    )]
    final class BoardGameResource implements ResourceInterface
    {
    // [...]
    }

    View Slide

  280. 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\Component\Resource\Metadata\Delete;
    // [...]
    #[Delete(
    processor: DeleteBoardGameProcessor::class,
    )]
    final class BoardGameResource implements ResourceInterface
    {
    // [...]
    }

    View Slide

  281. 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\Component\Resource\Metadata\Delete;
    // [...]
    #[Delete(
    provider: BoardGameItemProvider::class,
    )]
    final class BoardGameResource implements ResourceInterface
    {
    // [...]
    }

    View Slide

  282. 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\Component\Resource\Metadata\Delete;
    // [...]
    #[Delete(
    provider: BoardGameItemProvider::class,
    )]
    final class BoardGameResource implements ResourceInterface
    {
    // [...]
    }

    View Slide

  283. // [...]
    use Sylius\Component\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 Slide

  284. final class DeleteBoardGameProcessor implements ProcessorInterface
    // [...]
    use Sylius\Component\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 Slide

  285. use Sylius\Component\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 Slide

  286. public function process(mixed $data, Operation $operation, Context $context): mixed
    // [...]
    use Sylius\Component\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 Slide

  287. Assert::isInstanceOf($data, BoardGameResource::class);
    // [...]
    use Sylius\Component\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 Slide

  288. $this->deleteBoardGame($data);
    // [...]
    use Sylius\Component\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 Slide

  289. $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\Component\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 Slide

  290. return null;
    // [...]
    use Sylius\Component\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);
    }
    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 Slide

  291. View Slide

  292. View Slide

  293. View Slide

  294. Sorting board games

    View Slide

  295. 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 Slide

  296. 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 Slide

  297. 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 Slide

  298. 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 Slide

  299. View Slide

  300. View Slide

  301. 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 Slide

  302. $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 Slide

  303. $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 Slide

  304. $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 Slide

  305. 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 Slide

  306. 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 Slide

  307. 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 Slide

  308. 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 Slide

  309. 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 Slide

  310. View Slide

  311. View Slide

  312. Default sorting
    Is it sorted by name?

    View Slide

  313. View Slide

  314. 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 Slide

  315. $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 Slide

  316. $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 Slide

  317. View Slide

  318. What’s next?

    View Slide

  319. What’s next?
    Stable release

    View Slide

  320. What’s next?
    Stable release
    Remove Kernel events

    View Slide

  321. What’s next?
    Stable release
    Remove Kernel events
    PHP configuration

    View Slide

  322. Kernel Events

    View Slide

  323. View Slide

  324. PHP configuration
    Open-source cooperation

    View Slide

  325. 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\Resource;
    use Sylius\Component\Loader\Configuration\ResourceConfigurator;
    return static function (ResourceConfigurator $resourceConfigurator): void {
    $resourceConfigurator
    ->withResource((new Resource(Book::class))
    ->withRoutePrefix('admin')
    ->withOperations([
    new Index(),
    new Create(),
    new Update(),
    new Delete(),
    ])
    )
    ->withResource((new Resource(Subscription::class))
    ->withOperations([
    new Index(),
    ])
    )

    View Slide

  326. 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\Resource;
    use Sylius\Component\Loader\Configuration\ResourceConfigurator;
    $resourceConfigurator
    ->withResource((new Resource(Book::class))
    ->withRoutePrefix('admin')
    ->withOperations([
    new Index(),
    new Create(),
    new Update(),
    new Delete(),
    ])
    )
    ->withResource((new Resource(Subscription::class))
    ->withOperations([
    new Index(),
    ])
    )

    View Slide

  327. 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\Resource;
    $resourceConfigurator
    ->withResource((new Resource(Book::class))
    ->withRoutePrefix('admin')
    ->withOperations([
    new Index(),
    new Create(),
    new Update(),
    new Delete(),
    ])
    )
    ->withResource((new Resource(Subscription::class))
    ->withOperations([
    new Index(),
    ])
    )

    View Slide

  328. Resource configurator
    ->withResource((new Resource(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\Resource;
    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 Resource(Subscription::class))
    ->withOperations([
    new Index(),
    ])
    )

    View Slide

  329. Resource configurator
    use Sylius\Component\Resource\Metadata\Resource;
    ->withResource((new Resource(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 Resource(Subscription::class))
    ->withOperations([
    new Index(),
    ])
    )

    View Slide

  330. 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\Resource;
    use Sylius\Component\Loader\Configuration\ResourceConfigurator;
    return static function (ResourceConfigurator $resourceConfigurator): void {
    $resourceConfigurator
    ->withResource((new Resource(Book::class))
    ->withOperations([
    new Index(),
    new Create(),
    new Update(),
    new Delete(),
    ])
    )
    ->withResource((new Resource(Subscription::class))
    ->withOperations([
    new Index(),
    ])
    )

    View Slide

  331. 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\Resource;
    use Sylius\Component\Loader\Configuration\ResourceConfigurator;
    return static function (ResourceConfigurator $resourceConfigurator): void {
    $resourceConfigurator
    ->withResource((new Resource(Book::class))
    ->withRoutePrefix('admin')
    )
    ->withResource((new Resource(Subscription::class))
    ->withOperations([
    new Index(),
    ])
    )

    View Slide

  332. Resource configurator
    ->withResource((new Resource(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\Resource;
    use Sylius\Component\Loader\Configuration\ResourceConfigurator;
    return static function (ResourceConfigurator $resourceConfigurator): void {
    $resourceConfigurator
    ->withResource((new Resource(Book::class))
    ->withRoutePrefix('admin')
    ->withOperations([
    new Index(),
    new Create(),
    new Update(),
    new Delete(),
    ])
    )
    ->withOperations([
    new Index(),
    ])
    )

    View Slide

  333. 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\Resource;
    use Sylius\Component\Loader\Configuration\ResourceConfigurator;
    return static function (ResourceConfigurator $resourceConfigurator): void {
    $resourceConfigurator
    ->withResource((new Resource(Book::class))
    ->withRoutePrefix('admin')
    ->withOperations([
    new Index(),
    new Create(),
    new Update(),
    new Delete(),
    ])
    )
    ->withResource((new Resource(Subscription::class))
    )

    View Slide

  334. // 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(),
    ])
    )
    ->withResource((new ApiResource(Subscription::class))
    ->withOperations([
    new GetCollection(),
    ])
    )
    ;
    };

    View Slide

  335. 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(),
    ])
    )
    ->withResource((new ApiResource(Subscription::class))
    ->withOperations([
    new GetCollection(),
    ])
    )
    ;
    };

    View Slide

  336. 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(),
    ])
    )
    ->withResource((new ApiResource(Subscription::class))
    ->withOperations([
    new GetCollection(),
    ])
    )
    ;
    };

    View Slide

  337. ->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(),
    ])
    )
    ->withResource((new ApiResource(Subscription::class))
    ->withOperations([
    new GetCollection(),
    ])
    )
    ;
    };

    View Slide

  338. 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(),
    ])
    )
    ->withResource((new ApiResource(Subscription::class))
    ->withOperations([
    new GetCollection(),
    ])
    )
    ;
    };

    View Slide

  339. ->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(),
    ])
    )
    ->withResource((new ApiResource(Subscription::class))
    ->withOperations([
    new GetCollection(),
    ])
    )
    ;
    };

    View Slide

  340. ->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')
    )
    ->withResource((new ApiResource(Subscription::class))
    ->withOperations([
    new GetCollection(),
    ])
    )
    ;
    };

    View Slide

  341. ->withResource((new ApiResource(Subscription::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
    ->withResource((new ApiResource(Book::class))
    ->withRoutePrefix('admin')
    ->withOperations([
    new GetCollection(),
    new Post(),
    new Put(),
    new Delete(),
    ])
    )
    ->withOperations([
    new GetCollection(),
    ])
    )
    ;
    };

    View Slide

  342. ->withOperations([
    new GetCollection(),
    ])
    // 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(),
    ])
    )
    ->withResource((new ApiResource(Subscription::class))
    )
    ;
    };

    View Slide

  343. View Slide