HOWTO.symfony - raynaldmo/HOWTO GitHub Wiki

Table of Contents

Symfony

Resources

Symfony Coding Standards

Symfony 3

Symfony 3 Training

Installation

  • Use Symfony Installer
raynald@Raynalds-iMac ~ $ pwd
/Users/raynald

raynald@Raynalds-iMac ~ $ sudo mkdir -p /usr/local/bin
raynald@Raynalds-iMac ~ $ sudo curl -LsS https://symfony.com/installer -o /usr/local/bin/symfony
raynald@Raynalds-iMac ~ $ sudo chmod a+x /usr/local/bin/symfony
  • Install Symfony 3
raynald@Raynalds-iMac ~/PhpStormProject $ pwd
/Users/raynald/PhpStormProjects 
raynald@Raynalds-iMac ~/PhpStormProjects $ symfony new aqua_note 3.4.0

Directory Contents

  • src/ - Holds all PHP files (classes)
  • app/ - Holds configuration, templates
  • web/ - Holds publicly accessible files (html, css, js, images etc.)

Use Symfony Server

raynald@Raynalds-iMac ~/PhpStormProjects/aqua_note $ php bin/console server:run localhost:8000
PHP 7.0.20 Development Server started at Tue Sep  3 09:35:26 2019
Listening on http://127.0.0.1:8000
Document root is /Users/raynald/PhpStormProjects/aqua_note/web
Press Ctrl-C to quit.

Launch Site

  • Go to localhost:<port> or 127.0.0.1:<port>

Symfony 4

Symfony 4 Training

The Symfony Framework Best Practices

Installation

raynald@Raynalds-iMac ~/PhpStormProjects $ composer create-project symfony/skeleton symfony4-training/
Creating a "symfony/skeleton" project at "./symfony4-training"
Installing symfony/skeleton (v4.4.99)
  - Installing symfony/skeleton (v4.4.99): Extracting archive
....

Directory Contents

  • src/ - Holds PHP files (classes)
  • config/ - Holds configuration
  • public/ - Holds publicly accessible files (index.php, html, css, js, images)
  • templates/ - Holds Twig templates
  • migrations/ - Holds database migration files

Use PHP built-in Server

raynald@Raynalds-iMac ~/PhpStormProjects/symfony4-training $ php -S localhost:8001 -t public
PHP 7.1.6 Development Server started at Sat Dec 19 16:57:45 2020
Listening on http://localhost:8001
Document root is /Users/raynald/PhpStormProjects/symfony4-training/public
Press Ctrl-C to quit.

Use Symfony Server

  • Install older server
raynald@Raynalds-iMac ~/PhpStormProjects/symfony4-training (master) $ composer require server
  • Run server
raynald@Raynalds-iMac ~/PhpStormProjects/symfony4-training (master) $ bin/console server:run localhost:8001

Use newer Symfony Server

raynald@Raynalds-iMac ~/PhpStormProjects/symfony4-training (master) $ symfony server:start --port=8001
  • Be sure to remove older web server (WebserverBundle)
composer remove server

Launch Site

  • Go to localhost:<port> or 127.0.0.1:<port>

Symfony Console

  • Use Symfony Console (CLI) bin/console to debug and show configuration, routes, services etc

Symfony Console Autocomplete

PhpStorm

Symfony Upgrades

2.x to 3.0.x

Upgrade 2.x to 2.8.x

  • Update symfony/symfony package version in composer.json to 2.8.*
diff --git a/composer.json b/composer.json
index 4a51738..224928d 100644
--- a/composer.json
+++ b/composer.json
@@ -8,7 +8,7 @@
     },
     "require": {
         "php": ">=5.3.3",
-        "symfony/symfony": "2.6.*",
+        "symfony/symfony": "2.8.*",
         "doctrine/orm": "~2.2,>=2.2.3",
         "doctrine/doctrine-bundle": "~1.2",
         "twig/extensions": "~1.0",
  • Run composer update symfony/symfony --with-dependencies
  • Makes changes to conform to new Symfony 3.0 directory structure
  • Fix Deprecated code warnings from own code
  • Update third-party libraries and bundles that are generating deprecated code warnings
  • Run Unit and Functional tests to check application works as expected

Upgrade 2.8.x to 3.0.x

  • Update symfony/symfony package version in composer.json to 3.0.*
diff --git a/composer.json b/composer.json
index 4a51738..224928d 100644
--- a/composer.json
+++ b/composer.json
@@ -8,7 +8,7 @@
     },
     "require": {
         "php": ">=5.3.3",
-        "symfony/symfony": "2.8.*",
+        "symfony/symfony": "3.0.*",
         "doctrine/orm": "~2.2,>=2.2.3",
         "doctrine/doctrine-bundle": "~1.2",
         "twig/extensions": "~1.0",
  • Run composer update to update all packages
  • If update fails, check requires section in Packagist symfony/framework-standard-edition
    for version you're upgrading to for correct symfony and other library versions
  • Also check Symfony Standard Edition distribution
    composer.json etc. in version you're upgrading to for correct symfony and other
    library versions
  • Update third-party libraries and bundles that are generating deprecated code warnings
  • Run Unit and Functional tests to check application works as expected
  • Run composer update symfony/symfony --with-dependencies

3.x to 3.x

  • Update symfony/symfony package version in composer.json
diff --git a/composer.json b/composer.json
index 56f7327..bb1995d 100644
--- a/composer.json
+++ b/composer.json
@@ -15,12 +15,12 @@
     },
     "require": {
         "php": ">=5.5.9",
-        "symfony/symfony": "3.1.*",
+        "symfony/symfony": "3.3.*",
  • Run composer update symfony/symfony --with-dependencies

Upgrade from 3.x to Symfony 4 and Flex

  • Update to symfony/symfony ^3.4 first
    • Symfony 3.4 and 4.0 are basically identical but 4.0 removes deprecated code
  • Goto https://github.com/symfony/symfony
    • Switch to branch 4.0
    • Read UPGRADE-4.0.md for notes on upgrading to version 4.x
  • Ensure that services.yml uses autowiring etc.
  • Fix deprecated code
  • Update to symfony/symfony ^4.0

Update project to support Symfony Flex

  • Symfony 4 supports Flex
  • Flex is the new way to install and manage Symfony applications
  • Flex is basically a tool that replaces and improves the Symfony Installer and Symfony Standard Edition
  • Flex is implemented as a Composer plugin
    • Modifies/enhances the behavior of the composer require, update, and remove commands
composer require symfony/flex

Deprecation Fixing Tools

Symfony Fundamentals

Web Profiler & Debugging

  • Install symfony/web-profiler-bundle (profiler) for web profiler
  • Use dump(var1, var2...) in controllers and other code
  • Use dump() or dump(var1, var2...) in Twig templates

Caching

  • Clear caches
./bin/console cache:clear --env=dev
./bin/console cache:clear --env=prod

Controllers

  • Creating a page in Symfony involves two steps -
    • Define a route - configuration that specifies the URL
    • Define controller that handles the route - function that builds the page
  • A controller must always return a Response() object
  • Starting with Symfony 4, controllers are configured as services
    • A side effect of this is that controllers can have a constructor
    • Services can be injected via the controller's constructor or via auto-wiring / type-hinting the service class as an argument in the controller method

Routes

Symfony 3.x (pre Flex)

  • In dev environment routes are loaded from app/config/routing_dev.yml
  • In all environments main routes are loaded from app/config/routing.yml

Symfony 4.x / Flex

  • config/routes.yaml

  • Third-party Bundles can add routes

  • Show routes

bin/console debug:router

Routing Wildcards

  • Example wildcard route: @Route("/genus/{genusName}")

ParamConverter

  • @ParamConverter
  • The @ParamConverter annotation calls converters to convert request parameters to objects
  • Use in controller methods to automatically query / retrieve a single (i.e findOneBy()) entity / db entry
  • This feature requires sensio/framework-extra-bundle to be installed

Twig Templates

Symfony 3.x

  • Twig templates reside in app/Resources/views

Symfony 4.x +

  • Twig templates reside in templates/

Loading Assets (css, javascript, images)

  • Use Twig {{ asset('..') }} function load css, javascript and images

Symfony 3.x

  • web/ directory is web server document root and are publicly accessible
  • Files outside of web/ are not publicly accessible

Symfony 4.x +

  • public/ directory is web server document root and are publicly accessible
  • Files outside of public/ are not publicly accessible

Twig Extensions

Symfony 4.x +

  • Use bin/console make:twig-extension command to generate src/Twig/AppExtension.php file

Generating URLs

  • When creating links to other pages, instead of hardcoding URLs in templates,
    use the Twig path() function in templates
  • If link URL needs to change, can just change in the controller or route config - no need to
    change in any templates
  • See Linking to Pages
<link rel="stylesheet" href="{{ asset('css/styles.css') }}">
<img class="nav-profile-img rounded-circle" src="{{ asset('images/astronaut-profile.png') }}>

Services

  • See Service Container
  • Symfony comes with lots of objects that perform some functionality
  • A Service is an object that does some work
    • Access a database
    • Send mail
    • Log messages
    • Return JSON response
    • Render a template (Twig)
  • These objects are called Services
  • In Symfony all functionality is done by a Service
  • Symfony implements a Service Container to keep track of services
    • A Service Container (or Dependency Injection Container) is simply a PHP object that manages the instantiation of services (i.e. objects)
    • Each service is accessed via a key in the Service Container
  • Services are registered with the Service Container in services.yml
    • NOTE: Beginning with Symfony 3.3 services are configured differently and services.yml features some additional options

Listing Services

  • List Services
    • bin/console debug:container
    • bin/console debug:container --types # get service id to class mapping
    • bin/console debug:container <service_id> --show-arguments
    • bin/console debug:autowiring # get type hints for auto-wiring services
  • List Services for logging
    • php bin/console debug:container log
    • Use the above command syntax to determine the Service class name

Creating a Service

  • Create class in file src/AppBundle/Service/ServiceClassName.php
  • If class requires a service from the Service Container, pass in service object via class constructor - Dependency Injection
  • Implement class methods as necessary
  • Add service definition in services.yml

Symfony 3.3 Service Configuration Changes

  • References
  • Symfony 3.3 made enhancements to Autowiring and other Service
    configuration changes
  • Symfony needs to know the class name and arguments of every Service
  • Before Symfony 3.3 every service had to be added to services.yml
    • Service tags and arguments also had to be defined
  • With Symfony 3.3 a lot of this is automated
  • Instead of explicitly configuring services, only configure what you need

Autowiring

  • Defining Services Dependencies Automatically (Autowiring)
  • Autowiring allows you to manage services in the Service Container with minimal configuration
    • Reads the type-hints in constructors (or other methods) and automatically passes the correct service to each method
    • Eliminates the need to explicitly specify Service arguments
  • Autowiring is configured in services.yml
  • Use bin/console debug:autowiring --all command to get service type-hint when injection a service

Autoconfigure

  • New feature in Symfony version 3.3+
  • When a service is autoconfigured, it means that Symfony will automatically
    tag it when possible
# Example - autoconfigure can automatically tag services implementing a Twig extension
tags:
    - { name: twig.extension }
  • Autoconfigure doesn't work for all tags
    • Doesn't work for doctrine.event_subscriber or form.type_extension tags

Controllers are registered as Services

  • Configuring controllers as private Service is best practice
  • Setting the service as private means $this->get(ServiceClassName) will no longer work
  • If a controller needs a service use Dependency Injection
    • Add the type-hinted service class name directly in the controller method
// Example
// MarkdownTransformer is service class name
class GenusController extends Controller
{
    public function showAction(Genus $genus, MarkdownTransformer $markdownTransformer)
    {
      // ...
    }
}

Example - Add a Cache Service

  • Goal: Add a cache service so we can speed up slow processing on each request

Symfony 3.x

  • Use DoctrineCacheBundle for caching
  • Check via composer that doctrine/doctrine-cache-bundle is installed
  • Add new DoctrineCacheBundle() to app/AppKernel.php
  • Configure cache service
  • Dump configuration
    • ./bin/console debug:config doctrine_cache
  • Add doctrine_cache key in app/config/config.yml
  • Update GenusController:showAction() to use doctrine_cache.providers.my_markdown_cache
  • Cached output lives in var/cache/dev/doctrine/cache/file_system

Symfony 4.x

  • Symfony comes with with a built-in cache service (provided by framework bundle)
$ bin/console debug:autowiring --all | grep cache
 Psr\Cache\CacheItemPoolInterface (cache.app)
 ...
 Symfony\Component\Cache\Adapter\AdapterInterface (cache.app)
 Symfony\Contracts\Cache\CacheInterface (cache.app)
  • Can use any listed interface for type-hint and cache implementation methods
  • Cache configuration is in config/packages/cache.yaml (in framework.yaml previously)

Bundles

  • A bundle is a Symfony plugin - its main job is to add Services
    to the Service Container
  • All functionality in a Symfony application comes from Bundles
  • Sample Bundles
    • FrameworkBundle
    • TwigBundle
    • DoctrineBundle
    • etc
  • In Symfony 3.x Bundles are registered in app/AppKernel.php
  • In Symfony 4.x + Bundles are registered in config/bundles.php

Bundle & Service Configuration

  • List all bundles
./bin/console config:dump
  • Get all Bundle configuration settings (and their default values)
./bin/console config:dump <bundle>
  • Get current Bundle configuration setting values
./bin/console debug:config <bundle>

Symfony 3.x

  • Bundles / Services are configured in app/config/config.yml

Symfony 4.x +

  • All configuration is under config/ directory
  • Most Bundle configuration is under config/packages directory

Environments

  • In Symfony, an Environment is a set of configuration
    • database credentials
    • logging configuration
    • etc
  • Symfony has two environments by default: dev and prod
    • A third environment is test - used for writing automated tests

Symfony 3.x

  • For dev environment web/app_dev.php is run
  • For prod environment web/app.php is run
  • For dev environment (when app_dev.php is run), Symfony loads config_dev.yml
  • For prod environment (when app.php is run), Symfony loads config_prod.yml
  • Use FirePHP Browser extension to see log messages in Dev Tools -> Console

Symfony 4.x +

  • Configuration files are under config/, config/packages/, config/routes/

  • Configuration files for specific environments are in config/packages/{dev,prod,test} and config/routes/{dev,prod,test}

  • Settings in the specific environment files will over-ride those under config/, config/packages/ and config/routes/

  • Configuration file load order

    • config/packages
    • config/packages/dev or config/packages/prod or config/packages/test
    • services.yaml
    • services_dev.yaml
  • For all environments public/index.php (main front controller) is run

  • Environment is passed in to Kernel object

...
new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
...
  • Kernel object methods when called, load config and routing files

Changing Environments

  • In .env file change APP_ENV variable to dev, test, prod
  • Run bin/console cache:clear

Environment Variables

Symfony 3.x

Symfony 4.x +

  • Environment variable syntax '%env(VARIABLE_NAME)'
    • e.g. '%env(SLACK_WEBHOOK_ENDPOINT)'
  • Environment variables can be placed in any config file and are set in .env file
# config/nexy_slack.yaml
nexy_slack:
# The Slack API Incoming WebHooks URL.
    endpoint: '%env(SLACK_WEBHOOK_ENDPOINT)'
...
# .env
...
### CUSTOM VARS
SLACK_WEBHOOK_ENDPOINT=....
### END CUSTOM VARS
...
  • Dump environment variables
$ bin/console about
...
------------------------
  Environment (.env)
 -----------------------
  SLACK_WEBHOOK_ENDPOINT  ....
  APP_ENV                  dev
  APP_SECRET              ....
 ------------------------ 

Parameters - Variables of Configuration

  • In addition to holding Services, the Service Container also hold parameters
  • Parameters have the syntax: %parameter_name%

Show parameters

bin/console debug:container --parameters
bin/console debug:container --parameter=kernel.debug

Symfony 3.x

  • Global parameters can be set in app/config/parameters.yml
  • Parameters can also be set in individual app/config/*.yml files
  • parameters.yml and %kernel.*%
  • app/config/parameters.yml holds machine specific parameters
    • Database credentials
    • Mailer config
    • etc
  • app/config/parameters.yml should NOT be committed to git repo
  • Parameters with kernel.* aren't defined anywhere and are automatically set by
    Symfony
  • app/config/parameters.yml.dist serves as a template for parameters.yml and should be committed to git repo

Symfony 4.x +

  • Best practice is to put all parameters in services.yaml and/or services_dev.yaml using parameters key
# services_dev.yaml
parameters:
    cache_adapter: 'cache.adapter.filesystem'
...
  • Use getParameter() call in controller to retrieve parameters values
// Ex 1
...
 $debug = $this->getParameter('kernel.debug');
...

MakerBundle

  • The Symfony MakerBundle
  • Use MakerBundle to create:
    • custom commands
    • commands for generating code for controllers, form classes, tests etc

Doctrine

  • References
  • Doctrine Database Abstraction Layer (DBAL) and
    Doctrine Object Relational Mapper (ORM) is used to access databases
  • Doctrine DBAL is basically a wrapper around PHP Data Objects (PDO)
    • Can be used completely independent of the Doctrine ORM
  • Doctrine is an Object Relational Mapper (ORM)
    • This means:
      • Each database table maps to an Entity class
      • Each column in the table maps to a class property
  • An Entity is a class that holds data

Using Doctrine

Symfony 3.x

  • Database credentials are in app/config/parameters.yml

  • Class files exist in src/AppBundle/Entity

  • Create database

bin/console doctrine:database:create
  • Create class file src/AppBundle/Entity/Genus.php
    • Use PhpStorm code generation to generate annotations
    • Genus.php will map to database table genus
  • Alternately use bin/console doctrine:generate:entity command to create Entity class
  • Create database table
# Show SQL command to create table
bin/console doctrine:schema:update --dump-sql
CREATE TABLE genus (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB;

# Create table
bin/console doctrine:schema:update --force
Updating database schema...
Database schema updated successfully! "1" query was executed
  • Database table update
  • Add more fields to src/AppBundle/Entity/Genus.php
  • Update genus table to add new fields as columns in genus table
bin/console doctrine:schema:update --dump-sql
ALTER TABLE genus ADD sub_family VARCHAR(255) NOT NULL, ADD species_count INT NOT NULL, ADD fun_fact VARCHAR(255) NOT NULL;
  • Normally you would run bin/console doctrine:schema:update --force
    to update database tables but in a production environment this could break things if for example tables are re-named or data types are modified
  • Use DoctrineMigrationsBundle instead

Symfony 4.x+

  • Install Doctrine
    • composer require doctrine
  • Database credentials are in .env.local
  • Class files exist in src/Entity
  • Create Database
bin/console doctrine:database:create
  • Create Entity class file

    • Ensure MakerBundle is installed
    • Execute bin/console make:entity
  • Create database table

bin/console make:migration
  • Execute migration
bin/console doctrine:migrations:migrate
  • Show migration status
bin/console doctrine:migrations:status
  • Implement Controller methods to save/load data to/from database

Database Migrations

Symfony 3.x

composer require doctrine/doctrine-migrations-bundle
  • Add bundle to AppKernel.php
  • DoctrineMigrationsBundle adds a service but also add a bunch of console commands
bin/console | grep migrations
  doctrine:migrations:diff                Generate a migration by comparing your current database to your mapping information.
  doctrine:migrations:execute             Execute a single migration version up or down manually.
...

Migration Workflow

  • Add code that would affect database and/or database table(s)
  • Don't run bin/console doctrine:schema:update --force
  • Run bin/console doctrine:migrations:diff
    • This creates a Versionxxxx.php file in app/DoctrineMigrations
  • View file and check if ok
  • Run bin/console doctrine:migrations:migrate to execute migration

Symfony 4.x

Migration Workflow

  • For any database change (adding or modifying fields etc) follow these steps:

    • Run bin/console make:entity
      • Can also manually make change(s) in Entity class file
    • Run bin/console make:migration - generates migrations/Versionxxx.php file
    • Run bin/console doctrine:migrations:migrate to execute migration
    • Can also run:
      • bin/console doctrine:migrations:execute YYYYMMDDHHMMSS --down
      • bin/console doctrine:migrations:execute YYYYMMDDHHMMSS --up
  • The make:migration command looks at the Entity file and the database. If it sees differences between the two, it generates the SQL necessary to update the database

Data Fixtures

Symfony 3.x

composer require --dev doctrine/doctrine-fixtures-bundle
composer require --dev nelmio/alice:2.1.4
  • Add file AppBundle/DataFixtures/ORM/LoadFixtures.php
  • Execute load data fixtures command
bin/console doctrine:fixtures:load --append

Fixtures with Alice

  • Add AppBundle/DataFixtures/ORM/fixtures.yml to use Alice

Custom Alice Faker Function

  • In fixtures.yml and LoadFixtures.php add support for
    custom Faker formatter to customize Genus names
  • In fixtures.yml change name: name<()> to name: genus<()>

Symfony 4.x

composer require orm-fixtures --dev --with-all-dependencies
  • Generate fixtures class - This will generate skeleton DataFixtures/xxxFixtures.php
 bin/console make:fixtures
  • Implement fixtures code in DataFixtures/xxxFixtures.php
  • Execute load data fixtures command
bin/console doctrine:fixtures:load           // drop all db rows
bin/console doctrine:fixtures:load --append  // append to db rows

Use Faker library for Seeding Data

Faker is a PHP library that generates fake data for you

  • Install Faker
composer require --dev fzaninotto/faker
composer require --dev davidbadura/faker-markdown-generator
  • Use StofDoctrineExtensionsBundle to
    • Auto create URL slug from titles (Sluggable)
    • Auto create create and updated timestamps on Entities (Timestampable)
  • StofDoctrineExtensionsBundle
composer require stof/doctrine-extensions-bundle

Doctrine Usage

  • Doctrine EntityManager is the Service that saves to and queries a database
  • Implement EntityManager methods in controllers to save/load data to/from database

Persisting Data

  • Use EntityManager->persist() and EntityManager->flush() methods

Data Queries

  • Querying for Objects
  • Use Doctrine EntityRepository class / object
  • EntityRepository class comes with built-in methods to fetch objects
    • find() - find an entity by its primary key / identifier
    • findAll() - find all entities in the repository
    • findOneBy() - find a single entity by a set of criteria
    • findBy - find entities by a set of criteria
    • findOneById(), findOneByName() - Dynamic method names to find a single entity based on a column value
  • All of the above methods return an object of the given Entity type
  • If these methods aren't sufficient, custom queries can be written

Custom Queries

  • How to Create custom Repository Classes
  • Custom queries should be put in their own repository file (Best Practice)
    • e.g. ../Entity/<Entity_Name>Repository.php
  • Three ways to do custom queries
    • Use Doctrine Data Query Language (DQL)
    • Use Query Builder
    • Use Raw SQL

Doctrine Data Query Language (DQL)

  • Doctrine Query Language
  • DQL is similar to SQL syntax but you work with classes instead of tables
  • Use EntityRepository->createQuery() method
class CategoryRepository extends EntityRepository
{
    $dql = 'SELECT cat FROM AppBundle\Entity\Category cat ORDER BY cat.name DESC ';
    $query = $this->getEntityManager()->createQuery($dql);

    // show SQL
    // var_dump($query->getSQL()); die();

    // returns array of category objects
    return $query->execute();  
}

Query Builder

  • Use EntityRepository->createQueryBuilder() method
class CategoryRepository extends EntityRepository {

  public function findAllOrdered() {
    $qb = $this->createQueryBuilder('cat')
      ->addOrderBy('cat.name', 'DESC');
    $query = $qb->getQuery();

    // show DQL
    // var_dump($query->getDQL()); die();

    // show SQL
    // var_dump($query->getSQL()); die();
    return $query->execute();
  }

}
  • For Filtering use andWhere() method
  • To avoid ambiguity don't use orWhere() or where() methods
class CategoryRepository extends EntityRepository {

 public function search($term) {
    $qb =  $this->createQueryBuilder('cat')
      ->andWhere('cat.name LIKE :searchTerm OR cat.iconKey LIKE :searchTerm');
    $query = $qb->getQuery();
    $query->setParameter('searchTerm', '%'. $term . '%');

    // show SQL
    $sql = $query->getSQL();
    // var_dump($sql); die();

    // returns an array of category objects
    return $query->execute();
  }
}
  • JOIN example
  • Note that adding addSelect() may help in reducing number of queries involving a JOIN
class CategoryRepository extends EntityRepository {

 public function searchAll($term) {
    // Search category, icon and fortune itself
    $qb =  $this->createQueryBuilder('cat')
      ->andWhere('cat.name LIKE :searchTerm 
          OR cat.iconKey LIKE :searchTerm
          OR fc.fortune LIKE :searchTerm'
      )
      ->innerJoin('cat.fortuneCookies', 'fc')
      ->addSelect('fc')
      ->setParameter('searchTerm', '%'. $term . '%');

    $query = $qb->getQuery();

    // show SQL
    $sql = $query->getSQL();
    // var_dump($sql); die();

    // returns an array of category objects
    return $query->execute();

  }
}
  • Use select() to select specific fields
class FortuneCookieRepository extends EntityRepository
{

  public function countNumberPrintedForCategory(Category $category) {
    $qb = $this->createQueryBuilder('fc')
      ->andWhere('fc.category = :category')
      ->setParameter('category', $category)
      ->join('fc.category', 'cat')
      ->select(
        'SUM(fc.numberPrinted) as fortunesPrinted,
            AVG(fc.numberPrinted) fortunesAverage, cat.name'
        );

    $query = $qb->getQuery();

    $sql = $query->getSQL();

    // returns associate array
    return $query->getOneOrNullResult();
  }
}

Raw SQL

class FortuneCookieRepository extends EntityRepository
{
    public function countNumberPrintedForCategory(Category $category) {
        $conn = $this->getEntityManager()
            ->getConnection();

        $sql = '
            SELECT SUM(fc.numberPrinted) as fortunesPrinted, AVG(fc.numberPrinted) as fortunesAverage, cat.name
            FROM fortune_cookie fc
            INNER JOIN category cat ON cat.id = fc.category_id
            WHERE fc.category_id = :category
            ';
        $stmt = $conn->prepare($sql);
        $stmt->execute(array('category' => $category->getId()));

        // returns associative array
        return $stmt->fetch();
    }
}

Doctrine Filters

Pagination

Doctrine Relationships

Symfony 3.x

  • Create skeleton GenusNote Entity - will eventually map to genus_note table
    database
bin/console doctrine:generate:entity AppBundle:GenusNote
  • Add genus_note table to database
bin/console doctrine:migrations:diff
bin/console doctrine:migrations:migrate
  • Update fixtures.yml file
  • Add dummy data to genus_note table
bin/console doctrine:fixtures:load --append

ManytoOne Relationships

  • Analyze relationship between tables
    • Is the relationship ManytoOne or ManytoMany ?
  • For genus and genus_note it's ManyToOne
  • One genus can have MANY notes and many notes point to ONE genus
  • Add genus property along with ManyToOne annotation in GenusNote class
  • In the end (after database is updated) this will have the effect of adding
    genus_id and foreign key constraint in genus_note table
/**
   * @ORM\ManyToOne(targetEntity="AppBundle\Entity\Genus")
   */
  private $genus;
  • Update database
bin/console doctrine:migrations:diff 
bin/console doctrine:migrations:migrate 

Join Column Annotation

  • By default we can save a genus note without a corresponding genus
    • For integrity we should prevent this
  • Add JoinColumn annotation to genus property in GenusNote
@ORM\JoinColumn(nullable=false)
  • Drop genus_note table
  • Re-create genus_note table, update fixtures.yml to add genus_id
    when creating a genus_note
  • Load new set of genus notes each with a genus_id
bin/console doctrine:fixtures:load --append

OneToMany: Inverse Side of the Relation

  • In GenusController:getNotesAction method we have a genus object but how do
    we get the related notes for the object ?
  • Using SQL and given we have the genus object (and id) we would do this
select * from genus_note where genus_id = <genus id>;
  • Let's do it though in a more 'elegant' way
  • Implement some Doctrine "sugar"
  • We would like to be able to do $genusNote->getGenus() to access a genus from
    a genus note object and $genus->getGenusNote() to acess notes from a genus
  • Add $notes property and OneToMany annotation to Genus class
  • Add Genus constructor
public function __construct() {
    $this->notes = new ArrayCollection();
  }
  • When you have a table relationship
    • Figure out which entity/table should have the Foreign Key column
    • Add the ManyToOne annotation to the related property / table
      • This is the only side of the relationship that is required
      • It's called the "Owning" side
    • Mapping the other side - the OneToMany inverse side - is always optional
      • Add the OneToMany annotation if JOIN queries will be needed

Order By with a OneToMany

  • Using $genus->getNotes() shortcut gives few query options but we can control the order of the notes returned
  • In Genus class add @ORM\OrderBy({"createdAt" = "DESC"}) annotation
    to $notes property

Tricks with ArrayCollection

  • In GenusController:showAction use ArrayCollection filter function
    to get notes < 3 months old
  • Loading all objects though will incur a performance hit if hundreds/thousands
    millions of records exist
  • Solution is to use a custom query - See below

Querying on a Relationship

  • Use EntityRepository createQueryBuilder method
  • Implement custom query findAllRecentNotesForGenus in GenusNoteRepository
class GenusNoteRepository extends EntityRepository
{
  /**
   * @param \AppBundle\Entity\Genus $genus
   * @return Genus array
   */
  public function findAllRecentNotesForGenus(Genus $genus) {
    return $this->createQueryBuilder('genus_note')
      ->andWhere('genus_note.genus = :genus')
      ->setParameter('genus', $genus)
      ->andWhere('genus_note.createdAt > :recentDate')
      ->setParameter('recentDate', new \DateTime('-3 months'))
      ->getQuery()
      ->execute();
  }
}

Query across a JOIN

  • Use EntityRepository createQueryBuilder method
  • Implement custom query findAllPublishedOrderedByRecentlyActive in GenusRepository
  /**
   * @return \AppBundle\Entity\Genus array
   */
  public function findAllPublishedOrderedByRecentlyActive() {
    return $this->createQueryBuilder('genus')
      ->andWhere('genus.isPublished = :isPublished')
      ->setParameter('isPublished', TRUE)
      ->leftJoin('genus.notes', 'genus_note')
      ->getQuery()
      ->execute();
  }

Symfony 4.x

  • Run bin/console make:entity to create new Entity / db table
  • This new Entity will have a data relationship to a current Entity
  • Add field (i.e. Foreign Key) in new Entity to establish relation
  • Choose relation as field type and follow prompts given by bin/console make:entity
$ bin/console make:entity

 Class name of the entity to create or update (e.g. DeliciousPopsicle):
 > Comment

 Your entity already exists! So let's add some new fields!

 New property name (press <return> to stop adding fields):
 > article

 Field type (enter ? to see all types) [string]:
 > relation

 What class should this entity be related to?:
 > Article

What type of relationship is this?
 ------------ ---------------------------------------------------------------------- 
  Type         Description                                                           
 ------------ ---------------------------------------------------------------------- 
  ManyToOne    Each Comment relates to (has) one Article.                            
               Each Article can relate to (can have) many Comment objects            
                                                                                     
  OneToMany    Each Comment can relate to (can have) many Article objects.           
               Each Article relates to (has) one Comment                             
                                                                                     
  ManyToMany   Each Comment can relate to (can have) many Article objects.           
               Each Article can also relate to (can also have) many Comment objects  
                                                                                     
  OneToOne     Each Comment relates to (has) exactly one Article.                    
               Each Article also relates to (has) exactly one Comment.               
 ------------ ---------------------------------------------------------------------- 

 Relation type? [ManyToOne, OneToMany, ManyToMany, OneToOne]:
 > ManyToOne

...
  • Run bin/console make:migration and bin/console doctrine:migrations:migrate

Forms

Form Theming

  • If using Bootstrap add form_themes key to twig configuration in config.yml
# Twig Configuration
twig:
...
    form_themes:
      - bootstrap_3_layout.html.twig
...

Form Re-direction and Status Message

  • Example
 // Controller code
 // Add message
 ...
 $this->addFlash('success', 'Genus ' . $genus->getName . ' created.');
 return $this->redirectToRoute('admin_genus_list');
 ...
 // base.html.twig
 // Display message
 ...
 {% for msg in app.session.flashBag.get('success') %}
    <div class="alert alert-success">
      {{ msg }}
    </div>
 {% endfor %}
 ...

Form Date Field/Date Picker

  • Some browsers (e.g. Safari) don't support HTML5 date field <input type="date" ...>
  • For best UI experience best to use JavaScript based date picker
  • If using Bootstrap use bootstrap-datepicker

Form Validation

Client side/HTML5 Validation

  • By default Symfony will add required='required' to all form fields - this enables
    HTML5 validation at browser/client end
  • It's normally desirable to disable client side form validation and perform it server
    side
  • One option is to use Symfony bundle JsFormValidatorBundle - dumps your server-side
    rules into JavaScript
  • Excerpt to disable client side validation - one way to accomplish this
...
->add('save', SubmitType::class, [
        'attr' => [
          'class' => 'btn btn-primary',
          'formnovalidate' => TRUE,
        ]
      ]);
...

Server Side Validation

  • Validation isn't directly done in form code - instead validation is added to the
    class that is bound to the form
  • When the form is submitted, it automatically reads the validation rules and uses them
  • Validation rules are called Constraints
  • Validation is added with Annotations

Best Practice/Tips

  • When form is directly bound to an Entity class form handling is easy
  • If needed form does not directly map to an Entity class
    (perhaps it has a few extra fields or is a combination of fields from several Entities
  • Take these steps -
    • Don't bind form to Entity class
    • Create a new class (inside src/AppBundle/Form directory) i.e SomeClassModel
    • Add all needed properties for the form in SomeClassModel
    • Bind the class to your form and add all Validation rules like normal
    • After form submission, $form->getData() will return SomeClassModel object
    • Write code to extract Entity related data and write to database etc.

Security

Authentication

  • Authentication asks "Who are you?"
  • Ways to authenticate
    • Login forms
    • Social Media Authentication
    • APIs

Authorization

  • Authorization is concerned with user permissions
  • Deals with User Roles and Access Control
    • Once a user is logged in, what pages and our web site resources can he/she use

Symfony 3.x

  • Security and authentication is configured in security.yml

Create Authentication Facility - User Login

  • Create User Entity class
class User implements UserInterface {
 ...
}
  • Add User properties / columns with @ORM annotation
  • Implement UserInterface methods
  • Run bin/console doctrine:migrations:diff and bin/console doctrine:migrations:migrate to create user table in database
  • Set up fixture to load dummy users
    • Run bin/console doctrine:fixtures:load to load users
  • Create Login Form
  • Configure security related parameters in app/config/security.yml
  • Configure how users will authenticate (i.e login)
  • Configure how users are loaded - User Provider
  • Configure how user password is encoded
    • Symfony comes with built-in security.password_encode service that encoded passwords

Symfony 4.x +

Create Authentication Facility - User Login

  • Run composer require security to install SecurityBundle (Uses Flex!)
  • Security settings are in config/packages/security.yaml
  • Create User Entity class using bin/console make:user
  • If needed, update User Entity class using bin/console make:entity
  • Run bin/console make:migration and bin/console doctrine:migrations:migrate to add user table to database
  • Configure how users will authenticate (i.e login)
  • Configure how users are loaded - User Provider
  • Configure how user password is encoded
    • Symfony comes with built-in security.password_encode service that encoded passwords

Authorization

Tips

FOSUserBundle

  • This Bundle is useful for managing users as it provides
    • A User Entity
    • Login form, registration and reset password functionality
    • Routes and Controllers
  • It does not however handle security

APIs

Symfony Deep Dive

Request - Response

  • The heart of what happens during the Request - Response process in Symfony centers around the HttpKernel class
  • Drupal, phpBB and other projects use HttpKernel class
  • At a high level, here's what happens during this process
    • Symfony Boots
    • Triggers some events
    • Executes a controller
    • Dispatches other events
  • See HttpKernel.php::handle() and handleRaw() for main flow
  • Use $request->attributes->set() to add/alter Request info
    • This can be used to modify controller, add / modify parameters etc.

Event Listeners

Sub Requests

Dependency Injection Container

⚠️ **GitHub.com Fallback** ⚠️