HOWTO.symfony - raynaldmo/HOWTO GitHub Wiki
- Table of Contents
- Symfony
-
Symfony Upgrades
* 2.x to 3.0.x
* Upgrade 2.x to 2.8.x
* Upgrade 2.8.x to 3.0.x
* 3.x to 3.x
* Upgrade from 3.x to Symfony 4 and Flex
* Update project to support Symfony Flex
* Deprecation Fixing Tools -
Symfony Fundamentals
- Web Profiler & Debugging
- Caching
- Controllers
- Routes
- ParamConverter
- Twig Templates
- Loading Assets (css, javascript, images)
- Twig Extensions
- Generating URLs
- Services
- Bundles
- Environments
- Environment Variables
- Parameters - Variables of Configuration
- MakerBundle
- Doctrine
- Database Migrations
- Data Fixtures
- Doctrine Usage
- Doctrine Relationships
- Forms
- Security
- APIs
- Symfony Deep Dive
- Custom Sidebar
- Symfony
- Symfony - GitHub
- Symfony Standard Edition
- Symfony versus Flat PHP
- Difference between PHP-CGI and PHP-FPM
- https://bitbucket.org/raynaldmo/symfony-3-training
- https://bitbucket.org/raynaldmo/symfony-doctrine-queries/
- Symfony 3 Best Practices/Demo
- 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
- src/ - Holds all PHP files (classes)
- app/ - Holds configuration, templates
- web/ - Holds publicly accessible files (html, css, js, images etc.)
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.
- Go to
localhost:<port>or127.0.0.1:<port>
The Symfony Framework Best Practices
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
....
- 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
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.
- 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
- Symfony Local Web Server
- Download new
symfonybinary first Download Symfony - Run server
raynald@Raynalds-iMac ~/PhpStormProjects/symfony4-training (master) $ symfony server:start --port=8001
- Be sure to remove older web server
(WebserverBundle)
composer remove server
- Go to
localhost:<port>or127.0.0.1:<port>
- Use Symfony Console (CLI)
bin/consoleto debug and show configuration, routes, services etc
- The Easiest Way To Debug A Symfony Application in PhpStorm
- Beginner's Guide to PhpStorm - Tips & Tricks
- References
- Upgrade to 2.8.x first then to 3.0.x
- 2.8.x and 3.0 have the same features
- Update
symfony/symfonypackage version incomposer.jsonto 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
- Update
symfony/symfonypackage version incomposer.jsonto 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 updateto update all packages - If update fails, check
requiressection 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.jsonetc. 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
- Update
symfony/symfonypackage version incomposer.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
- Update to
symfony/symfony ^3.4first- 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
- 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, andremovecommands
- Modifies/enhances the behavior of the composer
composer require symfony/flex- When you install a package, Flex checks to see if there is a recipe for that package
- A recipe can:
- Add configuration files
- Auto-enable the Bundle
- Add paths to your
.gitignorefile - and more
- Flex introduces a new directory structure, different from Symfony 3.x
- Using Symfony Flex to Manage Symfony Applications
- Upgrading Existing Applications to Symfony Flex
- Symfony Recipes
- Symfony Recipes Server
- Install
symfony/web-profiler-bundle (profiler)for web profiler - Use
dump(var1, var2...)in controllers and other code - Use
dump()ordump(var1, var2...)in Twig templates
- Clear caches
./bin/console cache:clear --env=dev
./bin/console cache:clear --env=prod
- 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
- In
devenvironment routes are loaded fromapp/config/routing_dev.yml - In all environments main routes are loaded from
app/config/routing.yml
-
config/routes.yaml -
Third-party Bundles can add routes
-
Show routes
bin/console debug:router
- Example wildcard route:
@Route("/genus/{genusName}")
- @ParamConverter
- The
@ParamConverterannotation 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-bundleto be installed
- Twig templates reside in
app/Resources/views
- Twig templates reside in
templates/
- Use Twig
{{ asset('..') }}function load css, javascript and images
-
web/directory is web server document root and are publicly accessible - Files outside of
web/are not publicly accessible
-
public/directory is web server document root and are publicly accessible - Files outside of
public/are not publicly accessible
- Use
bin/console make:twig-extensioncommand to generatesrc/Twig/AppExtension.phpfile
- When creating links to other pages, instead of hardcoding URLs in templates,
use the Twigpath()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') }}>
- 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.ymlfeatures some additional options
- NOTE: Beginning with Symfony 3.3 services are configured differently and
- 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
- 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
- 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
- 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
- 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_subscriberorform.type_extensiontags
- Doesn't work for
- Configuring controllers as
privateService 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)
{
// ...
}
}
- Goal: Add a cache service so we can speed up slow processing on each request
- Use
DoctrineCacheBundlefor caching - Check via composer that
doctrine/doctrine-cache-bundleis installed - Add
new DoctrineCacheBundle()toapp/AppKernel.php - Configure cache service
- Dump configuration
./bin/console debug:config doctrine_cache
- Add
doctrine_cachekey inapp/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 comes with with a built-in cache service (provided by
frameworkbundle)
$ 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(inframework.yamlpreviously)
- 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
FrameworkBundleTwigBundleDoctrineBundle- etc
- In Symfony 3.x Bundles are registered in
app/AppKernel.php - In Symfony 4.x + Bundles are registered in
config/bundles.php
- 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>
- Bundles / Services are configured in
app/config/config.yml
- All configuration is under
config/directory - Most Bundle configuration is under
config/packagesdirectory
- In Symfony, an Environment is a set of configuration
- database credentials
- logging configuration
- etc
- Symfony has two environments by default:
devandprod- A third environment is
test- used for writing automated tests
- A third environment is
- For dev environment
web/app_dev.phpis run - For prod environment
web/app.phpis run - For dev environment (when
app_dev.phpis run), Symfony loadsconfig_dev.yml - For prod environment (when
app.phpis run), Symfony loadsconfig_prod.yml - Use FirePHP Browser extension to see log messages in Dev Tools -> Console
-
Configuration files are under
config/,config/packages/,config/routes/ -
Configuration files for specific environments are in
config/packages/{dev,prod,test}andconfig/routes/{dev,prod,test} -
Settings in the specific environment files will over-ride those under
config/,config/packages/andconfig/routes/ -
Configuration file load order
config/packages-
config/packages/devorconfig/packages/prodorconfig/packages/test services.yamlservices_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']);
...
-
Kernelobject methods when called, load config and routing files
- In
.envfile changeAPP_ENVvariable todev, test, prod - Run
bin/console cache:clear
- Environment variable syntax
'%env(VARIABLE_NAME)'- e.g.
'%env(SLACK_WEBHOOK_ENDPOINT)'
- e.g.
- Environment variables can be placed in any config file and are set in
.envfile
# 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 ....
------------------------
- In addition to holding Services, the Service Container also hold parameters
-
Parameters have the syntax:
%parameter_name%
bin/console debug:container --parameters
bin/console debug:container --parameter=kernel.debug
- Global parameters can be set in
app/config/parameters.yml - Parameters can also be set in individual
app/config/*.ymlfiles -
parameters.ymland%kernel.*% -
app/config/parameters.ymlholds machine specific parameters- Database credentials
- Mailer config
- etc
-
app/config/parameters.ymlshould NOT be committed to git repo - Parameters with
kernel.*aren't defined anywhere and are automatically set by
Symfony -
app/config/parameters.yml.distserves as a template forparameters.ymland should be committed to git repo
- Best practice is to put all parameters in
services.yamland/orservices_dev.yamlusingparameterskey
# 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');
...
- The Symfony MakerBundle
- Use MakerBundle to create:
- custom commands
- commands for generating code for controllers, form classes, tests etc
- 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
- This means:
- An Entity is a class that holds data
-
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.phpwill map to database tablegenus
- Alternately use
bin/console doctrine:generate:entitycommand 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
genustable
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
DoctrineMigrationsBundleinstead
- 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
MakerBundleis installed - Execute
bin/console make:entity
- Ensure
-
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
- References
- Install the bundle
composer require doctrine/doctrine-migrations-bundle
- Add bundle to AppKernel.php
-
DoctrineMigrationsBundleadds 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.
...
- 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 fileinapp/DoctrineMigrations
- This creates a
- View file and check if ok
- Run
bin/console doctrine:migrations:migrateto execute migration
-
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- generatesmigrations/Versionxxx.phpfile - Run
bin/console doctrine:migrations:migrateto execute migration - Can also run:
bin/console doctrine:migrations:execute YYYYMMDDHHMMSS --downbin/console doctrine:migrations:execute YYYYMMDDHHMMSS --up
- Run
-
The
make:migrationcommand looks at the Entity file and the database. If it sees differences between the two, it generates the SQL necessary to update the database
- Use
DoctrineFixturesBundleandnelmio/alicelibrary - See
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
- Add
AppBundle/DataFixtures/ORM/fixtures.ymlto use Alice
- In
fixtures.ymlandLoadFixtures.phpadd support for
custom Faker formatter to customize Genus names - In
fixtures.ymlchangename: name<()>toname: genus<()>
- DoctrineFixturesBundle
- Install
doctrine/doctrine-fixtures-bundle (DoctrineFixturesBundle)
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
Faker is a PHP library that generates fake data for you
- Install Faker
composer require --dev fzaninotto/faker
- Optionally use FakerMarkdownGenerator to create dummy Markdown data
composer require --dev davidbadura/faker-markdown-generator
- Use
StofDoctrineExtensionsBundleto- Auto create URL
slugfrom titles (Sluggable) - Auto create
createandupdatedtimestamps on Entities (Timestampable)
- Auto create URL
- StofDoctrineExtensionsBundle
composer require stof/doctrine-extensions-bundle
- Doctrine
EntityManageris the Service that saves to and queries a database - Implement
EntityManagermethods in controllers to save/load data to/from database
- Use
EntityManager->persist()andEntityManager->flush()methods
- Querying for Objects
- Use Doctrine
EntityRepositoryclass / object -
EntityRepositoryclass 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
- 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
- e.g.
-
Three ways to do custom queries
- Use Doctrine Data Query Language (DQL)
- Use Query Builder
- Use Raw SQL
- 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();
}
- 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()orwhere()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();
}
}
-
JOINexample - Note that adding
addSelect()may help in reducing number of queries involving aJOIN
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();
}
}
- See Data Retrieval And Manipulation
- Example
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();
}
}
- How to Work with Doctrine Associations / Relations
- Abstraction layer that deals with defining database table relationships
- i.e How are the tables JOIN-ed
- 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
- 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
GenusNoteclass - In the end (after database is updated) this will have the effect of adding
genus_idand foreign key constraint ingenus_notetable
/**
* @ORM\ManyToOne(targetEntity="AppBundle\Entity\Genus")
*/
private $genus;
- Update database
bin/console doctrine:migrations:diff
bin/console doctrine:migrations:migrate
- By default we can save a genus note without a corresponding genus
- For integrity we should prevent this
- Add
JoinColumnannotation togenusproperty inGenusNote
@ORM\JoinColumn(nullable=false)
- Drop genus_note table
- Re-create genus_note table, update
fixtures.ymlto addgenus_id
when creating a genus_note - Load new set of genus notes each with a
genus_id
bin/console doctrine:fixtures:load --append
- In
GenusController:getNotesActionmethod 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 agenusfrom
agenus noteobject and$genus->getGenusNote()to acess notes from agenus - Add
$notesproperty andOneToManyannotation to Genus class - Add
Genusconstructor
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
ManyToOneannotation 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
OneToManyinverse side - is always optional- Add the
OneToManyannotation ifJOINqueries will be needed
- Add the
- Using
$genus->getNotes()shortcut gives few query options but we can control the order of the notes returned - In
Genusclass add@ORM\OrderBy({"createdAt" = "DESC"})annotation
to$notesproperty
- In
GenusController:showActionuseArrayCollectionfilterfunction
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
- Use
EntityRepositorycreateQueryBuildermethod - Implement custom query
findAllRecentNotesForGenusinGenusNoteRepository
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();
}
}
- Use
EntityRepositorycreateQueryBuildermethod - Implement custom query
findAllPublishedOrderedByRecentlyActiveinGenusRepository
/**
* @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();
}
- Run
bin/console make:entityto 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
relationas field type and follow prompts given bybin/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:migrationandbin/console doctrine:migrations:migrate
- References
- Forms are built by
form.factoryService - Add forms in
src/AppBundle/Form
- If using Bootstrap add
form_themeskey to twig configuration inconfig.yml
# Twig Configuration
twig:
...
form_themes:
- bootstrap_3_layout.html.twig
...
- 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 %}
...
- 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
- References
- 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,
]
]);
...
- 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
- 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.eSomeClassModel - 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 returnSomeClassModelobject - Write code to extract Entity related data and write to database etc.
- References
- Security has two big parts: Authentication and Authorization
- Authentication asks "Who are you?"
- Ways to authenticate
- Login forms
- Social Media Authentication
- APIs
- 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
- Security and authentication is configured in
security.yml
- Create User Entity class
class User implements UserInterface {
...
}
- Add User properties / columns with
@ORMannotation - Implement
UserInterfacemethods - Run
bin/console doctrine:migrations:diffandbin/console doctrine:migrations:migrateto create user table in database - Set up fixture to load dummy users
- Run
bin/console doctrine:fixtures:loadto load users
- Run
- Create Login Form
- Configure security related parameters in
app/config/security.yml - Configure how users will authenticate (i.e login)
- Options
- HTTP basic authentication
http_basic - Login form -
form_login - OAuth
- Custom authentication provider (Guard and others)
- HTTP basic authentication
- Options
- Configure how users are loaded - User Provider
- Security User Providers
- Use Symfony user provider
memory,entity,ldap,chain - Use custom User Provider
- Configure how user password is encoded
- Symfony comes with built-in
security.password_encodeservice that encoded passwords
- Symfony comes with built-in
- Run
composer require securityto 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:migrationandbin/console doctrine:migrations:migrateto addusertable to database - Configure how users will authenticate (i.e login)
- Options
- HTTP basic authentication
http_basic - Login form -
form_login- How to Build a Traditional Login Form
- Use
bin/console make:authcommand
- OAuth
- Custom authentication provider (Guard and others)
- HTTP basic authentication
- Options
- Configure how users are loaded - User Provider
- Security User Providers
- Use Symfony user provider
memory,entity,ldap,chain - Use custom User Provider
- Configure how user password is encoded
- Symfony comes with built-in
security.password_encodeservice that encoded passwords
- Symfony comes with built-in
- References
- There's two main ways implement Authorization
- Access Control
- Use
access_controlkey and Roles insecurity.yml
- Use
- Allow/deny access directly in Controllers
- Access Control
- Assign user Roles to help implement authorization policy
- 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
- Create your own PHP Framework
- The HttpKernel Component
- The Symfony framework can be broken up into two main parts
- The Request/Routing/Controller/Response and Event Listener
- The Dependency Injection Container
- The heart of what happens during the Request - Response process in Symfony centers
around the
HttpKernelclass - Drupal, phpBB and other projects use
HttpKernelclass - 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()andhandleRaw()for main flow - Use
$request->attributes->set()to add/alter Request info- This can be used to modify controller, add / modify parameters etc.