# Sf4 ## Symfony Architecture Getting started: Setup, creating pages, routing, controllers, templates, config, components, bundles, reference. **Symfony Flex**: It's not a symfony version it's a tool that replaces symfony installer and symfony standar edition.**Automates the most common tasks of Symfony applications**, like installing and removing bundles and other Composer dependencies. Symfony Flex works for Symfony 3.3 and higher. Starting from Symfony 4.0, Flex should be used by default, but it is still optional. - alias system: shortcuts to packages (symfony.sh) - recipies can modify: symfony.lock (managed by flex) , new config files, add bundle(plugin system, config/bundles.php ), create files, modify gitignore file. same to remove **MIT license**: Lo único que exige es que los derechos de autor sean incluidos en todas las copias** o posibles porciones del software **Vendor, bundle, bridge**: - Vendor: Third part library - Bundle: Third part library integrated with symfony. - Bridge: Extends some components into Symfony and is part of the core. (DoctrineBridge, PhpUnitBridge, Monolog, Twig, SwiftMailer) ### Organizing Your Business Logic symfony-project/ ├─ config/ ├─ public/ ├─ src/ ├─ tests/ ├─ var/ └─ vendor/ - Services: - use autowiring - they should be private and inject them. - use yaml for own services - Use a persistence layer => Doctrine - Use annotation to defaine mapping information - Use data fixtures - Use Codign standars: PSR-1, PSR-2. PHP-CS-Fixer ## Main parts Symfony has two main parts: 1. Routing > Controller > Response & Event Listener flow (httpfoundation, httpkernel, eventlistener) 2. The container (dependency injection) ### HTTPFoundation The HttpFoundation component defines an object-oriented layer for the HTTP specification. Request is represented by some global variables (`$_GET`, `$_POST`, `$_FILES`, `$_COOKIE`, `$_SESSION`, ...) and the Response is generated by some functions (`echo`, `header()`, `setcookie()`, ...). The Symfony HttpFoundation component replaces these default PHP global variables and functions by an object-oriented layer. - Requests and Responses in Symfony: - Symfony\Component\HttpFoundation\Request Object. - Symfony\Component\HttpFoundation\Response Object. ![enter image description here](https://symfony.com/doc/4.0/_images/request-flow.png) ### Http Kernel Component in Symfony The HttpKernel component provides a structured process for converting a `Request` into a `Response` by making use of the EventDispatcher component![enter image description here](https://symfony.com/doc/2.3/_images/10-kernel-view.png) Most of the things that happen between the request and the response in Symfony are events. ```php #app_dev.php $kernel = new AppKernel('dev', true); // AppKernel register Bundles //most important line in symfony => $response = $kernel->handle($request); ``` Provides a structured process for converting a `Request` into a `Response` by making use of the EventDispatcher component In Symfony applications everything is already configured and ready to use with Controllers and Events and Event Listeners. 1. 'kernel.request' Event: The most important is RouterListener, that executes the routing layer and returns the controller in the Request Object. 2. Resolve the controller 3. 'kernel.controller' Event: Initialize things, ex: Sensio\ParamConverter triggered 4. Getting the Controller Args: `HttpKernel::handle()` calls ArgumentResolverInterface::getArguments() that returns the array of arguments that should be passed to that controller 5. Calling the controller: `HttpKernel::handle()` executes the controller. 6. 'kernel.view' Event: Transform a non-`Response` value into a `Response`, ex: Sensio => @Template, fos => FOSRestBUndle 7. 'kernel.response' Event: Modify the `Response` object just before it is sent, ex: WebDebugToolBarListner injects some javascript 8. 'kernel.terminate' Event: Perform some "heavy" action after the response has been streamed to the user #### The Base Controller Class & Services You can extend AbstractController to get access to some helper methods. get(service), generateurl, forward, addflash, redirect, isGranted, renderView, createForm, getUser, Request object, Session object ### Events an Event Listeners All the examples shown in this article use the same `KernelEvents::EXCEPTION`. LISTENER ```php // src/EventListener/ExceptionListener.php class ExceptionListener { public function onKernelException(GetResponseForExceptionEvent $event) { // You get the exception object from the received event $exception = $event->getException(); //... // sends the modified response object to the event $event->setResponse($response); } ``` ```yaml # config/services.yaml services: App\EventListener\ExceptionListener: tags: - { name: kernel.event_listener, event: kernel.exception } ``` logic to decide which method to execute: 1. `method` attribute defined in a tag in `kernel.event_listener` 2. the method whose name is `on` + "camel-cased event name" 3. `__invoke()` magic method 4. exception SUBSCRIBER ```php // src/EventSubscriber/ExceptionSubscriber.php class ExceptionSubscriber implements EventSubscriberInterface { public static function getSubscribedEvents() { // return the subscribed events, their methods and priorities return array( KernelEvents::EXCEPTION => array( array('processException', 10), array('logException', 0), array('notifyException', -10), ) ); } public function processException(GetResponseForExceptionEvent $event) { // ... } //... } ``` ### Dependency Injection Service is an object. Container is an array that holds service objects. Services need an order to be created, and have to be created when needed. ```php // define services with Definition Object of Symfony Container new YamlFileLoader($contaier, new FileLocator) // dumper $container->compile(); $dumper = new PhpDumper($container); file_put_contents('/directory/cachedContianer', $dumper->dump()); $cachedContainer = '/directory/'; $container->compile(); ``` 1. Process Dependency Injection Extensions (DIEs) Bundles 2. Run Compiler Passes # Service container #### Intro Service == Object The container allows you to centralize the way objects are constructed, better architecture. Services are created if needed and just once. #### Comands show avialable public services `php bin/console debug:autowiring` full list `php bin/console debug:container` #### Yaml - Services are created if needed and just once. ```yaml # config/services.yaml services: # default configuration for services in *this* file _defaults: autowire: true # Automatically injects dependencies in your services. autoconfigure: true # Automatically registers your services as commands, event subscribers, etc. public: false # Allows optimizing the container by removing unused services; this also means # fetching services directly from the container via $container->get() won't work. # The best practice is to be explicit about your dependencies anyway. # makes classes in src/ available to be used as services # this creates a service per class whose id is the fully-qualified class name App\: resource: '../src/*' exclude: '../src/{Entity,Migrations,Tests,Kernel.php}' # ... ``` Inject Services in constructors => Autowiring ```yaml # explicitly configure the service App\Updates\SiteUpdateManager: arguments: $adminEmail: 'manager@example.com' # If you need to specify the service with a common name # explicitly configure the service App\Service\MessageGenerator: arguments: $logger: '@monolog.logger.request' You can bind arguments by name or type _defaults: bind: $adminEmail: 'manager@example.com' Psr\Log\LoggerInterface: '@monolog.logger.request' Psr\Log\LoggerInterface $requestLogger: '@monolog.logger.request' ``` If a service needs a lots of container parameters you can inject ParameterBagInterface and: ```php $sender = $this->param_bag_inter->get('mailer_sender'); ``` autoconfigure: ¿?¿?¿?¿?¿? apply certain configuration to your services, based on your service's _class_. This is mostly used to _auto-tag_ your services. importing services from folder ```yaml App\: resource: '../src/*' ``` #### public vs private if public: $logger = $this->container->get('logger'); best practice: private and autowiring #### Tags Mechanism used by the di component to flag services that require special processing. To register a service in symfony/bundle in some special way. Ex: Services tagged with the `twig.extension` tag are collected during the initialization of TwigBundle and added to Twig as extensions. ##### There are built-in symfony service tags: - auto_alias: ```yaml # Instead of dealing with 3 services you deal with 1 depending on some config. services: app.mysql_lock: # ... app.postgresql_lock: # ... app.sqlite_lock: # ... app.lock: tags: - { name: auto_alias, format: "app.%database_type%_lock" } ``` - routing.loader ```yaml #: register a custom ser vice that loads routes services: App\Routing\CustomLoader: tags: [routing.loader] ``` #### Creating custom tags Tags on their own don't actually alter the functionality of your services in any way. Ex: Create a "transport chain" for swiftmailer => try several ways of transporting the message until one succeeds. 1 - Define the tranportChain class: With and addTransport(\Swift_transport $transport) function 2 - Define that class as a servie 3 - Make that several of the swift_transport classes be instantiated and added to the chain automatically using the addTransport method. Ex, you may add the following transports as services: ```yaml services: Swift_SmtpTransport: arguments: ['%mailer_host%'] tags: ['app.mail_transport'] Swift_SendmailTransport: tags: ['app.mail_transport'] ``` 4 - Use the compiler pass to ask the container for any services with the app.mail_transport tag: ```php class MailTransportPass implements CompilerPassInterface { public function process(ContaierBuilder $container){ //... foreach ($taggedServices as $id => $tags) { // add the transport service to the TransportChain service $definition->addMethodCall('addTransport', [new Reference($id)]); } } } ``` 5 - Register pass with the Container ```php class Kernel extends BaseKernel { // ... protected function build(ContainerBuilder $container) { $container->addCompilerPass(new MailTransportPass()); } } ``` #### Compiler pass Way of letting you interact with the service and parameter definitions before they have been compiled into the DIC. Steps: 1. Config files to an array 2. Array is passed to any compiler pass registered. 3. Array is compiled into a class called the dic. Why is useful? - Can update arguments passed to services. - Create service that require info about other defined service - Creating and modifying parameters in the container. Compiler passes are registered in the `build()` method of the application kernel: Register compiler pass ```php class Kernel extends BaseKernel { use MicroKernelTrait; // ... protected function build(ContainerBuilder $container): void { $container->addCompilerPass(new CustomPass()); } } ``` Work with tagged services: ```php class Kernel extends BaseKernel implements CompilerPassInterface { use MicroKernelTrait; public function process(ContainerBuilder $container) { // in this method you can manipulate the service container: // for example, changing some container service: $container->getDefinition('app.some_private_service')->setPublic(true); // or processing tagged services: foreach ($container->findTaggedServiceIds('some_tag') as $id => $tags) { // ... } } } ``` #### Using a Factory to Create Services ```php class NewsletterManagerStaticFactory { public static function createNewsletterManager() { $newsletterManager = new NewsletterManager(); // ... return $newsletterManager; } } ``` ```yaml services: # ... App\Email\NewsletterManager: # call the static method factory: ['App\Email\NewsletterManagerStaticFactory', createNewsletterManager] arguments: ['@templating'] ``` ## Routing ```php //SIMPLE ex use Symfony\Component\Routing\Annotation\Route; /* * @Route("/blog", name="blog_list") * The feature to localize routes was introduced in Symfony 4.1. * @Route({ * "nl": "/over-ons", * "en": "/about-us" * }, name="about_us") * */ // Wildcard with default value /** * @Route("/blog/{page}", name="blog_list", requirements={"page"="\d+"}) */ public function list($page = 1) {} // ADVANCED ex /* {} Advanced example * @Route( * "/articles/{_locale}/{year}/{slug}.{_format}", * defaults={"_format": "html"}, * requirements={ * "_locale": "en|fr", * "_format": "html|rss", * "year": "\d+" * } * */ // ROUTE MATCHING conditions -The expression syntax: Evaluates expressions from strings passing objects, functions, arrays... /** * @Route( * "/contact", * name="contact", * condition="context.getMethod() in ['GET', 'HEAD'] and request.headers.get('User-Agent') matches '/firefox/i'" * ) * * expressions can also include config parameters * condition: "request.headers.get('User-Agent') matches '%app.allowed_browsers%'" */ // ADD Http method requirement // @Route("/api/posts/{id}", methods={"GET","HEAD"}) // Generate route from a controller $url = $this->generateUrl( 'blog_show', ['slug' => 'my-blog-post')] ); ``` From other place use use Symfony\Component\Routing\Generator\UrlGeneratorInterface; The host that's used when generating an absolute URL is automatically detected using the current `Request` object. ##### Translating routes JMSI18nRoutingBundl or BeSimpleI18nRoutingBundle How to work with the user's locale The locale of the current user is stored in the request and is accessible via the `Request` object To set the user's locale, you may want to create a custom event listener so that it's set before any other parts of the system (i.e. the translator) need it. Since you can store the locale of the user in the session, it may be tempting to use the same URL to display a resource in different languages based on the user's locale. Unfortunately, this violates a fundamental rule of the Web: that a particular URL returns the same resource regardless of the user A better policy is to include the locale in the URL. `_locale` parameter if a user visits the URI `/fr/contact`, the locale `fr` will automatically be set as the locale for the current request. default locale framework: default_locale: en ## Templating with Twig Twig defines three types of special syntax: `{{ ... }}` "Says something" `{% ... %}` "Does something" `{# ... #}` "Comment something": {{ title|upper }} filters {{ dump(user) }} functions {% set foo = 'bar' %} tags Variables {{ foo.bar }} {{ foo['bar'] }} Twig is fast because each template is compiled to a native PHP class and cached. But don't worry: this happens automatically and doesn't require _you_ to do anything. #### - Inheritance ```twig {% extends 'base.html.twig' %} {% block title %}My cool blog posts{% endblock %} {% block body %} {% for entry in blog_entries %}

{{ entry.title }}

{{ entry.body }}

{% endfor %} {{ parent() }} {% endblock %} ``` #### HTML Escaping When generating HTML from templates, there's always a risk that a variable will include characters that affect the resulting HTML. There are two approaches: manually escaping each variable or automatically escaping everything by default. Twig performs automatic "output escaping" when rendering any content in order to protect you from Cross Site Scripting (XSS) attacks. {{ description }} {{ description|raw }} #### Macros Macros are comparable with functions in regular programming languages. They are useful to reuse often used HTML fragments to not repeat yourself. ```twig {% macro input(name, value = "", type = "text", size = 20) %} {% endmacro %} ``` #### Expressions ```twig {% set greeting = 'Hello ' %} {% set name = 'Fabien' %} {{ greeting ~ name|lower }} {# Hello fabien #} ``` #### Naming and Locations templates/ to render/extend `templates/blog/index.html.twig`, you'll use the `blog/index.html.twig` path #### Global variables ```twig

Username: {{ app.user.username }}

{% if app.debug %}

Request method: {{ app.request.method }}

Application Environment: {{ app.environment }}

{% endif %} ``` #### Inject variables #config/packages/twig.yaml twig: # ... globals: ga_tracking: UA-xxxxx-x #### Include other template ```twig {% for article in articles %} {{ include('article/article_details.html.twig', { 'article': article }) }} {% endfor %} ``` #### Link to page ```twig //- Link to asset Symfony! ``` #### Embed controller in a template ```twig ``` #### Translations (Internationalization (i18n)) dump($translator->trans('Hello World')); composer require symfony/translation ```php $translated = $translator->trans('Symfony is great'); ``` ```yaml # translations/messages.fr.yaml Symfony is great: J'aime Symfony ``` ```twig {{ message|trans({'%name%': 'Fabien'}, 'app') }} ``` #### Interpolation String interpolation (`#{expression}`) allows any valid expression to appear within a _double-quoted string_. ```twig {{ "foo #{bar} baz" }} {{ "foo #{1 + 2} baz" }} ``` ## Forms in symfony composer require symfony/form We have an Data class. ```php $task = new Task(sdf,sadf); $form = $this->createFormBuilder($task) ->add('task', TextType::class) ->add('dueDate', DateType::class) ->add('save', SubmitType::class, array('label' => 'Create Task')) ->getForm(); return $this->render('default/new.html.twig', array( 'form' => $form->createView(), )); ``` #### Rendering ```twig {# templates/default/new.html.twig #} {{ form_start(form) }} {{ form_widget(form) }} {{ form_end(form) }} ``` #### Handling submissions ```php $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { $task = $form->getData(); ///...... } ``` #### Form validation composer require symfony/validator ```php /** * @Assert\NotBlank * @Assert\Type("\DateTime") */ ``` #### Built-in Field Types Text Fields, Choice Fields, Date Fields, group fields, hidden fields, button fields, other fields, custom fields #### Creating Form Classes ```php class TaskType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('task') ->add('dueDate', null, array('widget' => 'single_text')) ->add('save', SubmitType::class) ; } } $form = $this->createForm(TaskType::class, $task); ``` it's generally a good idea to explicitly specify the `data_class` option ```php public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults(array( 'data_class' => Task::class, )); } ``` When mapping forms to objects, all fields are mapped. If you wanat a field not to be mapped: ```php $builder ->add('task') ->add('dueDate') ->add('agreeTerms', CheckboxType::class, array('mapped' => false)) ->add('save', SubmitType::class) ; ``` When building forms, keep in mind that the first goal of a form is to translate data from an object (`Task`) to an HTML form so that the user can modify that data. The second goal of a form is to take the data submitted by the user and to re-apply it to the object. #### Form Events 1) Pre-populaing the From (pre-set-data, post-set-data) 2) Submiting a form (pre-submit, submit, post-submit) Register event listeners or event subscribers #### Form Field Type Extension Modify field types, 2 use cases: - Add feature to field type (download to fieldtype) - Add feature to several field types (help text) ## Validation - Data needs to be validate for froms, to db or web services. - Validator component is based on a java validator. Use annotations on php data classes. - Validate: $errors = $validator->validate($author); - Most of the time, you won't interact directly with the `validator` service => form validation ```yaml # config/packages/framework.yaml framework: validation: - enabled: true - enable_annotations: true ``` #### Constraints A php object that makes an assertive statement. Can be applied in php object property, forms, getters #### Translations * @Assert\NotBlank(message="author.name.not_blank") # translations/validators.en.yaml author.name.not_blank: Please enter an author name. #### Custom constraint ```php //CONSTRAINT // src/Validator/Constraints/ContainsAlphanumeric.php namespace App\Validator\Constraints; use Symfony\Component\Validator\Constraint; /** * @Annotation */ class ContainsAlphanumeric extends Constraint { public $message = 'The string "{{ string }}" contains an illegal character: it can only contain letters or numbers.'; } //------ // CONSTRAINT VALIDATOR // src/Validator/Constraints/ContainsAlphanumericValidator.php namespace App\Validator\Constraints; class ContainsAlphanumericValidator extends ConstraintValidator { public function validate($value, Constraint $constraint) { if (null === $value || '' === $value) { return; } if (!is_string($value)) { throw new UnexpectedValueException($value, 'string'); } if (!preg_match('/^[a-zA-Z0-9]+$/', $value, $matches)) { $this->context->buildViolation($constraint->message) ->setParameter('{{ string }}', $value) ->addViolation(); } } } ``` ##### Validate a Class ```php class ProtocolClass extends Constraint public function getTargets() { return self::CLASS_CONSTRAINT; } } // so you can use the entire protocol class class ProtocolClassValidator extends ConstraintValidator { public function validate($protocol, Constraint $constraint) { if ($protocol->getFoo() != $protocol->getBar()) { $this->context->buildViolation($constraint->message) ->atPath('foo') ->addViolation(); } } } // Apply it /** * @AcmeAssert\ContainsAlphanumeric */ class AcmeEntity { } ``` #### Callback constrain to create custom validation rules ```php /** * @Assert\Callback */ public function validate(ExecutionContextInterface $context, $payload) { if (in_array($this->getFirstName(), $fakeNames)) { $context->buildViolation('This name sounds fake!') ->atPath('firstName') ->addViolation(); } } ``` If you want to execute a method out of the class you can use an static method public static function validate($object, ExecutionContextInterface $context, $payload) ```php /** * @Assert\Callback({"Acme\Validator", "validate"}) */ class Author { } ``` The Callback constraint does _not_ support global callback functions If you want to validate a scalar or array you need the validator service. #### Validation Groups * @Assert\Length(min=7, groups={"registration"}) In a class with this, will be 3 groups: The class (ex: User), the default, the registration. Default: All the constrainst that don't belong to any group If you use the default it will apply the constraint of the embedded objects. ```php $form = $this->createFormBuilder($user, array( 'validation_groups' => array('registration'), ))->add(...); // ---- or public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults(array( 'validation_groups' => array('registration'), )); } ``` #### Validate groups by steps ```php /** * @Assert\GroupSequence({"User", "Strict"}) */ class User implements UserInterface ``` Using GroupSequence make Defautl != User => Now the default will reference the group sequence. If you use it it will get an infinite recursion. ```php public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ 'validation_groups' => new GroupSequence(['First', 'Second']), ]); } ``` #### Apply different sequence depending on a logic ```php /** * @Assert\GroupSequenceProvider */ class User implements GroupSequenceProviderInterface { /** * @Assert\CardScheme( * schemes={"VISA"}, * groups={"Premium"}, * ) */ private $creditCard; public function getGroupSequence() { // if user fail => resto not validated return array('User', 'Premium', 'Api'); // if user fail => premium validated but api dont return array(array('User', 'Premium'), 'Api'); } } ``` # Security in symfony Security in symfony - install, user class, user provider, encoding password - firewall - authenticate => firewall, guard - authoritation => acces_control, roles, voters - check permissions => voters ### First steps - Install: composer require symfony/security-bundle - Create user class: Can do it with php bin/console make:user - User provider: Class that helps with things like remember me, impersonation. make:use configure one automatically If user class is not an entity you'll need to generate a UserProvider - Encode passwords: php bin/console security:encode-password ### Firewalls Firewall: Authentication system. ```yaml # config/packages/security.yaml security: firewalls: dev: pattern: ^/(_(profiler|wdt)|css|images|js)/ security: false main: anonymous: ~ ``` Only one firewall is active on each request. Symfony matches the first one with pattern key. If you go to the homepage right now, you _will_ have access and you'll see that you're "authenticated" as `anon.`. Don't be fooled by the "Yes" next to Authenticated. The firewall verified that it does not know your identity, and so, you are anonymous: #### Authenticating Users Authentication in Symfony can feel a bit "magic" at first because, instead of building a route & controller to handle login, you'll activate an _authentication provider_: some code that runs automatically _before_ your controller is called. Symfony has several built-in authentication providers (form_login, http_basic, json_login, ldap...). If your use-case matches one of these exactly, great! But, in most cases - including a login form - we recommend building a Guard Authenticator #### Guard A Guard authenticator is a class that gives you _complete_ control over your authentication process. Create a Authentication System with Guard (api, login form, sso system) Custom Authentication System with Guard (API token Example) 1 - Create a User Class and add php bin/console make:user ```php /** * @ORM\Column(type="string", unique=true) */ private $apiToken; ``` 2- Create the authenticator class To create a custom authentication system, create a class and make it implement AuthenticatorInterface. Or, extend the simpler AbstractGuardAuthenticator. 3 - Configure the authenticator ```yaml firewalls: # ... main: anonymous: ~ logout: ~ guard: authenticators: - App\Security\TokenAuthenticator ``` ### Authorization Users can now log in to your app. Great! Now, you need to learn how to deny access and work with the User object. The process of authorization has two different sides: 1 - A user receives a ROLE 2 - RESOURCES (urls, controllers) filter by ROLE #### Roles When a user logs in, Symfony calls the `getRoles()` method on your `User` object to determine which roles this user has. In the `User` class that we generated earlier, the roles are an array that's stored in the database, and every user is _always_ given at least one role: `ROLE_USER`: - How to denny access 1. access_control ```yaml security: access_control: # require ROLE_ADMIN for /admin* - { path: ^/admin, roles: ROLE_ADMIN } ``` 2. controller ```php $this->denyAccessUnlessGranted('ROLE_ADMIN'); ``` With SensioFrameworkExtraBundle ```php /** * @IsGranted("ROLE_ADMIN") */ class AdminController extends AbstractController { /** * @IsGranted("ROLE_ADMIN") */ public function adminDashboard() {} } ``` 3. Templates ```twig {% if is_granted('ROLE_ADMIN') %} Delete {% endif %} ``` 4. Service ```php if ($this->security->isGranted('ROLE_SALES_ADMIN')) { $salesData['top_secret_numbers'] = rand(); } ``` ##### Checking ia a user is logged 2 options: - Check if ROLE_USER - $this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY'); ### Fetch user After authentication, the `User` object of the current user can be accessed via the `getUser()` shortcut: controller: $this->getUser(); service: $user = $this->security->getUser(); template: {{ app.user.email }} #### Logging out ```yaml firewalls: main: # ... logout: path: app_logout ``` ##### Hierarchical roles ```yaml security: role_hierarchy: ROLE_ADMIN: ROLE_USER ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH] ``` ## Voters To authorizate to access to a resource. Voters are called when isGranted() is called on symfony's authorization checker or denyAccessUnlessGranted in a controller (that uses auth checker). Symfony takes de response of all voters and makes a decision - Create a voter: A User can view or edit a Post that they create. Is a Post is marked as public anyone can view it. ```php // src/Security/PostVoter.php class PostVoter extends Voter { protected function supports($attribute, $subject) { // if the attribute isn't one we support, return false if (!in_array($attribute, array(self::VIEW, self::EDIT))) return false; // only vote on Post objects inside this voter if (!$subject instanceof Post) return false; return true; } protected function voteOnAttribute($attribute, $subject, TokenInterface $token) { $user = $token->getUser(); if (!$user instanceof User) { // the user must be logged in; if not, deny access return false; } // you know $subject is a Post object, thanks to supports /** @var Post $post */ $post = $subject; switch ($attribute) { case self::VIEW: return $this->canView($post, $user); case self::EDIT: return $this->canEdit($post, $user); } throw new \LogicException('This code should not be reached!'); } private function canEdit(Post $post, User $user) { // this assumes that the data object has a getOwner() method // to get the entity of the user who owns this data object return $user === $post->getOwner(); } } ``` Normally, only one voter will return true from support. but multiple can voter for the same action and object. => Access Decision strategy: - affirmative (default): As one true voter. - consensus: More granting than denying. - unanimous: No voter denying. If all abstain => false. Change it (allow_if_all_abstain) ```yaml security: access_decision_manager: strategy: unanimous allow_if_all_abstain: false ``` ### Voters (check permissions) To authorizate to access to a resource. Security voters are the most granular way of checking permissions. All voters are called each time you use the `isGranted()` method on Symfony's authorization checker or call `denyAccessUnlessGranted()` in a controller. Ultimately, Symfony takes the responses from all voters and makes the final decision according to the strategy defined in the application, which can be: affirmative, consensus or unanimous. Custom Voter implements VoterInterface ```php abstract class Voter implements VoterInterface { abstract protected function supports($attribute, $subject); abstract protected function voteOnAttribute($attribute, $subject, TokenInterface $token); } ``` In controller: ```php $this->denyAccessUnlessGranted('view', $post); ``` denyAccessUnlessGranted calls the voter system. ##### Custom Voter ```php protected function voteOnAttribute($attribute, $subject, TokenInterface $token) { $user = $token->getUser(); if (!$user instanceof User) { // the user must be logged in; if not, deny access return false; } // you know $subject is a Post object, thanks to supports /** @var Post $post */ $post = $subject; switch ($attribute) { case self::VIEW: return $this->canView($post, $user); case self::EDIT: return $this->canEdit($post, $user); } throw new \LogicException('This code should not be reached!'); } ``` `affirmative` (default); This grants access as soon as there is _one_ voter granting access; `consensus`: This grants access if there are more voters granting access than denying; `unanimous`: This only grants access if there is no voter denying access. If all voters abstained from voting, the decision is based on the `allow_if_all_abstain` config option (which defaults to `false`). ## HTTP Cache - Definition: Caching is a technique that stores a copy of a given resource and serves it back when requested. - Reasons: Reduce latency and network traffic. - Caches can be shared or private and the types are: - Browser cache: Stored in hard disk (back button, opened link...) - Proxy cache: Dont belong neither the client or the server but the network => ISPs - Gateway cache (reverse proxy): Deployed by webmasters to make sites more scalable. CDN distribute gateway caches throughout the Internet. Test with RedBot - How does it work? All caches have a set of rules set in HTTP 1.0/1.1 and other set by the admin of thew cache. Rules: 1. Response's headers tell the cache not to keep => it wont 2. If https => wont cache by shared 3. Fresh if: - Has expirity time and is in the fresh period - If the cache has seen the representation recently and it was modifed long ago. 4. If representation stale => server will be asked to validate it. 5. Under some circumstances a cache can serve stale responses without checking the origin server. - How to control caches: - HTML Meta Tags: Arent very effective and only used by some browsers. - HTTP Headers: Give a lot of control. ```yaml HTTP/1.1 200 OK Date: Fri, 30 Oct 1998 13:19:41 GMT Server: Apache/1.3.3 (Unix) Cache-Control: max-age=3600, must-revalidate Expires: Fri, 30 Oct 1998 14:19:41 GMT Last-Modified: Mon, 29 Jun 1998 02:28:12 GMT ETag: "3e86-410-3596fbbc" Content-Length: 1040 Content-Type: text/html ``` - Cache-Control HTTP Headers: - max-age: max time fresh - s-maxage: same as before but only for shared caches - public, no-cache, no-store, must-revalidate, proxy-revalidate - Validators and Validation: Most common validator is the time that the document last changed as communicated in Last-Modified header. HTTp .1. introduced Etag. Etag validation is becoming prevalent. Most modern Web servers will generate both. Building a Cache -Aware site: - Dont serve same content on different urls - max-age large for images and pages that dont change - check with REDbot Gateway Cache (Symfony cache or varnish) Expiration, Validation, Expiration and Validation combined Symfony reverse proxy Create a Caching Kernel and call it from index.php Make responses http cacheable. 4 response cache headers that you can set to enable caching: Cache-control, expires, etag, last-modified 2 models: - Expiration caching: Cache invalidation is more difficult // cache for 3600 seconds $response->setSharedMaxAge(3600); - Validation caching: More complex but allows to invalidate as soon the content changes FOSHttpCacheBundle ## Console annotations (sensio/framwork bundle) # Cache ```php $item = $cache->getItem('markdown_'.md5($articleContent)); if (!$item->isHit()) { $item->set($markdown->transform($articleContent)); $cache->save($item); } ``` save it in redis can be checked in debug toolbar to show all services ./bin/console debug:container --show-private controllers: extend from abstractController or Controller ### Components | Name | Description | |:------------------------:|:----------------------------------------------------------------------------------------------------------------------------------------------------------------:| | HttpFoundation | Defines an object-oriented layer for the HTTP specification. | | HttpKernel | Provides the building blocks to create flexible and fast HTTP-based frameworks. | | DependencyInjection | Allows you to standardize and centralize the way objects are constructed in your application. | | - | - | | ClassLoader | Loads your project classes automatically if they follow some standard PHP conventions. | | Console | Eases the creation of beautiful and testable command line interfaces. | | EventDispatcher | Implements the Mediator pattern in a simple and effective way to make projects truly extensible. | | Form | Provides tools to easy creating, processing and reusing HTML forms. | | Guard | Brings many layers of authentication together, making it much easier to create complex authentication systems where you have total control. | | Routing | Maps an HTTP request to a set of configuration variables. | | Security | Provides an infrastructure for sophisticated authorization systems. | | Serializer | Turns objects into a specific format (XML, JSON, Yaml, ...) and the other way around. | | Debug | Provides tools to ease debugging PHP code. | | Yaml | Loads and dumps YAML files. | | Templating | Provides all the tools needed to build any kind of template system. | | VarDumper | Provides mechanisms for walking through any arbitrary PHP variable. | | Asset | Manages URL generation and versioning of web assets | | Validator | Provides tools to validate classes. | | Translation | Provides tools to internationalize your application. | | Cache | Implements PSR-6 and PSR-16 caching mechanisms and provides adapters for popular caching backends (Redis, Memcache, APCu, etc.) | | - | - | | Messenger | Helps applications send and receive messages to/from other applications or via message queues. | | Process | Executes commands in sub-processes. $process = new Process(array('ls', '-lsa')); | | Lock | Creates and manages locks, a mechanism to provide exclusive access to a shared resource. | | OptionsResolver | Helps you configuring objects with option arrays. | | Intl | Replacemnt layer for the intl c extension(performs locale-aware operations- formmatiing, eoncoding, timezones, currencies). | | CssSelector | Converts CSS selectors to XPath expressions. | | Config | Helps you find, load, combine, autofill and validate configuration values. | | Workflow | Provides tools for managing a workflow or finite state machine. | | Ldap | Provides an LDAP client for PHP on top of PHP's ldap extension. | | Stopwatch | Provides a way to profile code instead of using microtime. | | PropertyInfo | Extracts info about the properties of PHP classes using metadata of: Doctrine, PHP Reflection, PHPdoc... | | PropertyAccess | Provides function to read and write from/to an object or array using a simple string notation. | | PHPUnit Bridge | Provides utilities to report legacy tests and usage of deprecated code and a helper for time-sensitive tests. | | Finder | Finds files and directories via an intuitive fluent interface. | | Filesystem | Provides basic utilities for the filesystem. | | BrowserKit | Simulates the behavior of a web browser. | | DomCrawler | Eases DOM navigation for HTML and XML documents. | | Dotenv | Parses .env files to make environment variables stored in them accessible via getenv(), $_ENV or $_SERVER. | | ExpressionLanguage | Provides an engine that can compile and evaluate expressions. | | - | - | | WebLink | Implements HTML5 Links, Preload and Resource Hints specifications to preload and prefetch documents through HTTP and HTTP/2 pushes. | | Contracts | A set of abstractions extracted out of the Symfony components. | | - | - | | Polyfill PHP 5.4 - 7.3 | Provides functions unavailable in releases prior to PHP 5.4 7.3. | | Polyfill APCu... | Provides apcu_* functions of the legacy APC extension. An multiple plyfills for diff extensions | | Polyfill Iconv | Provides a native PHP implementation of the php.net/iconv functions. | | Icu | Deprecated since October 2014, use the Intl component instead. | | Locale | Deprecated since 2.3, use the Intl component instead. | ------------ ### OptionsResolver Component The OptionsResolver component is array_replace on steroids. It allows you to create an options system with required options, defaults, validation (type, value), normalization and more. ### Argument Resolver Is the symfony class that makes posible to get the Request object via an argument in a controller if it is type hinted. You can extend this funcionality. ### Custom annotations ## Webpack ... ## Symfony Best Practices Use symfony flex to create the app Use symfony skeleton to create a new symfony app Dont create bundles to organize your app Use .env files to set env variables. The symfony app doesnt care where the db is or other env variables values. Define application behavior related config in the config/services.yaml file Use autowiring Services should be private whenever possible (DI) Use annotations to define the mapping infomation of the Doctrine Entities Coding Standars (PSR-1, PSR-2, CS-fixer, sf4) http://cs.sensiolabs.org/ Symfony follows the philosophy of _"thin controllers and fat models"_. Make your controller extend the `AbstractController` base controller provided by Symfony and use annotations to configure routing, caching and security Don't use the `@Template` annotation to configure the template used by the controller. return $this->render('default/index.html.twig', [ 'posts' => $posts, ]); Use the ParamConverter trick to automatically query for Doctrine entities when it's simple and convenient. Use lowercased snake_case for directory and template names. Use a prefixed underscore for partial templates in template names. Define your forms as PHP classes. Add buttons in the templates, not in the form classes or the controllers. {{ form_start(form) }} {{ form_widget(form) }} {{ form_end(form) }} Do not define your validation constraints in the form but on the object the form is mapped to. Use the XLIFF format for your translation files. - Whenever possible, use the `@Security` annotation; Define a custom security voter to implement fine-grained restrictions. use webpack encore Define a functional test that at least checks if your application pages are successfully loading. $this->assertTrue($client->getResponse()->isSuccessful()); ## The Release Process patch version x.x.X (every month) minor version x.X (may and november) major version X (every 2 years) Version Type; Bugs are fixed for... ; Security issues are fixed for... Long-Term Support (LTS) ; 3 years ; 4 years - Backward Compatibility Promise Semantic Versioning means that only major releases (such as 2.0, 3.0 etc.) are allowed to break backward compatibility A feature is marked as deprecated by adding a `@deprecated` phpdoc to relevant classes, methods, properties, ...: PHP supports one error control operator: the at sign (@). When prepended to an expression in PHP, any error messages that might be generated by that expression will be ignored. custom annotations Encore Argument resolver logical paths into physical paths