Omwille van de huidige situatie in de wereld, zijn ook de Symfony events dit jaar digitaal. De Symfony community heeft gekozen voor Hopin als tool om Symfony World te geven. Ik ben alvast fan van de eenvoud van deze tool om snel een sessie te kiezen en te volgen.  

Het is ondertussen alweer enkele jaren geleden dat ik zelf nog een Symfony applicatie gebruikte. Ik werkte ondertussen alleen nog maar met Drupal of Laravel. Je zal dus verder in deze notities zien dat ik naar die frameworks relateer om dingen voor mezelf ook duidelijker te maken. 

Keynote

Fabien Potencier

In deze keynote had Fabien het over de grote gamechangers van Symfony:

  • De Symfony Community, veel one-time contributors maar ook een dedicated docs (7 mensen) en core team (17 mensen) 

  • HttpKernelInterface (van symfony2 - al 10 jaar niet meer aangepast) gebaseerd op http-standaarden en ook door veel andere frameworks gebruikt. (ook oa Drupal maakt hier gebruik van) 

  • Symfony Flex, de glue code die packages samen gooit naar het full stack framework. Dit zorgt ervoor dat php-libraries makkelijk als first-class citizens gebruikt kunnen worden. Flex is een enabler om sneller applicaties te kunnen ontwikkelen (RAD). 
    Er kwam dan een demo van het Symfony commando dat met de maker bundle zorgt voor snelle setup en class-generating, alsook integratie van docker & docker-compose. 

Daarna ging hij in op wat de volgende game changer voor Symfony kan zijn.  

Volgens Fabien is dat Javascript integratie. Dit is al een deel van de historiek van het Symfony-framework. In de prehistorie (sf1) was er al het prototype framework dat geïntegreerd was (http://prototypejs.org/). 

Volgens hem zijn Angular, Vue en React zijn allemaal rendabele manieren om Javascript projecten te schrijven in 2020.  

De oplossing van het Symfony team is er één die niet ingebakken zit in 1 framework, maar die gebouwd is op Vanilla Javascript oplossingen, die overal geïntegreerd kunnen worden. 

Symfony UX is een diepe integratie met Flex, zodat die zowel composer.json als package.json kan aanpassen. PHP-packages kunnen zo ook Javascript dependencies hebben. 

Een van de getoonde voorbeelden was om gebruik te maken van dropzone in een Symfony form. Na installatie van de package was dit alleen nog maar het veranderen van het input type. 

Dit lijkt mij een betere oplossing dan asset-packagist dat vaak in de Drupal community gebruikt wordt. 

Daarnaast zijn er nog extra integraties bij gekomen: https://stimulusjs.org/, in samenwerking met Swup om een meer SPA-achtige manier van pagina inladen mogelijk te maken. Dit is gebaseerd op turbolinks en erg gelijkaardig aan refreshless in de Drupal wereld.

Blurhash placeholder images zijn ook toegevoegd om een snellere afbeelding te kunnen weergeven. Dit zorgt voor een veel betere UX bij tragere connecties of pagina's met een groot aantal afbeeldingen. Dit vind ik een erg interessante library die we zeker gaan gebruiken in onze eigen projecten. 

 Alle nieuwe features werden tijdens de conferentie zelf gepubliceerd, in combinatie met deze blogpost: https://symfony.com/blog/new-in-symfony-the-ux-initiative-a-new-javascript-ecosystem-for-symfony  

Zie ook de slides van Fabien hier: https://speakerdeck.com/fabpot/symfony-ux  

Decoupling an application with Symfony Messenger

David Buchman

In Symfony applicaties lijkt dit heel erg op een event-based systeem - maar dan over verschillende threads en niet op de standaard PHP single-threaded manier van werken. PHP kan natuurlijk altijd maar over 1 thread werken, daarom wordt dit effectief door een ander proces opgevangen. Het originele proces stopt bij afleveren van het bericht op de bus.

Een van de grote voordelen van een message is dat het asynchroon is, de sender wacht niet op antwoord van de receiver. Een ander voordeel is dat ze betrouwbaar zijn, als de verwerker stopt met antwoorden dan wacht de message in de bus tot die wel opgepikt kan worden.

Dit zijn de grote veranderingen tegenover het Event systeem.

Voordelen:

  1. Betere applicatie structuur want niet alles moet op dezelfde plek, dit is deels ook een voordeel van het event systeem. Maar daar moet alles nog steeds in dezelfde applicatie leven. Een message die via een bus komt moet niet in dezelfde applicatie en zelfs niet in dezelfde taal afgehandeld worden.
  2. Asynchroon verwerken van data.
  3. Het is een eenvoudige manier om aan schaal vergroting te doen. Meerdere workers kunnen toegevoegd worden om zware events te verwerken. Op deze manier is het eenvoudig om op te schalen waar mogelijk.

Verder gaat David erg diep in op hoe de Symfony Messenger werkt. Maar er zijn ook een aantal framework-agnostic tips, waaronder deze:

Je message is best klein en bevat alleen de id van het object waar het over gaat. De subscriber gaat dan in de databank dezelfde data terug ophalen. Dit zorgt ervoor dat de receiver altijd de meest recente data heeft. Deze tip werd in de volgende sessie van Samuel Roze tegengesproken, volgens hem kan het soms wel om alle data in de message te stoppen. Interessant dat er toch altijd vanuit eigen achtergrond naar gekeken wordt.  

Symfony Messenger heeft daarnaast ook rejuvenation built-in. Dit wil zeggen dat een proces zelf een aantal messages kan verwerken voor het zichzelf afsluit. Dit proces kan daarna door supervisord/docker opnieuw gestart worden.

Als volgende concept ging het over transport decorators, dit is een manier om een message die uitgestuurd wordt te gaan wrappen met extra metadata voor ze effectief op de bus wordt gezet.

David gaf enkele voorbeelden, waaronder deduplicate – dit is een semaphore systeem in Redis. Dit zorgt ervoor dat een product maar 1 keer op de queue gezet kan worden tot het afgehandeld is. Op deze manier kunnen snelle updates er niet voor zorgen dat de queue overbelast geraakt.

In de chat van de talk werd er nog veel gepraat over message architecturen. Een tip was om standaard de doctrine/database transport te gebruiken, dat is makkelijk om mee te starten.  

Er zijn nog andere implementaties mogelijk van zodra er opgeschaald wordt, het verhuizen van je messages naar Amazon SQS, Redis, AMQP of Apache Kafka zorgt automatisch voor minder load op je databank. 

Symfony meets Cypress - E2E testing for symfony developers

Ramona Schwering

End-to-end tests worden soms ook wel omschreven als worfklow testing of integration testing. Dit is waarbij een computer een echte gebruiker simuleert en echt klikt, navigeert, typt.

Er zijn echter een aantal pijnpunten verbonden aan het schrijven van end-to-end tests.

  1. Ze zijn traag om te lopen t.o.v. unit tests
  2. Moeilijk om te debuggen.
  3. Moeilijk om te onderhouden
  4. Test zijn soms flaky, dit kan ook bij andere soorten van testen (zoals unit tests) voorkomen maar is pijnlijker vanwege de andere moeilijkheden.

Waarom Cypress gebruiken om e2e tests te schrijven? Het bied al 2 oplossingen van bovenstaande lijst, ze zijn snel en makkelijk te debuggen.

  1. Cypress is een all-in-one tool.
  2. Tests worden geschreven in es6-js.
  3. UI (testrunner) met insights daar rechtstreeks bij.
  4. Geen selenium/webdriver nodig op het systeem dat de testen draait.
  5. Console/network tab beschikbaar tijdens testen, omdat deze in de browser van de gebruiker kunnen runnen.

Ramona gaf ook een live demo van hoe dit werkt. Deze demo ging van het aanmaken van de test directory tot het runnen van de test en het schrijven van een paar assertions, omdat dit maar een deel van de talk was, ben ik wel onder de indruk van de snelheid waarmee de eerste test geschreven kon worden.

Cypress kan met selectors de juiste items selecteren, de Cypress UI heeft hiervoor een tool die kan helpen om de juiste selector te vinden. Ids kunnen gegeneerd zijn, dus daar moet mee opgelet worden, want die zijn daarvoor niet altijd hetzelfde.

De debugging features van de testrunner in de browser zijn erg interactief en je kan de history van een test meteen terugzien. Zo kan je duidelijk zien wat er fout loopt, dit is volgens mij wel echt een enorm voordeel tegenover testing met Behat.

Spec tests hebben vergelijkbare syntax als JEST unit tests. Ze gebruiken de describe-it (Mocha) syntax om tests te schrijven. In PHP hebben we ook het Kahlan framework dat deze syntax gebruikt. Ook de nieuwe syntax van pestphp is vergelijkbaar. Het is dus goed dat er hier voor een bestaande manier van test schrijven gekozen is. Zo is het makkelijker documentatie te vinden, duidelijk pluspunt volgens mij.

Er zijn ook docker-images gemaakt door het Cypress team. Deze bieden een makkelijke manier om te starten met testen in cypress: https://www.cypress.io/blog/2019/05/02/run-cypress-with-a-single-docker-command/

We gaan deze methode van testen zeker eens testen.

Streaming: an alternative to CRUD & batch processing

Samuel Roze

Deze sessie ging over hoe je over de gevolgen en uitdagingen van event streaming in PHP en de keuze hiervoor als architectuur.

Events in deze context zijn berichten die in real-time tussen verschillende services/componenten van een systeem lopen. Dit kan dezelfde applicatie of verschillende onderdelen van een microservice distributed systeem zijn.

Data die naar een van deze services gaat, gaat over een queue, en wanneer je zo een queue persistent maakt, kan zoiets ook blijven werken als de service die de message gaat verwerken tijdelijk offline is.

Deze verwerkende service moet niet weten we het event verzonden heeft, maar moet alleen maar weten hoe een event van het type dat ze moet verwerken er uit ziet, met als duidelijk voordeel duidelijkere grenzen doorheen je applicatie.

Er zijn een aantal grote downsides aan deze manier van werken:

  1. Writing code for distrubuted systems is hard.
  2. You need governance about how message bus is used.

Mogelijke problemen:

  1. Je moet zeker zijn dat een bericht verzonden is, een oplossing daarvoor kan het “outbox pattern” zijn, of je queue altijd in een tabel opslaan.
    Het outboxen slaat de data op in een lokale tabel en in een aparte outbox tabel. Zo weten we zeker dat het opslaan van de message en de echte data succesvol is.  
  2. Guaranteed exactly once delivery is heel erg moeilijk. Berichtend kunnen soms door 2 verschillende workers tegelijk opgepikt worden. Het probleem als dit niet goed opgevolgd wordt: event 2x processed waardoor een product 2x aangekocht wordt bijvoorbeeld.
    Volgens Samuel gaat het ooit wel eens voorkomen dat je dezelfde actie meerdere keren aan krijgt, je moet hier rekening mee houden in het opbouwen van je systeem. Dit kan je doen door idempotente acties, als je een actie meerdere keren uitvoert met dezelfde input moet er elke keer dezelfde output uit komen. Een oplossing is de huidige state mee te geven in de message of een idempotency key mee te geven. Deze key representeert een bericht en zo kan je zien of dit al verwerkt is.
  3. Berichten in parallel verwerken:

    Dit kan voor een grote verbetering in snelheid van de applicatie zorgen, door het opstarten van een nieuwe worker kan je de effectieve bandbreedte van je applicatie verdubbelen. Opgelet hiermee, want dit kan ervoor zorgen dat we state verliezen.

    De makkelijkste oplossing is niet concurrent processen, de andere oplossing is locking. Zo lock je een entity zodat deze maar door 1 worker verwerkt kan worden. De tweede worker kan dan al wel verder met een andere entity. Zo een optimistic locking kan met Symfony Lock (Zie ook Lock & Semaphore)

Deze talk zal ik nog enkele keren moeten bekijken om alles te begrijpen wat er is uitgelegd. Samuel deed enorm z'n best om alles duidelijk uit te leggen – maar hier zat veel informatie in. Ik kan alleszins al wel veel praktische applicaties hiervoor zien.

Lock & Semaphore: The gatekeepers of your resources

Jérémy DERUSSÉ

Deze talk gaat over twee Symfony componenten die erg gerelateerd zijn aan mekaar.

Een Semaphore laat aan N processen toe om een resource te verwerken, en geeft aan de rest die mogelijkheid niet, een Lock is een Semaphore waar N gelijk is aan 1.

In deze talk kwamen een aantal features van beide componenten aan bod:  

  • Dezelfde key instance is nodig om een lock terug te unlocken. Deze key kan wel serialized worden - maar het moet dezelfde zijn.
  • Er is een Auto release functionaliteit, dit gebeurt als een lock wordt garbage collected, daarom moet je ook steeds je lock aan een variable toewijzen bij acquire, anders kan die bij afsluiten van die functie als weggehaald worden.
  • Blocking mode: acquire(true) - gegarandeerd dat lock door dit process acquired is.
  • Expiration: om zombie locks tegen te gaan, dit is niet eenvoudig om hier een aantal seconden in te schatten, daarvoor kan er gebruik gemaakt worden van lock->refresh, op die manier is de lock niet te lang - maar word die wel refreshed in een loop (of elke keer refresh wordt aangesproken).
  • Semaphores kunnen ook gebruikt worden om de server resources onder controle te houden, zodat er maximaal X items tegelijk kunnen gebeuren, als je weet dat dit een zware actie zal zijn.
  • RecoverableException, Symfony zal automatisch opnieuw proberen om deze message te verwerken als deze vanuit een handler word gegooit.
  • Shared locks: acquireRead kan ervoor zorgen dat meerdere gebruikers van een lock een items kunnen lezen maar dat er bij meerdere updates een error wordt gegeven. Zo kan tijdens het updaten van een order, dat niet meer worden gelezen tot de update gedaan is.
  • Cross process: Dit zorgt ervoor dat we functionaliteit kunnen maken zoals Netflix, er kunnen maximaal 5 sessies tegelijkertijd actief zijn op een account.  

Let ook op om de factory te injecteren in je services in plaats van de lockservice zelf. Anders heeft elke instance van je service hetzelfde lock en is je locking eigenlijk nutteloos.

Keynote: Modern Security with Symfony's Shiny new Security Component

Ryan Weaver

De originele implementatie van het symfony security systeem is gebaseerd op die van Java Spring, deze is erg complex. De logica hiervan is verspreid over minstens 4 verschillende plaatsen en er is geen centralisatie van logica.   

Symfony Guard kwam 5 jaar geleden om het wat makkelijker te maken - maar elk onderliggend component bleef hetzelfde, alleen de configuratie werd wat makkelijker.

Tijdens de conferentie werd het nieuwe security component - experimenteel in 5.2 voorgesteld.

Het eerste wat Ryan hier over te zeggen had was dat de User class en access controle in controllers/services exact hetzelfde is gebleven, en dat ook alles van authorization hetzelfde bleef.

De wijzigingen zitten allemaal in security.yml en in de auth manager, en ook het volledige concept van een anonieme gebruiker is verdwenen uit de login flow en securitycomponenten.

Voor ingelogde gebruikers is er het concept van passport & badges als voorzien, een passport is een gebruiker en bevat een of meerdere badges.  

  1. UserBadge
  2. PasswordCredentialsBadge  
  3. CsrfBadge met token

Daarna wordt er gewerkt met events om die badges te checken en daar acties op uit te voeren. Deze badges en passports zijn alleen tijdens authenticatie beschikbaar en daarbuiten bestaan deze niet meer.

De rest van deze sessie is moeilijk samen te vatten, deze is erg dik. Het grote doel van deze aanpassing is om complexiteit te verminderen.

Dit lijkt ook gelukt, het securitysysteem ziet er eenvoudiger uit dan de vorige implementatie, hoewel mijn gebrek aan het werken in een moderne Symfony applicatie hier wel een beetje vervelend was. De laatste Symfony applicatie die ik maakte, maakte al wel gebruik van het guard systeem - maar toch merkte ik hier wel dat ik onder andere hierdoor enthausiast ben om een nieuwe Symfony applicatie te maken.