Ce diaporama a bien été signalé.
Nous utilisons votre profil LinkedIn et vos données d’activité pour vous proposer des publicités personnalisées et pertinentes. Vous pouvez changer vos préférences de publicités à tout moment.
Using API platform to build ticketing
system
Antonio Perić-Mažar, Locastic
Paula Čučuk, Locastic
18.10.2019. - #sfday
Antonio
Perić-Mažar
CEO @ Locastic
Co-founder @ Litto
Co-founder @ Tinel Meetup
t: @antonioperic
m: antonio@locastic.com
Paula Čučuk
Lead Backend Developer @ Locastic
Partner @ Locastic
t: @paulala_14
m: paula@locastic.com
Locastic
Helping clients create web
and mobile apps since 2011
• UX/UI
• Mobile apps
• Web apps
• Training & Consulting
ww...
• API Platform & Symfony
• Ticketing platform: GFNY (franchise business)
• ~ year and half in production
• ~ 60 000 ticket...
• Social network
• chat based
• matching similar to Tinder :)
• few CRM/ERP applications
Context & our
Experience
What is API platform ?
–Fabien Potencier (creator of Symfony), SymfonyCon 2017
“API Platform is the most advanced API platform,
in any framework ...
• full stack framework dedicated to API-Driven projects
• contains a PHP library to create a fully featured APIs supportin...
• creating, retrieving, updating and deleting (CRUD) resources
• data validation
• pagination
• filtering
• sorting
• hype...
• GraphQL support
• Nice UI and machine-readable documentations (Swagger UI/
OpenAPI, GraphiQL…)
• authentication (Basic H...
• invalidation-based HTTP caching
• and basically everything needed to build modern APIs.
API Platform built-in
features:
Creating Simple CRUD
in a minute
Create
Entity
Step One
<?php
// src/Entity/Greeting.php
namespace AppEntity;
class Greeting
{
private $id;
public $name = ...
Create
Mapping
Step Two
# config/doctrine/Greeting.orm.yml
AppEntityGreeting:
type: entity
table: greeting
id:
id:
type: i...
Add
Validation
Step Three
# config/validator/greeting.yaml
AppEntityGreeting:
properties:
name:
- NotBlank: ~
Expose
Resource
Step Four # config/api_platform/resources.yaml
resources:
AppEntityGreeting: ~
Serialization Groups
User Management &
Security
• Avoid using FOSUserBundle
• not well suited with API
• Use Doctrine User Provider
• simple and easy to integrate
User ma...
// src/Entity/User.php
namespace AppEntity;
use SymfonyComponentSecurityCoreUserUserInterface;
use SymfonyComponentSeriali...
// src/Entity/User.php
namespace AppEntity;
use SymfonyComponentSecurityCoreUserUserInterface;
use SymfonyComponentSeriali...
// src/Entity/User.php
namespace AppEntity;
use SymfonyComponentSecurityCoreUserUserInterface;
use SymfonyComponentSeriali...
use ApiPlatformCoreDataPersisterContextAwareDataPersisterInterface;
use AppEntityUser;
use DoctrineORMEntityManagerInterfa...
• Lightweight and simple authentication system
• Stateless: token signed and verified server-side then stored client-
side...
• API Platform allows to easily add a JWT-based authentication to your
API using LexikJWTAuthenticationBundle.
• Maybe you...
User security
checker
Security
<?php
namespace AppSecurity;
use AppExceptionAccountDeletedException;
use AppSecurityUser a...
User security
checker
Security
# config/packages/security.yaml
# ...
security:
firewalls:
main:
pattern: ^/
user_checker: ...
Resource and
operation
level
Security
# api/config/api_platform/resources.yaml
AppEntityBook:
attributes:
security: 'is_gr...
Resource and
operation
level using
Voters
Security
# api/config/api_platform/resources.yaml
AppEntityBook:
itemOperations:...
• A JWT is self-contained, meaning that we can trust into its payload
for processing the authentication. In a nutshell, th...
JWT tip
A database-less user
provider
# config/packages/security.yaml
security:
providers:
jwt:
lexik_jwt: ~
security:
fir...
Creating
multi-language APIs
• Locastic Api Translation Bundle
• Translation bundle for ApiPlatform based on Sylius translation
• It requires two entit...
POST
translation
example
Multi-language API
{
"datetime":"2017-10-10",
"translations": {
"en":{
"title":"test",
"content":...
Get response by locale
GET /api/posts/1?locale=en

{
"@context": "/api/v1/contexts/Post",
"@id": "/api/v1/posts/1')",
"@ty...
Get response with all translations
GET /api/posts/1?groups[]=translations
{
"@context": "/api/v1/contexts/Post",
"@id": "/...
• Endpoint for creating new language
• Creates all Symfony translation files when new language is added
• Endpoint for edi...
Manipulating
the Context
Context
namespace AppEntity;
use ApiPlatformCoreAnnotationApiResource;
use SymfonyComponentSerial...
Manipulating
the Context
Context
# api/config/services.yaml
services:
# ...
'AppSerializerBookContextBuilder':
decorates: ...
// api/src/Serializer/BookContextBuilder.php
namespace AppSerializer;
use ApiPlatformCoreSerializerSerializerContextBuilde...
Symfony Messanger
Component
• The Messenger component helps applications send and receive
messages to/from other applications or via message queues.
•...
• Allows to implement the Command Query Responsibility Segregation
(CQRS) pattern in a convenient way.
• It also makes it ...
CQRS
Symfony Messenger & API Platform
AppEntityPasswordResetRequest:
collectionOperations:
post:
status: 202
itemOperation...
CQRS
Symfony Messenger & API Platform
<?php
namespace AppHandler;
use AppEntityPasswordResetRequest;
use SymfonyComponentM...
CQRS
/w DTO
Symfony Messenger & API Platform
AppEntityUser:
collectionOperations:
post:
status: 202
itemOperations: []
att...
CQRS
/w DTO
Symfony Messenger & API Platform
// api/src/Handler/ResetPasswordRequestHandler.php
namespace AppHandler;
use ...
<?php
namespace AppDataPersister;
use ApiPlatformCoreDataPersisterContextAwareDataPersisterInterface;
use AppEntityImageMe...
namespace AppEventSubscriber;
use ApiPlatformCoreEventListenerEventPriorities;
use AppEntityBook;
use SymfonyComponentEven...
• problem:
• different objects from source and in our database
• multiple sources of data (3rd party)
• DataTransform tran...
resources:
AppEntityOrder:
collectionOperations:
get: ~
exports:
method: POST
path: '/orders/export'
formats:
csv: ['text/...
Real-time applications
with API platform
• Redis + NodeJS
• Pusher
• ReactPHP
• …
• but to be honest PHP is not build for realtime :)
Real-time applications
with A...
• Fast, written in Go
• native browser support, no lib nor SDK required (built on top of HTTP and server-sent
events)
• co...
resources:
AppEntityGreeting:
attributes:
mercure: true
const eventSource = new EventSource('http://localhost:3000/hub?top...
Testing
• Unit tests
• test your logic, refactor your code using SOLID priciples
• Integration tests
• validation
• 3rd party inte...
• Ask yourself: “Am I sure the code I tested works as it should?”
• 100% coverage doesn’t guarantee your code is fully tes...
Handy testing tools
PHP Matcher
Library that enables you to check your response against patterns.
Faker
Library for generating random data
Postman tests
Newman + Postman
• Infection - tool for mutation testing
• PHPStan - focuses on finding errors in your code without actually
running it
• C...
Api Platform is awesome!
Conclusion
Thank you!
Questions?
Antonio Perić-Mažar
t: @antonioperic
m: antonio@locastic.com
Paula Čučuk
t: @paulala_14
m: paula@locastic.com
Using API platform to build ticketing system (translations, time zones, ...)  #sfday #verona
Using API platform to build ticketing system (translations, time zones, ...)  #sfday #verona
Using API platform to build ticketing system (translations, time zones, ...)  #sfday #verona
Using API platform to build ticketing system (translations, time zones, ...)  #sfday #verona
Using API platform to build ticketing system (translations, time zones, ...)  #sfday #verona
Using API platform to build ticketing system (translations, time zones, ...)  #sfday #verona
Using API platform to build ticketing system (translations, time zones, ...)  #sfday #verona
Using API platform to build ticketing system (translations, time zones, ...)  #sfday #verona
Using API platform to build ticketing system (translations, time zones, ...)  #sfday #verona
Using API platform to build ticketing system (translations, time zones, ...)  #sfday #verona
Using API platform to build ticketing system (translations, time zones, ...)  #sfday #verona
Using API platform to build ticketing system (translations, time zones, ...)  #sfday #verona
Using API platform to build ticketing system (translations, time zones, ...)  #sfday #verona
Prochain SlideShare
Chargement dans…5
×

Using API platform to build ticketing system (translations, time zones, ...) #sfday #verona

2 043 vues

Publié le

Why is API platform a way to go and the new standard in developing apps? In this talk, I want to show you some real examples that we built using API platform including a ticketing system for the world’s biggest bicycle marathon and a social network that is a mixture of both Tinder and Facebook Messenger. We had to tackle problems regarding the implementation of tax laws in 18 different countries, dozens of translations (including Arabic), multiple role systems, different timezones, overall struggle with a complicated logic with an infinite number of branches, and more.

Publié dans : Logiciels
  • Identifiez-vous pour voir les commentaires

  • Soyez le premier à aimer ceci

Using API platform to build ticketing system (translations, time zones, ...) #sfday #verona

  1. 1. Using API platform to build ticketing system Antonio Perić-Mažar, Locastic Paula Čučuk, Locastic 18.10.2019. - #sfday
  2. 2. Antonio Perić-Mažar CEO @ Locastic Co-founder @ Litto Co-founder @ Tinel Meetup t: @antonioperic m: antonio@locastic.com
  3. 3. Paula Čučuk Lead Backend Developer @ Locastic Partner @ Locastic t: @paulala_14 m: paula@locastic.com
  4. 4. Locastic Helping clients create web and mobile apps since 2011 • UX/UI • Mobile apps • Web apps • Training & Consulting www.locastic.com @locastic
  5. 5. • API Platform & Symfony • Ticketing platform: GFNY (franchise business) • ~ year and half in production • ~ 60 000 tickets released & race results stored in DB • ~ 20 000 users/racers, • ~ 60 users with admin roles • 48 events in 26 countries, users from 82 countries • 8 different languages including Hebrew and Indonesian Context & our Experience
  6. 6. • Social network • chat based • matching similar to Tinder :) • few CRM/ERP applications Context & our Experience
  7. 7. What is API platform ?
  8. 8. –Fabien Potencier (creator of Symfony), SymfonyCon 2017 “API Platform is the most advanced API platform, in any framework or language.”
  9. 9. • full stack framework dedicated to API-Driven projects • contains a PHP library to create a fully featured APIs supporting industry standards (JSON-LD, Hydra, GraphQL, OpenAPI…) • provides ambitious Javascript tooling to consume APIs in a snap • Symfony official API stack (instead of FOSRestBundle) • shipped with Docker and Kubernetes integration API Platform
  10. 10. • creating, retrieving, updating and deleting (CRUD) resources • data validation • pagination • filtering • sorting • hypermedia/HATEOAS and content negotiation support (JSON-LD and Hydra, JSON:API, HAL…) API Platform built-in features:
  11. 11. • GraphQL support • Nice UI and machine-readable documentations (Swagger UI/ OpenAPI, GraphiQL…) • authentication (Basic HTP, cookies as well as JWT and OAuth through extensions) • CORS headers • security checks and headers (tested against OWASP recommendations) API Platform built-in features:
  12. 12. • invalidation-based HTTP caching • and basically everything needed to build modern APIs. API Platform built-in features:
  13. 13. Creating Simple CRUD in a minute
  14. 14. Create Entity Step One <?php // src/Entity/Greeting.php namespace AppEntity; class Greeting { private $id; public $name = ''; public function getId(): int { return $this->id; } }
  15. 15. Create Mapping Step Two # config/doctrine/Greeting.orm.yml AppEntityGreeting: type: entity table: greeting id: id: type: integer generator: { strategy: AUTO } fields: name: type: string length: 100
  16. 16. Add Validation Step Three # config/validator/greeting.yaml AppEntityGreeting: properties: name: - NotBlank: ~
  17. 17. Expose Resource Step Four # config/api_platform/resources.yaml resources: AppEntityGreeting: ~
  18. 18. Serialization Groups
  19. 19. User Management & Security
  20. 20. • Avoid using FOSUserBundle • not well suited with API • Use Doctrine User Provider • simple and easy to integrate User management
  21. 21. // src/Entity/User.php namespace AppEntity; use SymfonyComponentSecurityCoreUserUserInterface; use SymfonyComponentSerializerAnnotationGroups; class User implements UserInterface { /** * @Groups({"user-read"}) */ private $id; /** * @Groups({"user-write", "user-read"}) */ private $email; /** * @Groups({"user-read"}) */ private $roles = []; /** * @Groups({"user-write"}) */ private $plainPassword; private $password; … getters and setters … }
  22. 22. // src/Entity/User.php namespace AppEntity; use SymfonyComponentSecurityCoreUserUserInterface; use SymfonyComponentSerializerAnnotationGroups; class User implements UserInterface { /** * @Groups({"user-read"}) */ private $id; /** * @Groups({"user-write", "user-read"}) */ private $email; /** * @Groups({"user-read"}) */ private $roles = []; /** * @Groups({"user-write"}) */ private $plainPassword; private $password; … getters and setters … } # config/doctrine/User.orm.yml AppEntityUser: type: entity table: users repositoryClass: AppRepositoryUserRepository id: id: type: integer generator: { strategy: AUTO } fields: email: type: string length: 255 password: type: string length: 255 roles: type: array
  23. 23. // src/Entity/User.php namespace AppEntity; use SymfonyComponentSecurityCoreUserUserInterface; use SymfonyComponentSerializerAnnotationGroups; class User implements UserInterface { /** * @Groups({"user-read"}) */ private $id; /** * @Groups({"user-write", "user-read"}) */ private $email; /** * @Groups({"user-read"}) */ private $roles = []; /** * @Groups({"user-write"}) */ private $plainPassword; private $password; … getters and setters … } # config/doctrine/User.orm.yml AppEntityUser: type: entity table: users repositoryClass: AppRepositoryUserRepository id: id: type: integer generator: { strategy: AUTO } fields: email: type: string length: 255 password: type: string length: 255 roles: type: array # config/api_platform/resources.yaml resources: AppEntityUser: attributes: normalization_context: groups: ['user-read'] denormalization_context: groups: ['user-write']
  24. 24. use ApiPlatformCoreDataPersisterContextAwareDataPersisterInterface; use AppEntityUser; use DoctrineORMEntityManagerInterface; use SymfonyComponentSecurityCoreEncoderUserPasswordEncoderInterface; class UserDataPersister implements ContextAwareDataPersisterInterface { private $entityManager; private $userPasswordEncoder; public function __construct(EntityManagerInterface $entityManager, UserPasswordEncoderInterface $userPasswordEncoder) { $this->entityManager = $entityManager; $this->userPasswordEncoder = $userPasswordEncoder; } public function supports($data, array $context = []): bool { return $data instanceof User; } public function persist($data, array $context = []) { /** @var User $data */ if ($data->getPlainPassword()) { $data->setPassword( $this->userPasswordEncoder->encodePassword($data, $data->getPlainPassword()) ); $data->eraseCredentials(); } $this->entityManager->persist($data); $this->entityManager->flush($data); return $data; } public function remove($data, array $context = []) { $this->entityManager->remove($data); $this->entityManager->flush(); }
  25. 25. • Lightweight and simple authentication system • Stateless: token signed and verified server-side then stored client- side and sent with each request in an Authorization header • Store the token in the browser local storage JSON Web Token (JWT)
  26. 26. • API Platform allows to easily add a JWT-based authentication to your API using LexikJWTAuthenticationBundle. • Maybe you want to use a refresh token to renew your JWT. In this case you can check JWTRefreshTokenBundle. User authentication
  27. 27. User security checker Security <?php namespace AppSecurity; use AppExceptionAccountDeletedException; use AppSecurityUser as AppUser; use SymfonyComponentSecurityCoreExceptionAccountExpiredException; use SymfonyComponentSecurityCoreExceptionCustomUserMessageAuthenticat use SymfonyComponentSecurityCoreUserUserCheckerInterface; use SymfonyComponentSecurityCoreUserUserInterface; class UserChecker implements UserCheckerInterface { public function checkPreAuth(UserInterface $user) { if (!$user instanceof AppUser) { return; } // user is deleted, show a generic Account Not Found message. if ($user->isDeleted()) { throw new AccountDeletedException(); } } public function checkPostAuth(UserInterface $user) { if (!$user instanceof AppUser) { return; } // user account is expired, the user may be notified if ($user->isExpired()) { throw new AccountExpiredException('...'); } } }
  28. 28. User security checker Security # config/packages/security.yaml # ... security: firewalls: main: pattern: ^/ user_checker: AppSecurityUserChecker # ...
  29. 29. Resource and operation level Security # api/config/api_platform/resources.yaml AppEntityBook: attributes: security: 'is_granted("ROLE_USER")' collectionOperations: get: ~ post: security: 'is_granted("ROLE_ADMIN")' itemOperations: get: ~ put: security_: 'is_granted("ROLE_ADMIN") or object.owner == user'
  30. 30. Resource and operation level using Voters Security # api/config/api_platform/resources.yaml AppEntityBook: itemOperations: get: security_: 'is_granted('READ', object)' put: security_: 'is_granted('UPDATE', object)'
  31. 31. • A JWT is self-contained, meaning that we can trust into its payload for processing the authentication. In a nutshell, there should be no need for loading the user from the database when authenticating a JWT Token, the database should be hit only once for delivering the token. • It means you will have to fetch the User entity from the database yourself as needed (probably through the Doctrine EntityManager). JWT tip A database-less user provider
  32. 32. JWT tip A database-less user provider # config/packages/security.yaml security: providers: jwt: lexik_jwt: ~ security: firewalls: api: provider: jwt guard: # ...
  33. 33. Creating multi-language APIs
  34. 34. • Locastic Api Translation Bundle • Translation bundle for ApiPlatform based on Sylius translation • It requires two entities: Translatable & Translation entity • Open source • https://github.com/Locastic/ApiPlatformTranslationBundle • https://locastic.com/blog/having-troubles-with-implementing- translations-in-apiplatform/ Creating multi-language APIs
  35. 35. POST translation example Multi-language API { "datetime":"2017-10-10", "translations": { "en":{ "title":"test", "content":"test", "locale":"en" }, "de":{ "title":"test de", "content":"test de", "locale":"de" } } }
  36. 36. Get response by locale GET /api/posts/1?locale=en { "@context": "/api/v1/contexts/Post", "@id": "/api/v1/posts/1')", "@type": "Post", "id": 1, "datetime":"2019-10-10", "title":"Hello world", "content":"Hello from Verona!" }
  37. 37. Get response with all translations GET /api/posts/1?groups[]=translations { "@context": "/api/v1/contexts/Post", "@id": "/api/v1/posts/1')", "@type": "Post", "id": 1, "datetime":"2019-10-10", "translations": { "en":{ "title":"Hello world", "content":"Hello from Verona!", "locale":"en" }, "it":{ "title":"Ciao mondo", "content":"Ciao da Verona!", "locale":"it" } } }
  38. 38. • Endpoint for creating new language • Creates all Symfony translation files when new language is added • Endpoint for editing each language translation files Adding languages and translations dynamically
  39. 39. Manipulating the Context Context namespace AppEntity; use ApiPlatformCoreAnnotationApiResource; use SymfonyComponentSerializerAnnotationGroups; /** * @ApiResource( * normalizationContext={"groups"={"book:output"}}, * denormalizationContext={"groups"={"book:input"}} * ) */ class Book { // ... /** * This field can be managed only by an admin * * @var bool * * @Groups({"book:output", "admin:input"}) */ public $active = false; /** * This field can be managed by any user * * @var string * * @Groups({"book:output", "book:input"}) */ public $name; // ... }
  40. 40. Manipulating the Context Context # api/config/services.yaml services: # ... 'AppSerializerBookContextBuilder': decorates: 'api_platform.serializer.context_builder' arguments: [ '@AppSerializerBookContextBuilder.inner' ] autoconfigure: false
  41. 41. // api/src/Serializer/BookContextBuilder.php namespace AppSerializer; use ApiPlatformCoreSerializerSerializerContextBuilderInterface; use SymfonyComponentHttpFoundationRequest; use SymfonyComponentSecurityCoreAuthorizationAuthorizationCheckerInterface; use AppEntityBook; final class BookContextBuilder implements SerializerContextBuilderInterface { private $decorated; private $authorizationChecker; public function __construct(SerializerContextBuilderInterface $decorated, AuthorizationCheckerInterface $authorizationChecker) { $this->decorated = $decorated; $this->authorizationChecker = $authorizationChecker; } public function createFromRequest(Request $request, bool $normalization, ?array $extractedAttributes = null): array { $context = $this->decorated->createFromRequest($request, $normalization, $extractedAttributes); $resourceClass = $context['resource_class'] ?? null; if ($resourceClass === Book::class && isset($context['groups']) && $this->authorizationChecker- >isGranted('ROLE_ADMIN') && false === $normalization) { $context['groups'][] = 'admin:input'; } return $context; } }
  42. 42. Symfony Messanger Component
  43. 43. • The Messenger component helps applications send and receive messages to/from other applications or via message queues. • Easy to implement • Making async easy • Many transports are supported to dispatch messages to async consumers, including RabbitMQ, Apache Kafka, Amazon SQS and Google Pub/Sub. Symfony Messenger
  44. 44. • Allows to implement the Command Query Responsibility Segregation (CQRS) pattern in a convenient way. • It also makes it easy to send messages through the web API that will be consumed asynchronously. • Async import, export, image processing… any heavy work Symfony Messenger & API Platform
  45. 45. CQRS Symfony Messenger & API Platform AppEntityPasswordResetRequest: collectionOperations: post: status: 202 itemOperations: [] attributes: messenger: true output: false
  46. 46. CQRS Symfony Messenger & API Platform <?php namespace AppHandler; use AppEntityPasswordResetRequest; use SymfonyComponentMessengerHandlerMessageHandlerInterfac final class PasswordResetRequestHandler implements MessageHand { public function __invoke(PasswordResetRequest $forgotPassw { // do some heavy things } } <?php namespace AppEntity; final class PasswordResetRequest { public $email; }
  47. 47. CQRS /w DTO Symfony Messenger & API Platform AppEntityUser: collectionOperations: post: status: 202 itemOperations: [] attributes: messenger: “input” input: “ResetPasswordRequest::class” output: false // api/src/Entity/User.php namespace AppEntity; use ApiPlatformCoreAnnotationApiResource; use AppDtoResetPasswordRequest; final class User { }
  48. 48. CQRS /w DTO Symfony Messenger & API Platform // api/src/Handler/ResetPasswordRequestHandler.php namespace AppHandler; use AppDtoResetPasswordRequest; use SymfonyComponentMessengerHandlerMessageHandlerInterface; final class ResetPasswordRequestHandler implements MessageHandle { public function __invoke(ResetPasswordRequest $forgotPasswor { // do something with the resource } } // api/src/Dto/ResetPasswordRequest.php namespace AppDto; use SymfonyComponentValidatorConstraints as Assert; final class ResetPasswordRequest { public $var; }
  49. 49. <?php namespace AppDataPersister; use ApiPlatformCoreDataPersisterContextAwareDataPersisterInterface; use AppEntityImageMedia; use DoctrineORMEntityManagerInterface; use SymfonyComponentMessengerMessageBusInterface; class ImageMediaDataPersister implements ContextAwareDataPersisterInterface { private $entityManager; private $messageBus; public function __construct(EntityManagerInterface $entityManager, MessageBusInterface $messageBus) { $this->entityManager = $entityManager; $this->messageBus = $messageBus; } public function supports($data, array $context = []): bool { return $data instanceof ImageMedia; } public function persist($data, array $context = []) { $this->entityManager->persist($data); $this->entityManager->flush($data); $this->messageBus->dispatch(new ProcessImageMessage($data->getId())); return $data; } public function remove($data, array $context = []) { $this->entityManager->remove($data); $this->entityManager->flush(); $this->messageBus->dispatch(new DeleteImageMessage($data->getId())); } }
  50. 50. namespace AppEventSubscriber; use ApiPlatformCoreEventListenerEventPriorities; use AppEntityBook; use SymfonyComponentEventDispatcherEventSubscriberInterface; use SymfonyComponentHttpFoundationRequest; use SymfonyComponentHttpKernelEventViewEvent; use SymfonyComponentHttpKernelKernelEvents; use SymfonyComponentMessengerMessageBusInterface; final class BookMailSubscriber implements EventSubscriberInterface { private $messageBus; public function __construct(MessageBusInterface $messageBus) { $this->messageBus = $messageBus; } public static function getSubscribedEvents() { return [ KernelEvents::VIEW => ['sendMail', EventPriorities::POST_WRITE], ]; } public function sendMail(ViewEvent $event) { $book = $event->getControllerResult(); $method = $event->getRequest()->getMethod(); if (!$book instanceof Book || Request::METHOD_POST !== $method) { return; } // send to all users 2M that new book has arrived this->messageBus->dispatch(new SendEmailMessage(‘new-book’, $book->getTitle())); } }
  51. 51. • problem: • different objects from source and in our database • multiple sources of data (3rd party) • DataTransform transforms from source object to our object • exporting to CSV files Using DTOs with import and export
  52. 52. resources: AppEntityOrder: collectionOperations: get: ~ exports: method: POST path: '/orders/export' formats: csv: ['text/csv'] pagination_enabled: false output: "OrderExport::class" normalization_context: groups: ['order-export']
  53. 53. Real-time applications with API platform
  54. 54. • Redis + NodeJS • Pusher • ReactPHP • … • but to be honest PHP is not build for realtime :) Real-time applications with API platform
  55. 55. • Fast, written in Go • native browser support, no lib nor SDK required (built on top of HTTP and server-sent events) • compatible with all existing servers, even those who don't support persistent connections (serverless architecture, PHP, FastCGI…) • Automatic HTTP/2 and HTTPS (using Let's Encrypt) support • CORS support, CSRF protection mechanism • Cloud Native, follows the Twelve-Factor App methodology • Open source (AGPL) • … Mercure
  56. 56. resources: AppEntityGreeting: attributes: mercure: true const eventSource = new EventSource('http://localhost:3000/hub?topic=' + encodeURIComponent('http://example.com/greeting/1')); eventSource.onmessage = event => { // Will be called every time an update is published by the server console.log(JSON.parse(event.data)); }
  57. 57. Testing
  58. 58. • Unit tests • test your logic, refactor your code using SOLID priciples • Integration tests • validation • 3rd party integrations • database queries • Functional tests • response code, header and content (expected fields in expected format) Type of tests
  59. 59. • Ask yourself: “Am I sure the code I tested works as it should?” • 100% coverage doesn’t guarantee your code is fully tested and working • Write test first is just one of the approaches • Legacy code: • Start replicating bugs with tests before fixing them • Test at least most important and critical parts Testing tips and tricks
  60. 60. Handy testing tools
  61. 61. PHP Matcher Library that enables you to check your response against patterns.
  62. 62. Faker Library for generating random data
  63. 63. Postman tests Newman + Postman
  64. 64. • Infection - tool for mutation testing • PHPStan - focuses on finding errors in your code without actually running it • Continuous integration (CI) -  enables you to run your tests on git on each commit Tools for checking test quality
  65. 65. Api Platform is awesome! Conclusion
  66. 66. Thank you!
  67. 67. Questions? Antonio Perić-Mažar t: @antonioperic m: antonio@locastic.com Paula Čučuk t: @paulala_14 m: paula@locastic.com

×