{{ 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 ```twigUsername: {{ 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 ``` #### 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