SlideShare a Scribd company logo
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
www.locastic.com
@locastic
Using API platform to build ticketing system (translations, time zones, ...)  #sfday #verona
• 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
• 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 or language.”
• 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
• 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:
• 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:
• 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 = '';
public function getId(): int
{
return $this->id;
}
}
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
Add
Validation
Step Three
# config/validator/greeting.yaml
AppEntityGreeting:
properties:
name:
- NotBlank: ~
Expose
Resource
Step Four # config/api_platform/resources.yaml
resources:
AppEntityGreeting: ~
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
Serialization Groups
User Management &
Security
• Avoid using FOSUserBundle
• not well suited with API
• Use Doctrine User Provider
• simple and easy to integrate
User management
// 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 …
}
// 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
// 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']
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();
}
• 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)
Using API platform to build ticketing system (translations, time zones, ...)  #sfday #verona
Using API platform to build ticketing system (translations, time zones, ...)  #sfday #verona
• 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
Using API platform to build ticketing system (translations, time zones, ...)  #sfday #verona
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('...');
}
}
}
User security
checker
Security
# config/packages/security.yaml
# ...
security:
firewalls:
main:
pattern: ^/
user_checker: AppSecurityUserChecker
# ...
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'
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)'
• 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
JWT tip
A database-less user
provider
# config/packages/security.yaml
security:
providers:
jwt:
lexik_jwt: ~
security:
firewalls:
api:
provider: jwt
guard:
# ...
Creating
multi-language APIs
• 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
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"
}
}
}
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!"
}
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"
}
}
}
• 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
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;
// ...
}
Manipulating
the Context
Context
# api/config/services.yaml
services:
# ...
'AppSerializerBookContextBuilder':
decorates: 'api_platform.serializer.context_builder'
arguments: [ '@AppSerializerBookContextBuilder.inner' ]
autoconfigure: false
// 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;
}
}
Symfony Messanger
Component
• 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
Using API platform to build ticketing system (translations, time zones, ...)  #sfday #verona
Using API platform to build ticketing system (translations, time zones, ...)  #sfday #verona
• 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
CQRS
Symfony Messenger & API Platform
AppEntityPasswordResetRequest:
collectionOperations:
post:
status: 202
itemOperations: []
attributes:
messenger: true
output: false
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;
}
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
{
}
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;
}
<?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()));
}
}
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()));
}
}
• 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
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']
Real-time applications
with API platform
Using API platform to build ticketing system (translations, time zones, ...)  #sfday #verona
• Redis + NodeJS
• Pusher
• ReactPHP
• …
• but to be honest PHP is not build for realtime :)
Real-time applications
with API platform
Using API platform to build ticketing system (translations, time zones, ...)  #sfday #verona
• 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
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));
}
Testing
Using API platform to build ticketing system (translations, time zones, ...)  #sfday #verona
• 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
• 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
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
• Continuous integration (CI) -  enables you to run your tests on git on
each commit
Tools for checking test
quality
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

More Related Content

PDF
Using API Platform to build ticketing system #symfonycon
Antonio Peric-Mazar
 
PDF
REST easy with API Platform
Antonio Peric-Mazar
 
PDF
Create Your Own Framework by Fabien Potencier
Himel Nag Rana
 
ODP
Dependency Injection, Zend Framework and Symfony Container
Diego Lewin
 
PPTX
Zend Studio Tips and Tricks
Roy Ganor
 
PPTX
Xamarin.Android Introduction
Guido Magrin
 
PPTX
Internet of things the salesforce lego machine cloud
andyinthecloud
 
PPTX
[Devoxx Morocco 2015] Apache Cordova In Action
Hazem Saleh
 
Using API Platform to build ticketing system #symfonycon
Antonio Peric-Mazar
 
REST easy with API Platform
Antonio Peric-Mazar
 
Create Your Own Framework by Fabien Potencier
Himel Nag Rana
 
Dependency Injection, Zend Framework and Symfony Container
Diego Lewin
 
Zend Studio Tips and Tricks
Roy Ganor
 
Xamarin.Android Introduction
Guido Magrin
 
Internet of things the salesforce lego machine cloud
andyinthecloud
 
[Devoxx Morocco 2015] Apache Cordova In Action
Hazem Saleh
 

What's hot (20)

PDF
Baruco 2014 - Rubymotion Workshop
Brian Sam-Bodden
 
PPTX
C# fundamentals Part 2
iFour Institute - Sustainable Learning
 
PPTX
LEARNING  iPAD STORYBOARDS IN OBJ-­‐C LESSON 1
Rich Helton
 
PDF
Cross-platform mobile apps with Apache Cordova
Ivano Malavolta
 
PPTX
React Native
Heber Silva
 
PPTX
[JavaLand 2015] Developing JavaScript Mobile Apps Using Apache Cordova
Hazem Saleh
 
PPTX
[JMaghreb 2014] Developing JavaScript Mobile Apps Using Apache Cordova
Hazem Saleh
 
PDF
Design pattern in Symfony2 - Nanos gigantium humeris insidentes
Giulio De Donato
 
PDF
I pad uicatalog_lesson02
Rich Helton
 
PDF
Cross Platform Mobile Apps with the Ionic Framework
Troy Miles
 
PDF
PhoneGap: Accessing Device Capabilities
Ivano Malavolta
 
PDF
Building Mobile Friendly APIs in Rails
Jim Jeffers
 
PPTX
Swift meetup22june2015
Claire Townend Gee
 
PPTX
Automating the Gaps of Unit Testing Mobile Apps
Geoffrey Goetz
 
PPTX
JavaScript on HP webOS: Enyo and Node.js
Ben Combee
 
PDF
CapitolJS: Enyo, Node.js, & the State of webOS
Ben Combee
 
PPTX
Accessibility: A Journey to Accessible Rich Components
Achievers Tech
 
PPTX
Learning C# iPad Programming
Rich Helton
 
PDF
Understanding Identity in the World of Web APIs – Ronnie Mitra, API Architec...
CA API Management
 
PPTX
Documenting REST APIs
Tom Johnson
 
Baruco 2014 - Rubymotion Workshop
Brian Sam-Bodden
 
LEARNING  iPAD STORYBOARDS IN OBJ-­‐C LESSON 1
Rich Helton
 
Cross-platform mobile apps with Apache Cordova
Ivano Malavolta
 
React Native
Heber Silva
 
[JavaLand 2015] Developing JavaScript Mobile Apps Using Apache Cordova
Hazem Saleh
 
[JMaghreb 2014] Developing JavaScript Mobile Apps Using Apache Cordova
Hazem Saleh
 
Design pattern in Symfony2 - Nanos gigantium humeris insidentes
Giulio De Donato
 
I pad uicatalog_lesson02
Rich Helton
 
Cross Platform Mobile Apps with the Ionic Framework
Troy Miles
 
PhoneGap: Accessing Device Capabilities
Ivano Malavolta
 
Building Mobile Friendly APIs in Rails
Jim Jeffers
 
Swift meetup22june2015
Claire Townend Gee
 
Automating the Gaps of Unit Testing Mobile Apps
Geoffrey Goetz
 
JavaScript on HP webOS: Enyo and Node.js
Ben Combee
 
CapitolJS: Enyo, Node.js, & the State of webOS
Ben Combee
 
Accessibility: A Journey to Accessible Rich Components
Achievers Tech
 
Learning C# iPad Programming
Rich Helton
 
Understanding Identity in the World of Web APIs – Ronnie Mitra, API Architec...
CA API Management
 
Documenting REST APIs
Tom Johnson
 
Ad

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

PDF
Introduction to Titanium and how to connect with a PHP backend
Joseluis Laso
 
PDF
Building TweetEngine
ikailan
 
PPTX
API Workshop: Deep dive into REST APIs
Tom Johnson
 
PDF
Introduction to Google App Engine
Kanda Runapongsa Saikaew
 
PPTX
Telerik AppBuilder Presentation for TelerikNEXT Conference
Jen Looper
 
PDF
Checkmarx meetup API Security - API Security in depth - Inon Shkedy
Adar Weidman
 
PDF
Don't worry be API with Slim framework and Joomla
Pierre-André Vullioud
 
PPTX
How to build Simple yet powerful API.pptx
Channa Ly
 
PDF
Modern Web Applications Utilizing HTML5 APIs
Ido Green
 
PDF
Leveraging Playwright for API Testing.pdf
Steve Wortham
 
PDF
Building APIs in an easy way using API Platform
Antonio Peric-Mazar
 
PDF
Google App Engine Overview - BarCamp Phnom Penh 2011
traactivity
 
PDF
API Security - OWASP top 10 for APIs + tips for pentesters
Inon Shkedy
 
PDF
Utilizing HTML5 APIs
Ido Green
 
PDF
Developer Tutorial: WebAuthn for Web & FIDO2 for Android
FIDO Alliance
 
ODP
Creating REST Applications with the Slim Micro-Framework by Vikram Vaswani
vvaswani
 
PDF
Hexagonal architecture
Alessandro Minoccheri
 
PPTX
Developing Apps with Azure AD
SharePointRadi
 
PDF
automation framework
ANSHU GOYAL
 
PDF
FIDO2 Specifications Overview
FIDO Alliance
 
Introduction to Titanium and how to connect with a PHP backend
Joseluis Laso
 
Building TweetEngine
ikailan
 
API Workshop: Deep dive into REST APIs
Tom Johnson
 
Introduction to Google App Engine
Kanda Runapongsa Saikaew
 
Telerik AppBuilder Presentation for TelerikNEXT Conference
Jen Looper
 
Checkmarx meetup API Security - API Security in depth - Inon Shkedy
Adar Weidman
 
Don't worry be API with Slim framework and Joomla
Pierre-André Vullioud
 
How to build Simple yet powerful API.pptx
Channa Ly
 
Modern Web Applications Utilizing HTML5 APIs
Ido Green
 
Leveraging Playwright for API Testing.pdf
Steve Wortham
 
Building APIs in an easy way using API Platform
Antonio Peric-Mazar
 
Google App Engine Overview - BarCamp Phnom Penh 2011
traactivity
 
API Security - OWASP top 10 for APIs + tips for pentesters
Inon Shkedy
 
Utilizing HTML5 APIs
Ido Green
 
Developer Tutorial: WebAuthn for Web & FIDO2 for Android
FIDO Alliance
 
Creating REST Applications with the Slim Micro-Framework by Vikram Vaswani
vvaswani
 
Hexagonal architecture
Alessandro Minoccheri
 
Developing Apps with Azure AD
SharePointRadi
 
automation framework
ANSHU GOYAL
 
FIDO2 Specifications Overview
FIDO Alliance
 
Ad

More from Antonio Peric-Mazar (20)

PDF
You call yourself a Senior Developer?
Antonio Peric-Mazar
 
PDF
Are you failing at being agile? #digitallabin
Antonio Peric-Mazar
 
PDF
Symfony 4: A new way to develop applications #ipc19
Antonio Peric-Mazar
 
PDF
A year with progressive web apps! #webinale
Antonio Peric-Mazar
 
PDF
The UI is the THE application #dpc19
Antonio Peric-Mazar
 
PDF
Symfony 4: A new way to develop applications #phpsrb
Antonio Peric-Mazar
 
PDF
A year with progressive web apps! #DevConMU
Antonio Peric-Mazar
 
PDF
Service workers are your best friends
Antonio Peric-Mazar
 
PDF
Progressive Web Apps are here!
Antonio Peric-Mazar
 
PDF
Building APIs in an easy way using API Platform
Antonio Peric-Mazar
 
PDF
Symfony4 - A new way of developing web applications
Antonio Peric-Mazar
 
PDF
Build your business on top of Open Source
Antonio Peric-Mazar
 
PDF
Lessons learned while developing with Sylius
Antonio Peric-Mazar
 
PDF
Drupal8 for Symfony developers - Dutch PHP
Antonio Peric-Mazar
 
PDF
Drupal8 for Symfony Developers (PHP Day Verona 2017)
Antonio Peric-Mazar
 
PDF
Drupal8 for Symfony Developers
Antonio Peric-Mazar
 
PDF
Maintainable + Extensible = Clean ... yes, Code!
Antonio Peric-Mazar
 
PDF
A recipe for effective leadership
Antonio Peric-Mazar
 
PDF
Building real time applications with Symfony2
Antonio Peric-Mazar
 
PPT
Building Single Page Application (SPA) with Symfony2 and AngularJS
Antonio Peric-Mazar
 
You call yourself a Senior Developer?
Antonio Peric-Mazar
 
Are you failing at being agile? #digitallabin
Antonio Peric-Mazar
 
Symfony 4: A new way to develop applications #ipc19
Antonio Peric-Mazar
 
A year with progressive web apps! #webinale
Antonio Peric-Mazar
 
The UI is the THE application #dpc19
Antonio Peric-Mazar
 
Symfony 4: A new way to develop applications #phpsrb
Antonio Peric-Mazar
 
A year with progressive web apps! #DevConMU
Antonio Peric-Mazar
 
Service workers are your best friends
Antonio Peric-Mazar
 
Progressive Web Apps are here!
Antonio Peric-Mazar
 
Building APIs in an easy way using API Platform
Antonio Peric-Mazar
 
Symfony4 - A new way of developing web applications
Antonio Peric-Mazar
 
Build your business on top of Open Source
Antonio Peric-Mazar
 
Lessons learned while developing with Sylius
Antonio Peric-Mazar
 
Drupal8 for Symfony developers - Dutch PHP
Antonio Peric-Mazar
 
Drupal8 for Symfony Developers (PHP Day Verona 2017)
Antonio Peric-Mazar
 
Drupal8 for Symfony Developers
Antonio Peric-Mazar
 
Maintainable + Extensible = Clean ... yes, Code!
Antonio Peric-Mazar
 
A recipe for effective leadership
Antonio Peric-Mazar
 
Building real time applications with Symfony2
Antonio Peric-Mazar
 
Building Single Page Application (SPA) with Symfony2 and AngularJS
Antonio Peric-Mazar
 

Recently uploaded (20)

PPT
Why Reliable Server Maintenance Service in New York is Crucial for Your Business
Sam Vohra
 
PDF
QAware_Mario-Leander_Reimer_Architecting and Building a K8s-based AI Platform...
QAware GmbH
 
PDF
Build Multi-agent using Agent Development Kit
FadyIbrahim23
 
PPTX
TestNG for Java Testing and Automation testing
ssuser0213cb
 
PDF
Why Use Open Source Reporting Tools for Business Intelligence.pdf
Varsha Nayak
 
PDF
What to consider before purchasing Microsoft 365 Business Premium_PDF.pdf
Q-Advise
 
DOCX
Can You Build Dashboards Using Open Source Visualization Tool.docx
Varsha Nayak
 
PDF
Micromaid: A simple Mermaid-like chart generator for Pharo
ESUG
 
PDF
49785682629390197565_LRN3014_Migrating_the_Beast.pdf
Abilash868456
 
PDF
Salesforce Implementation Services Provider.pdf
VALiNTRY360
 
PPTX
Presentation about variables and constant.pptx
safalsingh810
 
PDF
Wondershare Filmora 14.5.20.12999 Crack Full New Version 2025
gsgssg2211
 
PPTX
ConcordeApp: Engineering Global Impact & Unlocking Billions in Event ROI with AI
chastechaste14
 
PDF
Key Features to Look for in Arizona App Development Services
Net-Craft.com
 
PPTX
The-Dawn-of-AI-Reshaping-Our-World.pptxx
parthbhanushali307
 
PDF
Become an Agentblazer Champion Challenge
Dele Amefo
 
PPTX
slidesgo-unlocking-the-code-the-dynamic-dance-of-variables-and-constants-2024...
kr2589474
 
PPTX
Web Testing.pptx528278vshbuqffqhhqiwnwuq
studylike474
 
PPTX
oapresentation.pptx
mehatdhavalrajubhai
 
PPTX
Smart Panchayat Raj e-Governance App.pptx
Rohitnikam33
 
Why Reliable Server Maintenance Service in New York is Crucial for Your Business
Sam Vohra
 
QAware_Mario-Leander_Reimer_Architecting and Building a K8s-based AI Platform...
QAware GmbH
 
Build Multi-agent using Agent Development Kit
FadyIbrahim23
 
TestNG for Java Testing and Automation testing
ssuser0213cb
 
Why Use Open Source Reporting Tools for Business Intelligence.pdf
Varsha Nayak
 
What to consider before purchasing Microsoft 365 Business Premium_PDF.pdf
Q-Advise
 
Can You Build Dashboards Using Open Source Visualization Tool.docx
Varsha Nayak
 
Micromaid: A simple Mermaid-like chart generator for Pharo
ESUG
 
49785682629390197565_LRN3014_Migrating_the_Beast.pdf
Abilash868456
 
Salesforce Implementation Services Provider.pdf
VALiNTRY360
 
Presentation about variables and constant.pptx
safalsingh810
 
Wondershare Filmora 14.5.20.12999 Crack Full New Version 2025
gsgssg2211
 
ConcordeApp: Engineering Global Impact & Unlocking Billions in Event ROI with AI
chastechaste14
 
Key Features to Look for in Arizona App Development Services
Net-Craft.com
 
The-Dawn-of-AI-Reshaping-Our-World.pptxx
parthbhanushali307
 
Become an Agentblazer Champion Challenge
Dele Amefo
 
slidesgo-unlocking-the-code-the-dynamic-dance-of-variables-and-constants-2024...
kr2589474
 
Web Testing.pptx528278vshbuqffqhhqiwnwuq
studylike474
 
oapresentation.pptx
mehatdhavalrajubhai
 
Smart Panchayat Raj e-Governance App.pptx
Rohitnikam33
 

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

  • 1. Using API platform to build ticketing system Antonio Perić-Mažar, Locastic Paula Čučuk, Locastic 18.10.2019. - #sfday
  • 2. Antonio Perić-Mažar CEO @ Locastic Co-founder @ Litto Co-founder @ Tinel Meetup t: @antonioperic m: [email protected]
  • 3. Paula Čučuk Lead Backend Developer @ Locastic Partner @ Locastic t: @paulala_14 m: [email protected]
  • 4. Locastic Helping clients create web and mobile apps since 2011 • UX/UI • Mobile apps • Web apps • Training & Consulting www.locastic.com @locastic
  • 6. • 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
  • 7. • Social network • chat based • matching similar to Tinder :) • few CRM/ERP applications Context & our Experience
  • 8. What is API platform ?
  • 9. –Fabien Potencier (creator of Symfony), SymfonyCon 2017 “API Platform is the most advanced API platform, in any framework or language.”
  • 10. • 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
  • 11. • 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:
  • 12. • 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:
  • 13. • invalidation-based HTTP caching • and basically everything needed to build modern APIs. API Platform built-in features:
  • 15. Create Entity Step One <?php // src/Entity/Greeting.php namespace AppEntity; class Greeting { private $id; public $name = ''; public function getId(): int { return $this->id; } }
  • 16. 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
  • 18. Expose Resource Step Four # config/api_platform/resources.yaml resources: AppEntityGreeting: ~
  • 25. • Avoid using FOSUserBundle • not well suited with API • Use Doctrine User Provider • simple and easy to integrate User management
  • 26. // 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 … }
  • 27. // 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
  • 28. // 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']
  • 29. 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(); }
  • 30. • 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)
  • 33. • 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
  • 35. 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('...'); } } }
  • 36. User security checker Security # config/packages/security.yaml # ... security: firewalls: main: pattern: ^/ user_checker: AppSecurityUserChecker # ...
  • 37. 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'
  • 38. 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)'
  • 39. • 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
  • 40. JWT tip A database-less user provider # config/packages/security.yaml security: providers: jwt: lexik_jwt: ~ security: firewalls: api: provider: jwt guard: # ...
  • 42. • 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
  • 44. 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!" }
  • 45. 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" } } }
  • 46. • 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
  • 47. 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; // ... }
  • 48. Manipulating the Context Context # api/config/services.yaml services: # ... 'AppSerializerBookContextBuilder': decorates: 'api_platform.serializer.context_builder' arguments: [ '@AppSerializerBookContextBuilder.inner' ] autoconfigure: false
  • 49. // 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; } }
  • 51. • 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
  • 54. • 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
  • 55. CQRS Symfony Messenger & API Platform AppEntityPasswordResetRequest: collectionOperations: post: status: 202 itemOperations: [] attributes: messenger: true output: false
  • 56. 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; }
  • 57. 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 { }
  • 58. 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; }
  • 59. <?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())); } }
  • 60. 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())); } }
  • 61. • 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
  • 62. 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']
  • 65. • Redis + NodeJS • Pusher • ReactPHP • … • but to be honest PHP is not build for realtime :) Real-time applications with API platform
  • 67. • 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
  • 68. 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)); }
  • 71. • 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
  • 72. • 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
  • 74. PHP Matcher Library that enables you to check your response against patterns.
  • 77. • 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
  • 78. Api Platform is awesome! Conclusion