symfony [Docs]

User Tools

Site Tools


symfony

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

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 componententer image description here Most of the things that happen between the request and the response in Symfony are events.

  #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

// 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);
    }
# 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

// 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.

	// 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.
# 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

   # 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:

$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

 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:
    #
     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

    #: 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:

    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:

    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

    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

class Kernel extends BaseKernel
{
    use MicroKernelTrait;
    // ...
    protected function build(ContainerBuilder $container): void
    {
        $container->addCompilerPass(new CustomPass());
    }
}

Work with tagged services:

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

class NewsletterManagerStaticFactory
{
    public static function createNewsletterManager()
    {
        $newsletterManager = new NewsletterManager();
        // ...
        return $newsletterManager;
    }
}
services:
    # ...
    App\Email\NewsletterManager:
        # call the static method
        factory: ['App\Email\NewsletterManagerStaticFactory', createNewsletterManager]
        arguments: ['@templating']

Routing

//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

{% extends 'base.html.twig' %}
 
{% block title %}My cool blog posts{% endblock %}
 
{% block body %}
    {% for entry in blog_entries %}
        <h2>{{ entry.title }}</h2>
        <p>{{ entry.body }}</p>
    {% 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.

{% macro input(name, value = "", type = "text", size = 20) %}
 <input type="{{ type }}" name="{{ name }}" value="{{ value|e }}" size="{{ size }}" />
{% endmacro %}

Expressions

{% 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

 <p>Username: {{ app.user.username }}</p>
{% if app.debug %}
    <p>Request method: {{ app.request.method }}</p>
    <p>Application Environment: {{ app.environment }}</p>
{% endif %}

Inject variables

#config/packages/twig.yaml twig:

# ...
globals:
    ga_tracking: UA-xxxxx-x

Include other template

 {% for article in articles %}
        {{ include('article/article_details.html.twig', { 'article': article }) }}
    {% endfor %}
<a href="{{ path('article_show', {'slug': article.slug}) }}">
//- Link to asset
<img src="{{ asset('images/logo.png') }}" alt="Symfony!" />
<link href="{{ asset('css/blog.css') }}" rel="stylesheet" />

Embed controller in a template

<div id="sidebar">
    {{ render(controller(
        'App\\Controller\\ArticleController::recentArticles',
        { 'max': 3 }
    )) }}
</div>

Translations (Internationalization (i18n))

dump($translator->trans('Hello World'));

composer require symfony/translation

$translated = $translator->trans('Symfony is great');
 # translations/messages.fr.yaml
Symfony is great: J'aime Symfony
{{ message|trans({'%name%': 'Fabien'}, 'app') }}

Interpolation

String interpolation (#{expression}) allows any valid expression to appear within a double-quoted string.

{{ "foo #{bar} baz" }}
{{ "foo #{1 + 2} baz" }}

Forms in symfony

composer require symfony/form

We have an Data class.

$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

{# templates/default/new.html.twig #}
{{ form_start(form) }}
{{ form_widget(form) }}
{{ form_end(form) }}

Handling submissions

 $form->handleRequest($request);
 
    if ($form->isSubmitted() && $form->isValid()) {
        $task = $form->getData();
        ///......
        }

Form validation

composer require symfony/validator

/**
 * @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

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

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:

  $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
     # 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

//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
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

/**
 * @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)

/**
 * @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.

$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

/**
 * @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.

public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'validation_groups' => new GroupSequence(['First', 'Second']),
        ]);
    }

Apply different sequence depending on a logic

/**
 * @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.

# 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

     /**
      * @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

    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
      security:
        access_control:
         # require ROLE_ADMIN for /admin*
         - { path: ^/admin, roles: ROLE_ADMIN }
    2. controller
      $this->denyAccessUnlessGranted('ROLE_ADMIN');

      With SensioFrameworkExtraBundle

       /**
      * @IsGranted("ROLE_ADMIN")
      */
      class AdminController extends AbstractController
      {
      /**
       * @IsGranted("ROLE_ADMIN")
       */
        public function adminDashboard()
        {}
      }
    3. Templates
      {% if is_granted('ROLE_ADMIN') %}
        <a href="...">Delete</a>
      {% endif %}
    4. Service
       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

    firewalls:
        main:
            # ...
            logout:
                path:   app_logout
Hierarchical roles
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.
// 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)
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

abstract class Voter implements VoterInterface
{
    abstract protected function supports($attribute, $subject);
    abstract protected function voteOnAttribute($attribute, $subject, TokenInterface $token);
}

In controller:

$this->denyAccessUnlessGranted('view', $post);

denyAccessUnlessGranted calls the voter system.

Custom Voter
 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.
      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

$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) }}

<input type="submit" class="btn" value="Create" />

{{ 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

symfony.txt · Last modified: 2020/07/02 16:06 (external edit)