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

The Evolution of Symfony: Now and to the Future!

weaverryan
November 18, 2022

The Evolution of Symfony: Now and to the Future!

With the release of 6.2, Symfony will contain somewhere near *500* new features from just the past 12 months! Wow!

And, while many of these are small, the landscape of *how* Symfony apps are developed continues to evolve. In this talk, we'll look at some of the most important changes, from new dependency injection attributes, framework-extra bundle features in Symfony, Symfony UX, testing, and more! Basically... a quick catch-up on 12 months, *thousands* of commits, and hundreds of releases across numerous repositories. Let's go!

weaverryan

November 18, 2022
Tweet

More Decks by weaverryan

Other Decks in Programming

Transcript

  1. 
 > Author at SymfonyCasts.com symfonycasts.com twitter.com/weaverryan Hello there! I’m

    Ryan! > Husband of the talented and beloved @leannapelham > Father to my much more imaginative son, Beckett > I work at Disney, as a mouse Mickey > On the core team, Encore, UX, animated fi lms, etc
  2. PHP 8.0 (2 years old) - Attributes - Property Promotion

    - Named Arguments - $null?->safeOp() - Trailing arg commas - Stringable @weaverryan PHP 8.1 (1 year old) - Enums - Readonly props - Callable syntax - New in initializers PHP 8.2 (-1 week) - Readonly classes
  3. /** @ORM\Entity(repositoryClass=UserRepository::class) */ class User implements UserInterface, PasswordAuthenticatedUserInterface { /**

    * @ORM\Id * @ORM\GeneratedValue * @ORM\Column(type="integer") */ private $id; /** @ORM\Column(type="string", unique=true) */ private $email; /** @ORM\Column(type="json") */ private $roles = []; /** @ORM\ManyToOne(targetEntity=Address::class) */ private $address; }
  4. #[ORM\Entity(repositoryClass: UserRepository::class)] class User implements UserInterface, PasswordAuthenticatedUserInterface { #[ORM\Id] #[ORM\GeneratedValue]

    #[ORM\Column(type: Types::INTEGER)] private $id; #[ORM\Column(type: Types::STRING, unique: true)] private $email; #[ORM\Column(type: Types::JSON)] private $roles = []; #[ORM\ManyToOne(targetEntity: Address::class)] private $address; }
  5. #[ORM\Entity(repositoryClass: UserRepository::class)] class User implements UserInterface, PasswordAuthenticatedUserInterface { #[ORM\Id] #[ORM\GeneratedValue]

    #[ORM\Column(type: Types::INTEGER)] private ?int $id = null; #[ORM\Column(type: Types::STRING, unique: true)] private ?string $email = null; #[ORM\Column(type: Types::JSON)] private array $roles = []; #[ORM\ManyToOne(targetEntity: Address::class)] private ?Address $address = null; }
  6. #[ORM\Entity(repositoryClass: UserRepository::class)] class User implements UserInterface, PasswordAuthenticatedUserInterface { #[ORM\Id] #[ORM\GeneratedValue]

    #[ORM\Column()] private ?int $id = null; #[ORM\Column(unique: true)] private ?string $email = null; #[ORM\Column] private array $roles = []; #[ORM\ManyToOne()] private ?Address $address = null; }
  7. #[Route('/products/edit/{id}')] public function edit(Product $product, #[CurrentUser] User $user) { }

    * #[CurrentUser] is not new. But playing nice with the "entity resolver" IS new
  8. class PlotFactory { private CharacterGenerator $characterGenerator; private ConflictCreator $conflictCreator; private

    LoveableSidekickBuilder $sidekickBuilder; public function __construct( CharacterGenerator $characterGenerator, ConflictCreator $conflictCreator, LoveableSidekickBuilder $sidekickBuilder, ) { $this->characterGenerator = $characterGenerator; $this->conflictCreator = $conflictCreator; $this->sidekickBuilder = $sidekickBuilder; } } 🥱
  9. class PlotFactory { public function __construct( private CharacterGenerator $characterGenerator, private

    ConflictCreator $conflictCreator, private LoveableSidekickBuilder $sidekickBuilder, ) { } } Property Promotion!
  10. class PlotFactory { public function __construct( private readonly CharacterGenerator $characterGenerator,

    private readonly ConflictCreator $conflictCreator, private readonly LoveableSidekickBuilder $sidekickBuilder, ) { } } Hipster properties
  11. class PlotFactory { public function __construct( private bool $isDebug, private

    LoaderInterface $twigLoader, ) { } } Non-autowireable args
  12. class PlotFactory { public function __construct( private bool $isDebug, private

    LoaderInterface $twigLoader, ) { } } #[Autowire('%kernel.debug%')] #[Autowire(service: 'twig.loader')]
  13. #[AutoconfigureTag('kernel.event_listener', [ 'event' => RequestEvent::class, 'method' => 'onKernelRequest', ])] class

    MovieCreditsListener { public function onKernelRequest(RequestEvent $event) { } }
  14. class DecoratedRouter implements RouterInterface { public function __construct( private RouterInterface

    $router, private LoggerInterface $logger ) { } public function generate(string $name, array $parameters = []) { $this->logger->info('Generating route', [ 'name' => $name, ]); return $this->router->generate($name, $parameters); } }
  15. #[AsDecorator(decorates: 'router')] class DecoratedRouter implements RouterInterface { public function __construct(

    private RouterInterface $router, private LoggerInterface $logger ) { } public function generate(string $name, array $parameters = []) { $this->logger->info('Generating route', [ 'name' => $name, ]); return $this->router->generate($name, $parameters); } }
  16. #[Route('/products/edit/{id}', name: 'product_edit')] public function editProduct(Product $product): Response { $form

    = $this->createForm(ProductFormType::class, $product); // ... return $this->render('product/edit.html.twig', [ 'form' => $form, ]); } $this->renderForm() $form->createView() ❌ ❌ 422 status code ✅
  17. class AccessTokenHandler implements AccessTokenHandlerInterface { public function getUserIdentifierFrom(string $token): string

    { $accessToken = $this->repository->findOneByValue($token); if (null === $accessToken || !$accessToken->isValid()) { throw new BadCredentialsException('Invalid credentials.'); } return $accessToken->getUserId(); } }
  18. 6 Big Releases Dec 2021 v2.0 Mar 2022 v2.1 Jun

    2022 v2.2 Jul 2022 v2.3 Aug 2022 v2.4 Nov 2022 v2.5
  19. #[AsTwigComponent] class SearchPackagesComponent { public function __construct(private PackageRepository $packageRepo) {

    } public function getPackages(): array { return $this->packageRepo->findAll(); } } <div {{ attributes }}> <input> {% for package in packages %} ... {% endfor %} </div> 7 New Components! • autocomplete • twig-component
  20. #[AsLiveComponent] class SearchPackagesComponent { #[LiveProp(writable: true)] public string $search =

    ''; // ... public function getPackages(): array { return $this->packageRepo->findAll($this->search); } } <div {{ attributes }}> <input data-model="search"> {% for package in packages %} ... {% endfor %} </div> Live Components
  21. <div {{ vue_component('PackageSearch', { packages: packagesData }) }}></div> <div {{

    react_component('PackageSearch', { packages: packagesData }) }}></div> • autocomplete • twig-component • live-component • react • vue • notify • typed 7 New Components!
  22. Webpack Encore Dec 2021 v1.7.0 May 2022 v2.0.0 Jul 2022

    v3.0.0 Sep 2022 v4.0.0 Easy Upgrade Path
  23. MakerBundle 10 new minor releases symfony/ fl ex composer recipes:update

    API Platform Major V3!!! EasyAdmin 4.0 -> 4.4 zenstruck/browser 1.0! Noti fi ers 25+ new noti fi er bridges!
  24. class BlogPostTest extends TestCase { use HasBrowser; public function testViewPostAndAddComment()

    { $post = PostFactory::new()->create(['title' => 'My First Post']); $this->browser() // $this->pantherBrowser() ->visit("/posts/{$post->getId()}") ->assertSuccessful() ->assertSeeIn('title', 'My First Post') ->assertSeeIn('h1', 'My First Post') ->assertNotSeeElement('#comments') ->fillField('Comment', 'My First Comment') ->click('Submit') ->assertOn("/posts/{$post->getId()}") ->assertSeeIn('#comments', 'My First Comment') ; } }
  25. symfony/symfony 500 contributors 4000+ commits symfony/symfony-docs 290 contributors 3,000+ commits

    symfony/ux 50+ contributors 500+ commits symfony/maker-bundle 30+ contributors 250+ commits
  26. "Through The Force, Things You Will See. Other Places. The

    Future, The Past" @weaverryan What's coming next?
  27. Symfony UX • Svelte? • Form Collection? • Date Field?

    • Upload? • Rich Text Editor? UX Live Components: parity with Livewire
  28. zenstruck/document-library // $library wraps around Flysystem $document = $library->open('path/to/file.txt'); $document->path();

    $document->size(); $document->contents(); $document->publicUrl(); $document->temporaryUrl('+30 minutes'); $document->store('some/path.txt', $contents);
  29. #[ORM\Entity] class Product { #[Mapping(library: 'public')] #[ORM\Column(type: Document::class)] public ?Document

    $image = null; } /** @var UploadedFile $file */ $product->image = new PendingDocument($file); $entityManager->persist($product); $entityManager->flush(); // on next request $product = $repository->find($id); $product->image->temporaryUrl('+10 minutes'); $product->image->contents();