New Media Design & Development III - The Symfony Framework Academiejaar 2014-2015, Arteveldehogeschool ©2014 Olivier Parent
[TOC]
Zie AHS Laravel Homestead voor instructies.
De domeinnamen die we gaan gebruiken:
- Productie: http://www.nmdad-iii.arteveldehogeschool.be/
- Development: http://dev.nmdad-iii.arteveldehogeschool.be/
Deze domeinen zijn toegevoegd aan Homestead.yaml
:
sites:
- map: dev.nmdad-iii.arteveldehogeschool.be
to: /home/vagrant/Code/nmdad-iii.arteveldehogeschool.be/www/web/
sh: symfony_dev
- map: www.nmdad-iii.arteveldehogeschool.be
to: /home/vagrant/Code/nmdad-iii.arteveldehogeschool.be/www/web/
sh: symfony_prod
Tip: Elke webapplicatie heeft bij voorkeur een eigen databasegebruiker met zo weinig mogelijk rechten. Zo maak je het leven van hackers lastiger en bovendien kan de ene applicatie niet per ongeluk de database van een andere applicatie beschadigen.
We gaan een nieuwe databasegebruiker aanmaken voor onze applicatie. We geven die een makkelijk te onthouden naam, en wachtwoord, maar denk eraan dat die voor een productieserver onvoldoende complex zijn.
Zie ook:
Meld je aan op de databaseserver via de MySQL Command-Line Tool en gebruik als credentials:
- Databasegebruikersnaam:
homestead
- Databasewachtwoord:
secret
Aanmelden doe je via mysql
:
vagrant@homestead$ mysql -uhomestead -p
password: _
mysql> _
Afmelden doe je met exit
mysql> exit
Bye
vagrant@homestead$ _
Zie ook:
Open het configuratiebestand my.cnf
met een teksteditor:
vagrant@homestead$ sudo nano /etc/mysql/my.cnf
Zoek [mysqld]
(MySQL Server Daemon) met <ctrl>
+<w>
en er onder voegen we dit toe:
character-set-server = utf8
collation-server = utf8_unicode_ci
Herstart tenslotte de MySQL Server met:
vagrant@homestead$ sudo service mysql restart
Controleer de instellingen:
mysql> SHOW VARIABLES LIKE 'character\_set\_%';
mysql> SHOW VARIABLES LIKE 'collation%';
Of met één enkele query:
mysql> SHOW VARIABLES WHERE Variable_name LIKE 'character\_set\_%' OR Variable_name LIKE 'collation%';
We moeten enkel een nieuwe databasegebruiker aanmaken en rechten geven. Dit kan met één enkel SQL-statement:
mysql> GRANT ALL PRIVILEGES
-> ON `nmdad3_arteveldehogeschool_be`.*
-> TO 'nmdad3_db_user' IDENTIFIED BY 'nmdad3_db_password';
De database bestaat nog niet, maar de nieuwe databasegebruiker heeft nu wel rechten om die zelf aan te maken. We gaan de database niet rechtstreeks in de MySQL Command-Line Tool aanmaken, maar later via de Symfony Console.
Symfony is niet alleen een opensource web framework, maar ook een verzameling losstaande componenten die in andere projecten waaronder Laravel (sinds versie 4) of Drupal (sinds versie 8) gebruikt worden.
Symfony word ontwikkeld door Fabien Potencier (@fabpot) en wordt door zijn bedrijf SensioLabs.
Populair, goede reputatie en Enterprise Level
- Wordt gebruikt door grote organisaties zoals onder meer de BBC en TED gebruikt.
- Uitstekende documentatie.
- Wordt ondersteund door populaire IDE's waaronder NetBeans IDE en PhpStorm.
Matuur, actief ontwikkeld en op regelmatige basis geüpdatet
- In ontwikkeling sinds 2005
- Symfony 2.0 in juli 2011 gereleaset
- Strikte releaseplanning voor de volgende jaren en met LTS (Long Term Support) releases.
Modern, state of the art en front-end friendly
- PHP 5.3.3+
- Volgt de industriestandaarden (PSR, PHPUnit) en maakt gebruik van architecurale en design patterns (MVC, DI …) en best practices.
- Twig is de geïntegreerde Templating Engine.
Opensource
- MIT License
- GitHub repository
- Interoperabiliteit met andere opensource projecten waaronder PHPunit, Doctrine en Propel
Sterke Documentatie, Community en Commerciële diensten
- The Book, The Cookbook, The components, Best Practices
- Video's
- Forum
- Jaarlijkse conferenties: SymfonyCon Warsaw 2013, SymfonyCon Madrid 2014
- Symfony Live conferenties
- SensioLabs levert allerlei commerciële diensten waaronder zelfs officiële certificatie.
Om een beetje vertrouwd te geraken met Symfony kan je de 10 minuten durende Quick Tour volgen.
We gaan het framework met Composer installeren, maar eerst moeten we er zeker van zijn dat we de allerlaatste versie van Composer gebruiken door te updaten:
vagrant@homestead$ sudo composer self-update
of van achter de proxy:
vagrant@homestead$ proxy on
vagrant@homestead$ sudo -E composer self-update
Tip: Als te veel anonieme requests van hetzelfde IP-adres komen, kan GitHub vragen dat je aanmeldt. Mocht het nodig zijn, dan kan je je GitHub-credentials als volgt configureren in Git::
vagrant@homestead$ git config --global user.name "Olivier Parent" vagrant@homestead$ git config --global user.email "olivier.parent@arteveldehs.be"
We moeten eerst een map maken voor het project. Als naam nemen we dezelfde naam als de domeinnaam:
vagrant@homestead$ mkdir ~/Code/nmdad-iii.arteveldehogeschool.be
vagrant@homestead$ cd ~/Code/nmdad-iii.arteveldehogeschool.be/
In de nieuwe map gaan we met Composer het framework (symfony/framework-standard-edition
) installeren in een submap www
:
vagrant@homestead$ composer create-project symfony/framework-standard-edition www/
Gebruik volgende opties:
* Would you like to install Acme demo bundle [y/N] y
* Some parameters are missing. please provide them.
* database_driver (pdo_mysql): <return>
* database_host (127.0.0.1): localhost
* database_port (null): <return>
* database_name (symfony): nmdad3_arteveldehogeschool_be
* database_user (root): nmdad3_db_user
* database_password (null): nmdad3_db_password
* mailer_transport (smtp): <return>
* mailer_host (127.0.0.1): <return>
* mailer_user (null): <return>
* mailer_password (null): <return>
* locale (en): <return>
* secret (ThisTokenIsNotSoSecretChangeIt): <return>
* debug_toolbar (true): <return>
* debug_redirects (false): <return>
* use_assetic_controller (true): <return>
Eens het framework geïnstalleerd is, kan je het met Composer updaten naar de laatste versie:
vagrant@homestead$ cd ~/Code/nmdad-iii.arteveldehogeschool.be/www/
vagrant@homestead$ composer update
OPGELET: Na een Composer update kan het nodig zijn dat je de beveiliging opnieuw aangepast moet worden!
Omdat we voor de lokale ontwikkelomgeving niet het domein localhost
gebruiken, moeten we de beveiliging aanpassen die verhindert dat bestanden die enkel binnen de ontwikkelomgeving gebruikt mogen worden op de productieomgeving uitgevoerd kunnen worden.
De twee bestanden die we moeten aanpassen zijn app_dev.php
en config.php
.
Mappenstructuur
nmdad-iii.arteveldehogeschool.be/ └── www/ └── web/ ├── app_dev.php └── config.php
Dit doen we eenvoudigweg door de beveiliging enkel uit te voeren als het domein niet dev.nmdad-iii.arteveldehogeschool.be
is.
if ('dev.nmdad-iii.arteveldehogeschool.be' !== $_SERVER['HTTP_HOST']) {
…
}
if ('dev.nmdad-iii.arteveldehogeschool.be' !== $_SERVER['HTTP_HOST']) {
if (isset($_SERVER['HTTP_CLIENT_IP'])
|| isset($_SERVER['HTTP_X_FORWARDED_FOR'])
|| !(in_array(@$_SERVER['REMOTE_ADDR'], array('127.0.0.1', 'fe80::1', '::1')) || php_sapi_name() === 'cli-server')
) {
header('HTTP/1.0 403 Forbidden');
exit('You are not allowed to access this file. Check '.basename(__FILE__).' for more information.');
}
}
Test met http://dev.nmdad-iii.arteveldehogeschool.be/
if ('dev.nmdad-iii.arteveldehogeschool.be' !== $_SERVER['HTTP_HOST']) {
if (!in_array(@$_SERVER['REMOTE_ADDR'], array(
'127.0.0.1',
'::1',
))
) {
header('HTTP/1.0 403 Forbidden');
exit('This script is only accessible from localhost.');
}
}
Test met http://dev.nmdad-iii.arteveldehogeschool.be/config.php
De volgende stap is controleren of de serveromgeving voldoet aan de vereisten van Symfony.
Mappenstructuur
nmdad-iii.arteveldehogeschool.be/ └── www/ └── app/ └── check.php
vagrant@homestead$ cd ~/Code/nmdad-iii.arteveldehogeschool.be/www/
vagrant@homestead$ php app/check.php
We krijgen twee waarschuwingen:
xdebug.max_nesting_level should be above 100 in php.ini
Set "xdebug.max_nesting_level" to e.g. "250" in php.ini* to stop Xdebug's infinite recursion protection erroneously throwing a fatal error in your project.
intl extension should be available
Install and enable the intl extension (used for validators).
We passen het Xdebug-configuratiebestand aan met nano
:
vagrant@homestead$ sudo nano /etc/php5/mods-available/xdebug.ini
En voegen volgende lijn toe om de waarschuwing op te lossen:
xdebug.max_nesting_level = 250
Tenslotte moeten we de PHP-service herstarten om de wijzigingen actief te maken op de webserver:
vagrant@homestead$ sudo service php5-fpm restart
Tip: Je kan de lijn ook toevoegen met één enkele opdracht:
vagrant@homestead$ sudo sh -c "echo xdebug.max_nesting_level = 250 >> /etc/php5/mods-available/xdebug.ini" && sudo service php5-fpm restart
De PHP Internationalization Functions installeer je met:
vagrant@homestead$ sudo apt-get update && sudo apt-get install -y php5-intl
Of achter de proxy:
vagrant@homestead$ proxy on
vagrant@homestead$ sudo -E apt-get update && sudo -E apt-get install -y php5-intl
Ga naar http://dev.nmdad-iii.arteveldehogeschool.be/config.php
Om Git bepaalde bestanden en mappen van de IDE en het besturingssysteem te laten negeren moeten we het bestand .gitignore
aanpassen:
vagrant@homestead$ cd ~/Code/nmdad-iii.arteveldehogeschool.be/www/
vagrant@homestead$ nano .gitignore
Het resultaat moet er zo uitzien:
/web/bundles/
/app/bootstrap.php.cache
/app/cache/*
/app/config/parameters.yml
/app/logs/*
!app/cache/.gitkeep
!app/logs/.gitkeep
/build/
/vendor/
/bin/
/composer.phar
# Optional files to ignore
/.idea
/composer.lock
.DS_Store
Thumbs.db
Download en installeer de allerlaatste versie van PhpStorm.
Tip: Zie Project and IDE Settings om mappen en instellingen van vorige versies te verwijderen.
Welcome to PhpStorm → Create New Project from Existing Files → Source files are in a local directory, no Web server is yet configured.
Rechtsboven klikken op Edit Configurations… → + → PHP Web Application
Nieuwe servers aanmaken:
- Name:
development
- Host:
dev.nmdad-iii.arteveldehogeschool.be
:80
Xdebug
- Host:
- Name:
production
- Host:
www.nmdad-iii.arteveldehogeschool.be
:80
Xdebug
- Host:
Run configuraties aanmaken:
- Name:
dev
- Server:
Development Server
- Start URL:
/
- Server:
- Name:
prod
- Server:
Production Server
- Start URL:
/
- Server:
- PhpStorm → Preferences…: Project Settings
- PHP
- PHP language level:
5.6
- Interpreter: → … → + → Remote…:
- Vagrant
- Vagrant Instance Folder:
~/Homestead
- PHP language level:
- SSH Terminal
- Current Vagrant
- Vagrant
- Vagrant executable:
vagrant
- Instance folder:
~/Homestead
- Vagrant executable:
- PHP
Zie ook:
[Symfony / The Book](http://symfony.com/doc/current/book/index.html)
[Symfony / The Cookbook](http://symfony.com/doc/current/cookbook/index.html)
[Symfony / The Components](http://symfony.com/doc/current/components/index.html)
[Symfony / Reference](http://symfony.com/doc/current/reference/index.html)
[Symfony / Bundles](http://symfony.com/doc/current/bundles/index.html)
[Symfony / Best Practices](http://symfony.com/doc/current/best_practices/index.html)
Zie ook:
Een Symfony-applicatie bestaat uit minstens één Bundle. Een Bundle is een modulair deel van de applicatie die je bouwt. Het is een map met deze elementen:
- Configuratiebestanden;
- PHP-klasses;
- JavaScript-bestanden;
- CSS-bestanden;
- …
De map staat in een map die de naamruimte voorstelt en deze staat in de src/
map.
Mappenstructuur
nmdad-iii.arteveldehogeschool.be/ └── www/ └── src/
Je een applicatie bijvoorbeeld opsplitsen in aparte Bundles voor:
- Front-office
- Back-office
- API
- ...
Wat we gaan doen
We gaan vier Bundles maken:
- CommonBundle: voor de gemeenschappelijke delen van de app, zoals de klasses voor Modellen.
- BackOfficeBundle
- FrontOfficeBundle
- ApiBundle: voor de RESTful service.
We gaan annotations gebruiken om de Routes te bepalen.
vagrant@homestead$ php app/console generate:bundle --namespace=Artevelde/CommonBundle --format=annotation
Met volgende opties:
* Bundle name [ArteveldeCommonBundle]: <return>
* Target directory
[/home/vagrant/Code/nmdad-iii.arteveldehogeschool.be/www/src]: <return>
* Do you want to generate the whole directory structure [no]? <return>
* Do you confirm generation [yes]? <return>
* Confirm automatic update of your Kernel [yes]? <return>
* Confirm automatic update of the Routing [yes]? <return>
vagrant@homestead$ php app/console generate:bundle --namespace=Artevelde/BackOfficeBundle --format=annotation
Met volgende opties:
* Bundle name [ArteveldeBackOfficeBundle]: <return>
* Target directory
[/home/vagrant/Code/nmdad-iii.arteveldehogeschool.be/www/src]: <return>
* Do you want to generate the whole directory structure [no]? <return>
* Do you confirm generation [yes]? <return>
* Confirm automatic update of your Kernel [yes]? <return>
* Confirm automatic update of the Routing [yes]? <return>
vagrant@homestead$ php app/console generate:bundle --namespace=Artevelde/FrontOfficeBundle --format=annotation
Met volgende opties:
* Bundle name [ArteveldeFrontOfficeBundle]: <return>
* Target directory
[/home/vagrant/Code/nmdad-iii.arteveldehogeschool.be/www/src]: <return>
* Do you want to generate the whole directory structure [no]? <return>
* Do you confirm generation [yes]? <return>
* Confirm automatic update of your Kernel [yes]? <return>
* Confirm automatic update of the Routing [yes]? <return>
vagrant@homestead$ php app/console generate:bundle --namespace=Artevelde/ApiBundle --format=annotation
Met volgende opties:
* Bundle name [ArteveldeApiBundle]: <return>
* Target directory
[/home/vagrant/Code/nmdad-iii.arteveldehogeschool.be/www/src]: <return>
* Do you want to generate the whole directory structure [no]? <return>
* Do you confirm generation [yes]? <return>
* Confirm automatic update of your Kernel [yes]? <return>
* Confirm automatic update of the Routing [yes]? <return>
Zie ook:
Voordat je een Bundle verwijdert moeten ook alle referenties naar de Bundle in deze bestanden verwijderd worden:
app/AppKernel.php
app/config/routing.yml
Mappenstructuur
nmdad-iii.arteveldehogeschool.be/ └── www/ └── app/ └── AppKernel.php
Zie ook:
Gegevens op een website zouden verdwijnen van zodra de gebruikersessie op de server eindigt, tenzij het ergens opgeslagen kan worden waar het niet meer kan wijzigen. In de computerwetenschappen noemt men dit Persistentie (Persistence). Om gegevens van een website te persisteren, worden die op een harde schijf (meestal meerdere harde schijven) opgeslagen. Dit kan eenvoudigweg door een bestand weg te schrijven, maar het is in de meeste gevallen makkelijker om een DBMS te gebruiken, een 'database' laten we maar zeggen.
Gegevens worden opgeslagen in modellen die vervolgens in de persistentielaag opgeslagen moeten worden. De interactie met de persistente opslag zoals een RDBMS gebeurt in de meeste gevallen door vele complexe databasequery's te schrijven in een bepaald SQL-dialect en de resultaatarray's naar OOP-objecten om te zetten en vice versa. Uiteraard is dit een moeilijk, traag en vermoeiend proces, daarenboven vergt het overschakelen op een ander DBMS veel wijzigingen in de code. Een DAL (Database Abstraction Layer) kan dit gevoelig vereenvoudigen door SQL (met zijn vele dialecten) schrijven voor zijn rekening te nemen. Bovenop een DAL kan een ORM (Object Relational Mapper) gebouwd worden.
Een Object Relational Mapper kan OOP-objecten mappen op databasetabellen. Het ORM zal dus objecten wegschrijven naar rijen in databasetabellen en de rijen terug uitlezen en omzetten naar objecten.
Symfony ondersteunt verschillende ORM's om de database aan te spreken. De twee ORM's die out-of-the-box ondersteund worden zijn:
We kiezen voor Doctrine ORM omdat deze populairder is, en omdat de maker van Symfony actief meewerkt aan het project. Doctrine ORM is bovenop Doctrine DBAL gebouwd. Dit is een DAL (Database Abstraction Layer). Concreet wil dit zeggen dat het op vlak van code vrijwel geen verschil maakt welk DBMS er gebruikt wordt, behalve dan enkele specifieke configuratie-instellingen. Er moet wel nog altijd rekening gehouden worden met het specifieke SQL-dialect en beschikbare functionaliteiten voor elk DBMS, maar in principe zou het overschakelen van het ene systeem op het andere relatief eenvoudig moeten zijn. Het nadeel van ORM is wel dat je niet alle functionaliteiten van je DBMS zal kunnen gebruiken, omdat het ORM compatibel moet zijn met alle ondersteunde systemen.
Symfony en Doctrine ORM ondersteunen verschillende DBMS'en via een Doctrine DBAL-stuurprogramma, waaronder deze:
Database | Stuurprogramma | Type | Beschikbaar op Laravel Homestead? |
---|---|---|---|
IBM DB2 | ibm_db2 |
Native | Zelf te installeren |
IBM DB2 | pdo_ibm |
PDO | Zelf te installeren |
Microsoft SQL Server | pdo_sqlsrv |
PDO | Nee |
MySQL | pdo_mysql |
PDO | Ja |
Oracle | oci8 |
Native | Zelf te installeren |
Oracle | pdo_oci |
PDO | Zelf te installeren |
ProgreSQL | pdo_pgsql |
PDO | Ja |
SQLite | pdo_sqlite |
PDO | Ja |
Er zijn drie mogelijke pistes om met ORM te beginnen:
- Code First
- Model First
- Database First
Code First Eerst de code schrijven en pas daarna de database opzetten via het ORM. Dit heeft als voordeel dat je code compatibel is met zoveel mogelijk DBMS'en, zodat makkelijk naar een ander systeem gemigreerd kan worden.
Model First Beginnen met het modelleren van de gegevens met een tool (bijv. MySQL Workbench).
Database First Beginnen met het aanmaken van de database, of een reeds bestaande database gebruiken. Vertrekken vanaf een bestaande database laat toe dat functionaliteiten gebruikt die speciek zijn voor je DBMS. Je kan dus de maximale efficiëntie halen uit het systeem door zo weinig mogelijk compromissen te sluiten. Het nadeel is dan wel dat je project sterk verankerd is met het gebruikte DBMS.
Symfony maakt gebruik van het Model-View-Controller (MVC) architectural pattern. In Doctrine ORM noemt men het Model-gedeelte een Entity, omdat ze een entiteit in de databasetabel voorstellen.
Een Entity kan optioneel ook een Entity Respository-klasse hebben. Deze klasse zal zorgen voor de link tussen Entity en Database Tabel. Deze klasse is enkel nodig als je zelf het lezen en wegschrijven naar de databasetabel wil regelen.
Wat we gaan doen
We gaan een miniblog maken met:
- Een Post (Article of Image)
- Een Post behoort tot een Category, dit kunnen er zelfs verscheidene zijn
- Een Category kan tot een andere Category behoren.
Zie ook:
Doctrine ondersteunt de volgende datatypes die:
array
,simple_array
,json_array
object
boolean
,integer
,smallint
,bigint
string
,text
datetime
,datetimetz
(tz
: tijdzone, niet compatibel met MySQL!),date
,time
decimal
float
blob
guid
OPGELET: Er zijn een aantal DBMS-vendorspecifieke issues.
Zie ook:
Zo wordt
datetimetz
niet ondersteund door MySQL, en wordt die doordatetime
vervangen. Dit leidt tot problemen met de synchronisatie van het Schema.
We maken een entity-klasses met deze gegevens:
Entity | Field name | Field type | Opmerking |
---|---|---|---|
User | name: users |
||
first_name |
string |
length: 255 |
|
last_name |
string |
length: 255 |
|
birthday |
date |
nullable | |
created_at |
datetime |
||
enabled_at |
datetime |
nullable | |
locked_at |
datetime |
nullable | |
expired_at |
datetime |
nullable | |
expired_credentials_at |
datetime |
nullable | |
updated_at |
datetime |
nullable | |
deleted_at |
datetime |
nullable | |
username |
string |
length: 255 , unique |
|
password |
string |
length: 64 , fixed |
|
PostAbstract | name: posts |
||
title |
string |
length: 255 |
|
created_at |
datetime |
||
updated_at |
datetime |
nullable | |
deleted_at |
datetime |
nullable | |
published_at |
datetime |
nullable | |
Article | name: articles |
||
body |
text |
||
Image | name: images |
||
uri |
string |
length: 255 |
|
Category | name: categories |
||
name |
string |
length: 255 |
|
created_at |
datetime |
||
updated_at |
datetime |
nullable | |
deleted_at |
datetime |
nullable |
Via de Symfony Console kunnen we Doctrine ORM een Entity-klasse laten generen in src/Artevelde/CommonBundle/Entity
.
vagrant@homestead$ cd ~/Code/nmdad-iii.arteveldehogeschool.be/www/
vagrant@homestead$ php app/console doctrine:generate:entity --entity=ArteveldeCommonBundle:User
Met volgende opties:
* The Entity shortcut name [ArteveldeCommonBundle:User]: <return>
* Configuration format (yml, xml, php, or annotation) [annotation]: <return>
* New field name (press <return> to stop adding fields): first_name
* Field type [string]: <return>
* Field length [255]: <return>
* New field name (press <return> to stop adding fields): last_name
* Field type [string]: <return>
* Field length [255]: <return>
* New field name (press <return> to stop adding fields): created_at
* Field type [string]: datetime
* …
* New field name (press <return> to stop adding fields): username
* Field type [string]: <return>
* Field length [255]: <return>
* New field name (press <return> to stop adding fields): password
* Field type [string]: <return>
* Field length [255]: 64
* New field name (press <return> to stop adding fields): <return>
* Do you want to generate an empty repository class [no]? y
* Do you confirm generation [yes]? <return>
Daarna maken we nog de overige Entity-klasses aan:
vagrant@homestead$ php app/console doctrine:generate:entity --entity=ArteveldeCommonBundle:Article
vagrant@homestead$ php app/console doctrine:generate:entity --entity=ArteveldeCommonBundle:Image
vagrant@homestead$ php app/console doctrine:generate:entity --entity=ArteveldeCommonBundle:Category
Post noemen we PostAbstract omdat Post enkel zal gebruikt worden om van over te erven en niet als instantie. We hebben ook geen empty repository class nodig.
vagrant@homestead$ php app/console doctrine:generate:entity --entity=ArteveldeCommonBundle:PostAbstract
Mappenstructuur en bestanden
nmdad-iii.arteveldehogeschool.be/ └── www/ └── src/ └── Artevelde/ └── CommonBundle/ └── Entity/ ├── Article.php ├── ArticleRepository.php ├── Category.php ├── CategoryRepository.php ├── Image.php ├── ImageRepository.php ├── PostAbstract.php ├── User.php └── UserRepository.php
Zie ook:
We moeten elke Entity-klasse aanpassen zodat we een eigen naam aan de tabel kunnen geven, zoniet wordt de naam van de Entity gebruikt. Dit doen we door name=""
toe te voegen aan @ORM\Table()
met de naam van de tabel. We gebruiken telkens de meervoudsvorm, in kleine letters geschreven:
@ORM\Table(name="articles")
@ORM\Table(name="categories")
@ORM\Table(name="images")
@ORM\Table(name="posts")
@ORM\Table(name="users")
Zie ook:
Kolomeigenschappen | in @ORM\Column() |
---|---|
CHAR(64) |
type="string", length=64, options={"fixed":true} |
NULL |
nullable=true |
UNIQUE |
unique=true |
INT UNSIGNED |
type="integer", options={"unsigned":true} |
Pas ook alle $id
's aan zodat ze UNSIGNED
zijn.
Hoewel je een optie "default":
bestaat, kan je die niet gebruiken voor de huidige tijd. Je kan de kolomdefinitie manueel aanpassen met columnDefinition
(met alle problemen van dien qua synchronisatie van het schema), maar het is makkelijker om de huidige tijd voor created_at
toevoegen via de Constructor van de Entity-klasse:
public function __construct()
{
$this->createdAt = new \DateTime();
}
Zie ook:
-
De Entity's
Article
enImage
erven over vanPostAbstract
:PostAbstract.php
- Vervang
class PostAbstract
doorabstract class PostAbstract
zodat nooit een instantie van PostAbstract gemaakt kan worden. - Vervang
private $id
doorprotected $id
- Vervang
Article.php
- Vervang
class Article
doorclass Article extends PostAbstract
- Vervang
private $id
doorprotected $id
- Vervang
Image.php
- Vervang
class Image
doorclass Image extends PostAbstract
- Vervang
private $id
doorprotected $id
- Vervang
-
We moeten overal
private $id
vervangen doorprotected $id
/**
* PostAbstract
*
* @ORM\Table(name="posts")
* @ORM\Entity
* @ORM\InheritanceType("JOINED")
* @ORM\DiscriminatorColumn(name="discriminator", type="string", length=1)
* @ORM\DiscriminatorMap({
* PostAbstract::POST_ARTICLE="Article",
* PostAbstract::POST_IMAGE ="Image"
* })
*
*/
abstract class PostAbstract
{
const POST_ARTICLE = 'A';
const POST_IMAGE = 'I';
// …
PhpStorm Tip: Met
<alt>
+<klik>
kan je meerdere cursors gebruiken, wat handig is om meerdere lijnen tegelijk aan te passen. Met<esc>
maak je de meerdere cursors ongedaan.
Zie ook:
Zie ook:
Er zit al een stuk van gebruikersfunctionaliteit in Symfony waarvan je de interfaces kan implementeren:
- UserInterface moet deze methodes implementeren:
getUsername()
getSalt()
getPassword()
getRoles()
eraseCredentials()
- AdvancedUserInterface moet bovendien ook nog deze methodes implementeren:
isAccountNonExpired()
isAccountNonLocked()
isCredentialsNonExpired()
isEnabled()
In app/config/security.yml
:
security:
encoders:
# Symfony\Component\Security\Core\User\User: plaintext
Artevelde\CommonBundle\Entity\User:
algorithm: bcrypt
cost: 15
# …
providers:
# in_memory:
# memory:
# users:
# user: { password: userpass, roles: [ 'ROLE_USER' ] }
# admin: { password: adminpass, roles: [ 'ROLE_ADMIN' ] }
default:
entity: { class: ArteveldeCommonBundle:User, property: username }
Tip: Controleer de geldigheid van het YAML-bestand met de Console-opdracht opdracht
yaml:lint
:vagrant@homestead$ php app/console yaml:lint app/config/security.yml
In het DBMS moet eerst een database aangemaakt worden. Doctrine maakt een onderscheid tussen een Database en een Schema. In principe is een Schema een naamruimte binnen een Database. Het is eigenlijk een verzameling databaseobjecten (bijv. tabellen).
Opmerking: De interpretatie van wat de termen 'Database' en 'Schema' precies zijn, verschilt heel erg van databasemanagementsysteem tot databasemanagementsysteem. Zo maakt MySQL geen onderscheid tussen beide (de SQL-keywords
DATABASE
enSCHEMA
synoniemen voor MySQL), maar doet PostgreSQL dat dan weer wel.
Doctrine kan de database aanmaken (en verwijderen), op voorwaarde dat:
- de databaseconfiguratie in
app/config/parameters.yml
correct is; - de databasegebruiker de nodige rechten heeft op de database;
- de database nog niet bestaat.
De database aanmaken doe je met de Console-opdracht doctrine:database:create
:
vagrant@homestead$ cd ~/Code/nmdad-iii.arteveldehogeschool.be/www/
vagrant@homestead$ php app/console doctrine:database:create
Om de database te verwijderen gebruik je de Console-opdracht doctrine:database:drop
met de optie --force
(omdat verwijderen een risicovolle opdracht is):
vagrant@homestead$ php app/console doctrine:database:drop --force
Het schema (met tabellen op basis van de Modellen) maken doe je met de Console-opdracht doctrine:schema:create
:
vagrant@homestead$ php app/console doctrine:schema:create
Tip: Gebruik de optie
--dump-sql
om de SQL-statements te zien, zonder ze uit te voeren.
Na elke wijziging aan de Entity's moet het schema bijgewerkt worden. Het schema bijwerken doe je met de Console-opdracht doctrine:schema:update
met de optie --force
(omdat bijwerken een risicovolle opdracht is):
vagrant@homestead$ php app/console doctrine:schema:update --force
Tip: Gebruik de optie
--dump-sql
(in plaats van--force
) om de SQL-statements te zien, zonder ze uit te voeren.
Om het schema te verwijderen gebruik je de Console-opdracht doctrine:schema:drop
met de optie --force
(omdat verwijderen een risicovolle opdracht is):
vagrant@homestead$ php app/console doctrine:schema:drop --force
Om te controleren of er geen fouten in de Entity's staan en of alles up to date is, kan je het schema laten valideren met de Console-opdracht doctrine:schema:validate
:
vagrant@homestead$ php app/console doctrine:schema:validate
Tip: Je kan met MySQL Workbench via Reverse Engineer een EER-diagram laten genereren van je schema.
Zie ook:
Data Fixtures worden gebruikt om testgegevens te seeden (in de database inladen).
Eerst moeten we zorgen dat we de allerlaatste versie van Composer hebben:
vagrant@homestead$ sudo composer self-update
Daarna updaten we ook ons Symfony-project:
vagrant@homestead$ composer update
Dan installeren we de we de Bundle met Composer:
vagrant@homestead$ cd ~/Code/nmdad-iii.arteveldehogeschool.be/www/
vagrant@homestead$ composer require doctrine/doctrine-fixtures-bundle:"2.2.*"
Dan moeten we de bundle nog registreren in door deze onder DoctrineBundle()
toe te voegen aan de array $bundles
in app/AppKernel.php
:
public function registerBundles()
{
$bundles = array(
// …
new Doctrine\Bundle\DoctrineBundle\DoctrineBundle(),
new Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle(),
// …
);
}
Vraag alle Console-opdrachten voor doctrine:fixtures
op met list
:
vagrant@homestead$ php app/console list doctrine:fixtures
Zie ook:
We maken de mappen aan met mkdir
en de optie -p
of (--parents
) om alle niet-bestaande mappen in het pad ook aan te maken:
vagrant@homestead$ cd ~/Code/nmdad-iii.arteveldehogeschool.be/www/
vagrant@homestead$ mkdir -p src/Artevelde/CommonBundle/DataFixtures/ORM/
We maken 3 fixtures aan, voor de Entity's User
, Article
en Image
.
Opmerking: De fixtures worden uitgevoerd volgens de alfabetische volgorde van de bestandsnaam. Om zelf de volgorde te kiezen kan je een prefix toevoegen aan de bestandsnaam, zoals bijvoorbeeld:
001_LoadArticleData.php
Met touch
kunnen we alvast de lege bestanden aanmaken:
vagrant@homestead$ cd src/Artevelde/CommonBundle/DataFixtures/ORM/
vagrant@homestead$ touch LoadArticleData.php
vagrant@homestead$ touch LoadImageData.php
vagrant@homestead$ touch LoadUserData.php
Mappenstructuur en bestanden
nmdad-iii.arteveldehogeschool.be/ └── www/ └── src/ └── Artevelde/ └── CommonBundle/ └── DataFixtures/ └── ORM/ ├── LoadArticleData.php ├── LoadImageData.php └── LoadUserData.php
In de Data Fixture-klasse implementeren we de methode om de volgorde van uitvoeren te bepalen en implementeren we ook de methode om de gegevens in te laden.
Tip: Standaard wordt de volgorde van uitvoeren bepaald door de alfabetische volgorde van de bestandsnaam (niet van de fixture-klassenaam). Je kan de volgorde ook laten bepalen door twee interfaces. Maar OPGELET, je moet dan één van beide kiezen, want ze kunnen niet door elkaar gebruikt worden!
Als de interface
OrderedFixtureInterface
geïmplementeerd wordt, dan bepaalt de methodegetOrder()
de volgorde van uitvoeren door een integer terug te geven. De alfabetische volgorde van de bestandsnaam wordt dan genegeerd. Met deze manier kan je heel precies de volgorde van uitvoeren bepalen, maar kan veel werk vergen en complex zijn als er veel fixtures zijn.Als de interface
DependentFixtureInterface
geïmplementeerd wordt, dan bepaalt de methodegetDependencies()
de volgorde van uitvoeren door een array met volledig gekwalificeerde fixture-klassenamen terug te geven. Hiervoor kan je ook__NAMESPACE__
gebruiken. Met deze manier heb je iets minder controle over de volgorde van uitvoeren, maar ze is wel flexibeler dan de eerste manier.
We gebruiken we $this->addReference()
(overgeërfd van AbstractFixture
) om een referentie naar de User-objecten te bewaren voor gebruik in een volgende Data Fixture.
In het bestand LoadUserData.php
:
<?php
namespace Artevelde\CommonBundle\DataFixtures\ORM;
use Artevelde\CommonBundle\Entity\User;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
class LoadUserData extends AbstractFixture implements OrderedFixtureInterface
{
/**
* {@inheritdoc}
*/
public function getOrder()
{
return 1; // The order in which fixture(s) will be loaded.
}
/**
* {@inheritdoc}
*/
public function load(ObjectManager $em)
{
$userA = new User();
$em->persist($userA); // Manage Entity for persistence.
$userA
->setFirstName('Test User')
->setLastName('A')
->setUsername('testuserA')
->setPassword('testuserA');
$this->addReference('TestUserA', $userA); // Reference for the next Data Fixture(s).
$userB = new User();
$em->persist($userB); // Manage Entity for persistence.
$userB
->setFirstName('Test User')
->setLastName('B')
->setUsername('testuserB')
->setPassword('testuserB');
$this->addReference('TestUserB', $userB); // Reference for the next Data Fixture(s).
$em->flush(); // Persist all managed Entities.
}
}
In het bestand LoadArticleData.php
:
<?php
namespace Artevelde\CommonBundle\DataFixtures\ORM;
use Artevelde\CommonBundle\Entity\Article;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
class LoadArticleData extends AbstractFixture implements OrderedFixtureInterface
{
/**
* {@inheritdoc}
*/
public function getOrder()
{
return 2; // The order in which fixture(s) will be loaded.
}
/**
* {@inheritdoc}
*/
public function load(ObjectManager $em)
{
$articleA = new Article();
$em->persist($articleA); // Manage Entity for persistence.
$articleA
->setTitle('Test Artikel A')
->setBody('Lorem Ipsum A')
->setUser($this->getReference('TestUserB')); // Get reference from a previous Data Fixture.
$articleB = new Article();
$em->persist($articleB); // Manage Entity for persistence.
$articleB
->setTitle('Test Artikel B')
->setBody('Lorem Ipsum B')
->setUser($this->getReference('TestUserA')); // Get reference from a previous Data Fixture.
$em->flush(); // Persist all managed Entities.
}
}
Om wachtwoorden te hashen hebben we de service security.encoder_factory
nodig die we via de Service Container moeten aanspreken. In tegenstelling tot een Controller heeft een Data Fixture niet zomaar toegang tot de Service Container.
Tip: Je kan alle services zien met deze Console-opdracht
container:debug
.vagrant@homestead$ php app/console container:debug | grep 'security'
We kunnen de Service Container aanspreken als we eerst ContainerAwareInterface
implementeren.
// …
class LoadUserData extends AbstractFixture implements OrderedFixtureInterface, ContainerAwareInterface
{
private $container;
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
public function hashPassword($user)
{
$factory = $this->container->get('security.encoder_factory');
$encoder = $factory->getEncoder($user);
$hashedPassword = $encoder->encodePassword($user->getPassword(), $user->getSalt());
$user->setPassword($hashedPassword);
}
// …
}
Met de Console-opdracht doctrine:fixtures:load
kan je de gegevens inladen:
vagrant@homestead$ cd ~/Code/…-iii.arteveldehogeschool.be/www/
vagrant@homestead$ php app/console doctrine:fixtures:load
Met deze optie:
* Careful, database will be purged. Do you want to continue Y/N y
OPGELET: Voor het inladen wordt de database geleegd ('purged'). Indien je dit niet wenst moet je de
--append
(toevoegen) optie toevoegen:vagrant@homestead$ php app/console doctrine:fixtures:load --append
Je kan de opdracht ook zonder interactie laten uitvoeren (zal de gegevens wissen):
vagrant@homestead$ php app/console doctrine:fixtures:load --no-interaction
Zie ook:
Migrations zijn een soort van versiebeheer op het niveau van databaseschema's.
Eerst moeten we zorgen dat we de allerlaatste versie van Composer hebben:
vagrant@homestead$ sudo composer self-update
Daarna updaten we ook ons Symfony-project:
vagrant@homestead$ composer update
Dan installeren we de we de Bundle met Composer:
vagrant@homestead$ cd ~/Code/nmdad-iii.arteveldehogeschool.be/www/
vagrant@homestead$ composer require doctrine/migrations:"1.0.*@dev" doctrine/doctrine-migrations-bundle:"2.1.*@dev"
Dan moeten we de bundle nog registreren in door deze onder DoctrineBundle()
en DoctrineFixturesBundle()
toe te voegen aan de array $bundles
in app/AppKernel.php
:
public function registerBundles()
{
$bundles = array(
// …
new Doctrine\Bundle\DoctrineBundle\DoctrineBundle(),
new Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle(),
new Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle(),
// …
);
}
Vraag alle Console-opdrachten voor doctrine:migrations
op met list
:
vagrant@homestead$ php app/console list doctrine:migrations
Migrations vervangen de Console-opdrachten van doctrine:schema
grotendeels.
De huidige status van de migrations opvragen doe je met:
vagrant@homestead$ php app/console doctrine:migrations:status
Je kan wijzigingen in het schema bewaren met:
vagrant@homestead$ php app/console doctrine:migrations:diff
Dit maakt een versiebestand aan in de map app/DoctrineMigrations/
waarin de schemawijzigen worden bijgehouden, vergelijkbaar met doctrine:schema:update --dump-sql
. Daarnaast bevat het ook instructies om de wijzigingen weer ongedaan te maken.
Opmerking: Als deze Console-opdracht voor de eerste keer uitgevoerd wordt, dan wordt er ook een lege tabel
migrations_version
gemaakt, waarin de uitgevoerde Migrations bijgehouden worden.
Het bestand heeft de vorm VersionJJJJMMDDuummss.php
, waarbij JJJJMMDDuummss
het versienummer voorstelt in de vorm van datum en tijd.
De laatste Migration uitvoeren ('migreren') doe je met deze Console-opdracht:
vagrant@homestead$ php app/console doctrine:migrations:migrate
Je kan naar een bepaalde versie migreren door het versienummer (JJJJMMDDuummss
) achter de Console-opdracht doctrine:migrations:migrate
te zetten:
vagrant@homestead$ php app/console doctrine:migrations:migrate JJJJMMDDuummss
Je kan altijd teruggaan naar de oorspronkelijke toestand (versie 0
) van voor de migratie met:
vagrant@homestead$ php app/console doctrine:migrations:migrate 0
Zie ook:
Om de Migrations uit te breiden met Data Fixtures moeten we de Console kunnen aanspreken vanuit de Migrations. Om de Symfony Console aan te spreken moeten we eerst toegang hebben tot de Service Container, daarom moeten we de ContainerAwareInterface
implementeren, zodat de Service Container via de methode setContainer()
in de Migration geïnjecteerd wordt. Deze injectie gebeurt door middel van een speciaal Design Pattern dat Dependency Injection heet.
In een nieuwe methode loadFixtures()
kunnen we aan de Symfony HttpKernel (de kern van Symfony) via de Service Container, en deze HttpKernel hebben we nodig om een nieuwe Console Application instantie te maken waarmee we de Symfony Console kunnen aanspreken.
Eens de up()
methode gedaan heeft, kan de loadFixtures()
methode aangeroepen worden vanuit de postUp()
methode.
app/DoctrineMigrations/VersionJJJJMMDDuummss.php
:
// …
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
// …
class VersionJJJJMMDDuummss extends AbstractMigration implements ContainerAwareInterface
{
private $container;
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
public function loadFixtures($fixturesDirectory, $append = true)
{
$input = new ArrayInput([
'command' => 'doctrine:fixtures:load',
'--fixtures' => $fixturesDirectory,
'--append' => (boolean) $append,
]);
$kernel = $this->container->get('kernel');
$application = new Application($kernel);
$application->setAutoExit(false);
$application->run($input);
}
public function postUp(Schema $schema)
{
$this->loadFixtures('src/Artevelde/CommonBundle/DataFixtures/ORM');
}
// …
Zie ook:
Er zijn verschillende soorten testen. Hieronder zijn er een aantal courante testen beschreven van gedetailleerd naar globaal.
De Unit Test test bouwstenen (units of code) van een applicatie correct werken volgens de specificaties. Een unit is doorgaans een volledige klasse, maar kan bijvoorbeeld ook een functie of methode zijn.
De Integration Test test of de bouwstenen correct samen werken volgens de specificaties..
De Functional Test test een functionaliteit van een applicatie. Een Functional Test zegt weinig over de interne werking van de code en is dus een aanvulling op Unit Testing en Integration Testing.
De Acceptance Test test of de applicatie werkt volgens de specificaties van de opdrachtgever. Na het succesvol doorlopen van de Acceptance Testing, accepteert de opdrachtgever het eindresultaat op als zijnde voldoende voor oplevering.
Hiervoor worden Acceptance Criteria gebruikt. Deze kunnen zowel functioneel (functionaliteiten) als niet-functioneel zijn (bijv. snelheid, gebruiksvriendelijkheid, etc.).
Omdat testen vaak niet of te weinig uitgevoerd worden (omwille van bijvoorbeeld hybris van de ontwikkelaar, te kleine budgetten, tijdsgebrek, etc.); heeft men een aantal softwareontwikkelingsmethodologieën ontwikkeld waarbij men begint met het schrijven van testen voordat met aan de eigenlijke softwareontwikkeling begint.
Deze methodologieën zijn allen nauw verbonden met Agile Software Development.
TDD wordt toegeschreven aan de Amerikaan Kent Beck, die ook XP (Extreme Programming) bedacht, een vorm van Agile Software Development. TDD vorm een onderdeel van XP.
Bij TDD begint men met het schrijven van een test en voert men alle testen uit om na te gaan of alle testen nog werken, vooraleer men de eigenlijke code schrijft.
De Brit Dan North breidde TDD verder uit tot BDD, een methodologie die niet enkel voor programmeurs bedoeld was, maar ook voor testers, analisten, projectmanagers, domeinexperts, etc. BDD heeft als extra troef dat de documenten ook door niet-programmeurs gelezen en geschreven kunnen worden.
Een deel van de mosterd werd bij DDD (Domain-Driven Development) gehaald. Bij DDD speelt het domein (een kennisgebied met gemeenschappelijke vereisten, terminologie en functionaliteiten) een centrale rol. Bij DDD betrekt men mensen met veel domeinexpertise maar zonder programmeerervaring heel nauw bij het softwareontwikkelingsproces.
We kunnen de Entity-klasses testen met PHPUnit. Dit is een unit testing framework dat ontwikkeld werd door de Duitser Sebastian Bergmann
Om PHPUnit in de Command-Line Interface te installeren:
vagrant@homestead$ omposer global require "phpunit/phpunit=4.3.*"
Test of PHPUnit correct werkt door de versie op te vragen:
vagrant@homestead$ phpunit --version
vagrant@homestead$ phpunit --self-update
Om alle testen in het Symfony-project uit te voeren:
vagrant@homestead$ cd ~/Code/nmdad-iii.arteveldehogeschool.be/www/
vagrant@homestead$ phpunit --configuration app/
Of korter:
vagrant@homestead$ phpunit -c app/
Mappenstructuur en bestanden
nmdad-iii.arteveldehogeschool.be/ └── www/ └── src/ └── Artevelde/ └── CommonBundle/ └── Tests/ └── Entity/ └── PostAbstractTest.php
// …
class PostAbstractTest extends WebTestCase
{
private $doctrine;
/**
* {@inheritDoc}
*/
public function setUp()
{
static::$kernel = static::createKernel();
static::$kernel->boot();
$this->doctrine = static::$kernel->getContainer()->get('doctrine'); // Doctrine service uit de container opvragen
$em = $this->doctrine->getManager();
$em->getConnection()->beginTransaction(); // Geen auto-commit gebruiken.
}
/**
* Test for the Entity Article.
*/
public function testArticleEntity()
{
$article = new Article();
$article
->setTitle('Testartikel')
->setBody('Lorem ipsum');
$em = $this->doctrine->getManager(); // Entity Manager
$em->persist($memberB); // Object managen voor persistentie.
$em->flush(); // Gemanagede objecten wegschrijven naar de persistentielaag.
}
/**
* {@inheritDoc}
*/
protected function tearDown()
{
parent::tearDown();
$em = $this->doctrine->getManager();
$em->getConnection()->rollback(); // Alle wijzigingen ongedaan maken, maar auto_increment waarde zal toch verhoogd blijven.
$em->close();
}
}
Zie ook:
BDD is TDD met een aantal Best Practices:
- Vertrek van de doelen van de business of organisatie.
- Gebruik voorbeelden om de requirements te verduidelijken.
- Ontwikkel een universele taal voor zowel de domeinexperten als de ontwikkelaars.
BDD formaliseert deze TDD-Best Practices en zorgt ervoor dat alle belanghebbenden (stakeholders) begrijpen wat er gebouwd wordt, doordat er begrijpbare documentatie ontstaat.
Zie ook:
Behat is een BDD-framework voor PHP geïnspireerd op het in Ruby geschreven Cucumber, gemaakt door Konstantin Kudryashov.
In Cucumber worden de testen (Behaviour Tests) geschreven in de taal Gherkin. Behat neemt deze taal over en sinds Behat 3 is de Behat-implementatie van Gherkin zelfs de officiële PHP-implementatie van Gherkin.
Gherkin is vertaald in ca. 40 verschillende gesproken talen.
Mink is een Web Acceptance Test Suite bovenop Behat. Concreet wil dit zeggen dat Mink gebruikt wordt om geautomatiseerde Acceptance Tests uit te voeren door een browser te emuleren, of door echte browser aan te sturen. Hiervoor heb je keuze uit een aantal stuurprogramma's (drivers) die je nog apart moet installeren.
Eerst moeten we zorgen dat we de allerlaatste versie van Composer hebben:
vagrant@homestead$ sudo composer self-update
Daarna updaten we ook ons Symfony-project:
vagrant@homestead$ cd ~/Code/nmdad-iii.arteveldehogeschool.be/www/
vagrant@homestead$ composer update
Symfony2Extension zorgt voor de integratie van Behat in Symfony.
vagrant@homestead$ composer require behat/behat:"~3.0" behat/symfony2-extension:"~2.0"
Je kan testen of Behat correct geïnstalleerd is door het versienummer op te vragen:
vagrant@homestead$ bin/behat --version
Help:
vagrant@homestead$ bin/behat --help
MinkExtension zorgt voor de link tussen Behat en Mink. Daarnaast hebben we ook minstens één browserstuurprogramma nodig. Hier gebruiken we Mink BrowserKit Driver
vagrant@homestead$ composer require behat/mink:"~1.6" behat/mink-extension:"~2.0" behat/mink-browserkit-driver:"~1.2"
In de rootmap van ons project maken we een bestand behat.yml
aan om de Behat Symfony2Extension, MinkExtension in te stellen.
default:
translation:
locale: en
fallback_locale: en
extensions:
Behat\Symfony2Extension: ~
Behat\MinkExtension:
base_url: 'http://dev.nmdad-iii.arteveldehogeschool.be'
sessions:
default:
symfony2: ~
Tip: Controleer de geldigheid van het YAML-bestand met de Console-opdracht opdracht
yaml:lint
:vagrant@homestead$ php app/console yaml:lint behat.yml
Naslagwerk over het configuratiebestand:
vagrant@homestead$ bin/behat --config-reference
Onder suites:
staan de Behat Test Suites, een manier om testen te groeperen. Voor Symfony is het handig om Test Suites te koppelen aan Symfony Bundles, wat zeer eenvoudig is dankzij de Behat Symfony2Extension. In dit voorbeeld is de Test Suite acme_demo_features
geconfigureerd voor de Bundle AcmeDemoBundle
.
Mappenstructuur en bestanden
nmdad-iii.arteveldehogeschool.be/ └── www/ └── behat.yml
default:
translation:
locale: en
fallback_locale: en
extensions:
Behat\Symfony2Extension: ~
Behat\MinkExtension:
base_url: 'http://dev.nmdad-iii.arteveldehogeschool.be'
sessions:
default:
symfony2: ~
suites:
acme_demo_features:
type: symfony_bundle
bundle: AcmeDemoBundle
Bij het initialiseren van Behat zal een map Features
in de Bundles aangemaakt worden.
vagrant@homestead$ bin/behat --init
In dit voorbeeld werd scr/Acme/DemoBundle/Features/
aangemaakt
Zie ook:
We gaan een Functionaliteit schrijven in de taal Gherkin. Gherkin bestaat voor verschillende gesproken talen, maar we houden het op Engels omdat IDE's voorlopig enkel de Engelstalige sleutelwoorden herkent. Maar, het is dus mogelijk om functionaliteiten in het Nederlands te schrijven, indien nodig.
Een Functionaliteit (Feature) heeft (optioneel) een Achtergrond (Background) en één of meerdere Scenario's (Scenario). Naast een gewoon Scenario bestaat er ook een Abstract Scenario (Scenario Outline of Scenario Template) dat eigenlijk een scenario is met parameters. De argumenten voor die parameters staan in een soort tabel: Voorbeelden (Examples of Scenarios).
Elk Scenario heeft een aantal stappen die beginnen met één van deze sleutelwoorden:
Nederlands | Nederlands Alternatief | English |
---|---|---|
Gegeven |
Stel |
Given |
En |
And |
|
Als |
When |
|
Dan |
Then |
|
Maar |
But |
Eens de Funtionaliteit geschreven is, moeten we voor elke stap in de Scenario's een Step Definition schrijven. Dit is een methode in een PHP-klasse.
Opmerking: Er bestaan BDD-frameworks voor verschillende programmeertalen zoals o.m. Cucumber voor Ruby en SpecFlow voor .NET, die allen met dezelfde, in Gherkin geschreven Funtionaliteit-bestanden werken.
We gaan eerst naar de rootmap van ons Symfony-project:
vagrant@homestead$ cd ~/Code/nmdad-iii.arteveldehogeschool.be/www/
De syntaxis van een Functionaliteit
vagrant@homestead$ bin/behat --story-syntax
Om de syntaxis te zien in een specifieke taal:
vagrant@homestead$ bin/behat --story-syntax --lang=de
vagrant@homestead$ bin/behat --story-syntax --lang=en
vagrant@homestead$ bin/behat --story-syntax --lang=fr
vagrant@homestead$ bin/behat --story-syntax --lang=nl
We maken een nieuwe Functionaliteit: Login om de Behavior Tests voor http://dev.nmdad-iii.arteveldehogeschool.be/demo/secured/login te schrijven.
Met het commando touch
kunnen we een leeg bestand maken.
vagrant@homestead$ cd scr/Acme/DemoBundle/Features/
vagrant@homestead$ touch login.feature
Open daarna de bestand login.feature
met een teksteditor.
Mappenstructuur en bestanden
nmdad-iii.arteveldehogeschool.be/ └── www/ └── src/ └── Acme/ └── DemoBundle/ └── Features/ └── login.feature
# language: en
Feature: Login Form
In order to access secured content of the website
As a user or an admin
I need to log in
Background:
Given the users are:
|username|password |role |
|user |userpass |ROLE_USER |
|admin |adminpass|ROLE_ADMIN|
@mink:default
Scenario Outline: log in with valid credentials
Given I am on the homepage
When I follow "Run The Demo"
Then I should be on "/demo/"
When I follow "Access the secured area"
Then I should be on "/demo/secured/login"
When I fill in "username" with "<username>"
And I fill in "password" with "<password>"
And I press "Login"
Then I should be on "/demo/secured/hello/World"
And I should see "<text>"
# De reeds voorgedefineerde stappen in de context (MinkContext) vind je met:
# vagrant@homestead$ bin/behat -di
# vagrant@homestead$ bin/behat -dl
Examples:
|username|password |text |
|user |userpass |logged in as user |
|admin |adminpass|logged in as admin|
Opmerking: Met
@mink:
kan je instellen welke sessie (cfr. Mink Driver) gebruikt moet worden.default
is de Mink BrowserKit Driver.
Initialiseer opnieuw
vagrant@homestead$ cd ~/Code/nmdad-iii.arteveldehogeschool.be/www/
vagrant@homestead$ bin/behat --init
Behat uitvoeren
vagrant@homestead$ bin/behat
Je kan Behat ook voor een specifieke Test Suite uitvoeren:
vagrant@homestead$ bin/behat --suite=acme_demo_features
of voor een specifieke Symfony Bundle:
vagrant@homestead$ bin/behat @AcmeDemoBundle
En zelf een specifieke feature:
vagrant@homestead$ bin/behat @AcmeDemoBundle/login.feature
Nu moeten we nog Actions maken voor elke stap. Dit zijn methodes in de Context-klasse features/bootstrap/Acme/DemoBundle/Features/Context/FeatureContext.php
Mappenstructuur en bestanden
nmdad-iii.arteveldehogeschool.be/ └── www/ └── features/ └── bootstrap/ └── Acme/ └── DemoBundle/ └── Features/ └── Context/ └── FeatureContext.php
Als je Behat uitvoert zal die snippets tonen voor elke ontbrekende stap in de Context-klasse.
vagrant@homestead$ bin/behat
Je kan de snippets aan de klasse laten toevoegen net:
vagrant@homestead$ bin/behat --dry-run --append-snippets
--dry-run
zorgt ervoor dat enkel de Feature Suite getoond wordt, zonder de onderliggende code
De methodes maken (voorlopig) gebruik van de klasse PendingException
, die we dus moeten toevoegen:
use Behat\Behat\Tester\Exception\PendingException;
De klasse FeatureContext
laten we MinkContext
extenden:
namespace Acme\DemoBundle\Features\Context;
use Behat\Behat\Context\SnippetAcceptingContext;
use Behat\Gherkin\Node\TableNode;
use Behat\MinkExtension\Context\MinkContext;
/**
* Behat context class.
*/
class FeatureContext extends MinkContext implements SnippetAcceptingContext
{
/**
* Initializes context.
*
* Every scenario gets its own context object.
* You can also pass arbitrary arguments to the context constructor through behat.yml.
*/
public function __construct()
{
}
/**
* @Given the users are:
*/
public function theUsersAre(TableNode $table)
{
// Do nothing.
}
}
MinkContext
geeft toegang tot heel wat voorgedefinieerde stappen. Klik in PhpStorm rechts op MinkExtension
en dan Go To → Declaration om de klasse te openen en de stappen te zien.
Je kan de Context klasses ook toevoegen in behat.yml
en hoef/mag de klasse FeatureContext
deze niet meer extenden.
Zie ook:
vagrant@homestead$ php app/console generate:controller --controller=ArteveldeFrontOfficeBundle:Articles
Met volgende opties:
* Controller name [ArteveldeFrontOfficeBundle:Articles]: <return>
* Routing format (php, xml, yml, annotation) [annotation]: <return>
* Template format (twig, php) [twig]: <return>
* New action name (press <return> to stop adding actions): indexAction
* Action route [/index]: /articles
* …
* New action name (press <return> to stop adding actions): <return>
* Confirm automatic update of your Kernel [yes]? <return>
* Do you confirm generation [yes]? <return>
Controllers op basis van een Doctrine Entity worden gegenereerd in de Bundle waar de Entity Staat.
vagrant@homestead$ php app/console generate:doctrine:crud --entity=ArteveldeCommonBundle:User
Met volgende opties:
* The Entity shortcut name [ArteveldeCommonBundle:User]: <return>
* Do you want to generate the "write" actions [no]? y
* Configuration format (yml, xml, php, or annotation) [annotation]: <return>
* Routes prefix [/user]: <return>
* Do you confirm generation [yes]? <return>
Mappenstructuur en bestanden
nmdad-iii.arteveldehogeschool.be/ └── www/ └── src/ └── Artevelde/ └── CommonBundle/ ├── Controller/ | └── UserController.php ├── Forms/ | └── UserType.php └── Resources/ └── views/ └── User/ ├── edit.html.twig ├── index.html.twig ├── new.html.twig └── show.html.twig
Ga naar: http://dev.nmdad-iii.arteveldehogeschool.be/user/
Voor de hashing van het wachtwoord van de Entity User
gaan we het bcrypt algoritme gebruiken met een cost van 15
. Hoe hoger de cost, hoe langer het duurt om het wachtwoord te hashen en hoe minder praktisch het is om een succesvolle brute force-aanval uit te voeren. Bcrypt zorgt zelf voor een Salt, vandaar dat de getSalt()
methode null
mag teruggeven.
app/config/security.yml
:
security:
# …
encoders:
# Symfony\Component\Security\Core\User\User: plaintext
Artevelde\CommonBundle\Entity\User:
algorithm: bcrypt
cost: 15
# …
providers:
# in_memory:
# memory:
# users:
# user: { password: userpass, roles: [ 'ROLE_USER' ] }
# admin: { password: adminpass, roles: [ 'ROLE_ADMIN' ] }
default:
entity: { class: ArteveldeCommonBundle:User, property: username }
# …
In de Controller Action:
// Password Hashing
$factory = $this->get('security.encoder_factory');
$encoder = $factory->getEncoder($entity);
$hashedPassword = $encoder->encodePassword($entity->getPassword(), $entity->getSalt());
$entity->setPassword($hashedPassword);
Zie ook:
Om alle routes in dev
(developmentomgeving) te zien:
vagrant@homestead$ php app/console router:debug
Om alle routes in prod
(productieomgeving) te zien:
vagrant@homestead$ php app/console router:debug --env=prod
Normaal zijn er nog geen routes gedefineerd voor de productieomgeving.
Zie ook:
Views kunnen automatisch aangemaakt worden door de Generator als een Controller Action aangemaakt wordt.
Views worden in de Twig Templating Language geschreven. Het kan in principe in elke tekstgebaseerd bestandsformaat toegepast worden.
Twig is een autonoom project van SensioLabs dat standaard in Symfony2 geïntegreerd, maar het kan ook in andre projecten gebruikt worden. Enkele alternatieven voor Twig:
- PHP
- Rain.TPL
- [Smarty](http://www.smarty.net/
Twig-sjablonen (Twig Templates) eindigen op de extensie .twig
. Courante extensies:
.html.twig
.css.twig
.js.twig
.xml.twig
- …
Twig Templates worden automatisch tot gewone tekst gecompileerd, zodat het geen impact heeft op laadtijden.
Twig afbakeningstekens | Betekenis |
---|---|
{# en #} |
Commentaar. |
{% en %} |
Statement uitvoeren. |
{{ en }} |
Toont resultaat van een expressie evaluatie. |
{# volledig formulier renderen #}
{{ form(formulier_naam) }}
{# begintag van een formulier #}
{{ form_start(formulier_naam) }}
{# eindtag van een formulier #}
{{ form_end(formulier_naam) }}
{# Label van een element renderen #}
{{ form_label(formulier_naam.element_naam) }}
{# Element zelf renderen #}
{{ form_widget(formulier_naam.element_naam) }}
{# Validatiefouten renderen #}
{{ form_errors(formulier_naam.element_naam) }}
In de developmentomgeving kan je de Twig-functie dump()
gebruiken. Deze is vergelijkbaar met de standaard PHP-functie var_dump()
.
Je kan assets rechtstreeks in de map web/
zetten, maar je kan ze ook in de bundles zetten in de Resources/public/
map van de bundle.
Met deze Console-opdracht kan je de assets kopiëren naar de map web/
:
vagrant@homestead$ php app/console assets:install
Tip: Wil je echter onmiddellijk resultaat zien zonder telkens een install uit te voeren, dan kan je ook symbolische koppelingen gebruiken (al dan niet relatief ten opzichte van de projectmap).
vagrant@homestead$ php app/console assets:install --symlink --relative
OPGELET: niet elke server laat toe dat je symbolische koppelingen gebruikt!
Zie ook:
vagrant@homestead$ cd src/Artevelde/CommonBundle/Resources/
vagrant@homestead$ mkdir assets/
.sass-cache/
bower_components/
node_modules/
OPGELET: Deze stap is potentieel gevaarlijk!
vagrant@homestead$ sudo apt-get upgrade node vagrant@homestead$ sudo npm -g update
Proxyinstellingen worden bewaard in het bestand
~/.npmrc
proxy on sudo -E npm set http-poxy $proxy && sudo -E npm set https-poxy $proxy
Bower bewaart de instellingen in een bestand bower.json
dat je kan laten aanmaken met de opdracht init
:
vagrant@homestead$ cd src/Artevelde/CommonBundle/Resources/
vagrant@homestead$ mkdir assets/
vagrant@homestead$ cd assets/
vagrant@homestead$ bower init
Een package installeren in een submap met de naam bower_components
doe je met de opdracht install
en als je de dependency wil bewaren in het JSON-bestand, dan voeg je daar de optie --save
aan toe.
We installeren de officiële SASS-versie van Bootstrap:
vagrant@homestead$ bower install --save bootstrap-sass-official
vagrant@homestead$ bower install --save fontawesome
We bewaren de package dependency's in een bestand package.json
zodat we die niet in ons project moeten bewaren, maar later kunnen installeren met behulp npm install
.
vagrant@homestead$ cd src/Artevelde/CommonBundle/Resources/assets/
vagrant@homestead$ npm init
Gulp is in Laravel Homestead al globaal geïnstalleerd (met de optie --global
of -g
), maar nu moet je het nog lokaal in je project installeren met de --save-dev
optie.
We hebben de volgende NPM-packages nodig:
Tip: Installeer de packages één voor één in plaats van allemaal tezamen. Zo voorkom je build-problemen.
De meeste NMP-packages installeren we als volgt:
vagrant@homestead$ npm install --save-dev gulp
vagrant@homestead$ npm install --save-dev gulp-bower
vagrant@homestead$ npm install --save-dev gulp-notify
vagrant@homestead$ npm install --save-dev gulp-ruby-sass
vagrant@homestead$ npm install --save-dev gulp-uglify
OPGELET: Op Windows moet je PowerShell 3.0 gebruiken, opgestart als Administrator om vervolgens Vagrant met SMB (Server Message Block) folder sharing te starten:
In
Homestead.yaml
:- map: ~/Code to: /home/vagrant/Code type: smb
Gebruik daarna
npm
met de--no-bin-link
optie zodat geen Symbolische Koppelingen (symlinks) gebruikt worden:vagrant@homestead$ npm install --no-bin-link
Tip:
gulp-notify
maakt gebruik vannotify-send
om notificaties naar de desktop te sturen. Aangezien Ubuntu Server geen desktop heeft, is deze niet geïnstalleerd. Om de foutmelding te vermijden kan je deze toch installeren.vagrant@homestead$ sudo apt-get install -y libnotify-bin Of achter de proxyserver: vagrant@homestead$ proxy on vagrant@homestead$ sudo -E apt-get install -y libnotify-bin
We moeten ook nog SASS installeren met Ruby Gem:
vagrant@homestead$ sudo gem install sass
Of achter de proxyserver:
vagrant@homestead$ proxy on
vagrant@homestead$ sudo -E gem install sass
Daarna maken we een nieuwe bestand gulpfile.js
waarin we de Gulp Tasks schrijven.
De Gulp API heeft een aantal methoden:
Methode | Actie |
---|---|
task() |
Taak definiëren. |
src() |
Geeft stroom van ingelezen bronbestanden terug. |
pipe() |
Opdracht uitvoeren op ingelezen bronbestanden. |
dest() |
Bestanden wegschrijven naar doelbestemming. |
watch() |
Taken uitvoeren als bestanden wijzigen. |
gulpfile.js
:
'use strict';
var gulp = require('gulp'),
bower = require('gulp-bower'),
notify = require('gulp-notify'),
rename = require('gulp-rename'),
uglify = require('gulp-uglify'),
sass = require('gulp-ruby-sass'),
// Tasks
// -----
/**
* Run all tasks when 'gulp' is executed.
*/
gulp.task('default', ['css', 'js'], function() {
// Default task must exist.
});
/**
* Install Bower packages defined in bower.json into the given components folder.
*/
gulp.task('bower', function() {
// …
});
/**
* Create font files in destination folder.
*/
gulp.task('fonts', ['bower'], function() {
// …
});
/**
* Create CSS files in destination folder.
*/
gulp.task('css', ['bower', 'fonts'], function() {
// …
});
/**
* Create JS files in destination folder.
*/
gulp.task('js', ['bower'], function() {
// …
});
Met symbolische koppelingen:
vagrant@homestead$ php app/console assets:install --symlink --relative
Zonder (voor Windows Host Besturingssystemen):
vagrant@homestead$ php app/console assets:install
Zie ook:
Formulieren worden in een View gemaakt en bevatten velden uit een Entity. Omdat niet altijd alle velden van de Entity nodig zijn, kan je Form Types gebruiken. Deze bevatten een selectie en configuratie van de velden. Van een Form Type kan Symfony een Form renderen.
Doctrine kan eenvoudige Form Types generen op basis van een Entity. Deze kan je dan verder aanpassen en uitbreiden.
Om bijvoorbeeld een Form Type te genereren op basis van de Entity ArteveldeCommonBundle:User
:
vagrant@homestead$ php app/console generate:doctrine:form ArteveldeCommonBundle:User
Mappenstructuur en bestanden
nmdad-iii.arteveldehogeschool.be/ └── www/ └── src/ └── Artevelde/ └── CommonBundle/ ├── Controller/ ├── Forms/ | └── UserType.php └── Resources/
In src/Artevelde/CommonBundle/Forms/UserType.php
scheakelen we nog een paar velden uit die niet in het formulier getoond mogen worden:
<?php
namespace Artevelde\CommonBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class UserType extends AbstractType
{
/**
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('firstName')
->add('lastName')
// ->add('createdAt')
// ->add('enabledAt')
// ->add('lockedAt')
// ->add('expiredAt')
// ->add('expiredCredentialsAt')
// ->add('deletedAt')
->add('username')
->add('password')
;
}
/**
* @param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Artevelde\CommonBundle\Entity\User'
));
}
/**
* @return string
*/
public function getName()
{
return 'artevelde_commonbundle_user';
}
}
Zie ook:
We vertrekken van de SecurityController
. In de controller hebben we nieuwe Methoden nodig:
loginAction()
met als routenaamartevelde_frontoffice_security_login
De controller maken we met:
vagrant@homestead$ php app/console generate:controller --controller=ArteveldeFrontOfficeBundle:Security
Met volgende opties:
* Controller name [ArteveldeFrontOfficeBundle:Articles]: <return>
* Routing format (php, xml, yml, annotation) [annotation]: <return>
* Template format (twig, php) [twig]: <return>
* New action name (press <return> to stop adding actions): loginAction
* Action route [/login]: /login
* Templatename (optional) [ArteveldeFrontOfficeBundle:Security:login.html.twig]: <return>
* New action name (press <return> to stop adding actions): <return>
* Confirm automatic update of your Kernel [yes]? <return>
* Do you confirm generation [yes]? <return>
Mappenstructuur en bestanden
nmdad-iii.arteveldehogeschool.be/ └── www/ └── src/ └── Artevelde/ └── FrontofficeBundle/ ├── Controller/ | └── SecurityController.php └── Resources/ └── views/ └── Security/ └── login.html.twig
We kunnen de routes opvragen met:
vagrant@homestead$ php app/console router:debug | grep 'artevelde_frontoffice_security'
Er is nu al één route, maar we hebben nog twee speciale routes nodig die door Symfony afgehandeld worden:
- Een route om de aanmeldgegevens te verifiëren.
- Een route om terug af te melden
We maken een nieuw routeconfiguratiebestand aan
src/Artevelde/FrontOffice/Resources/config/routing.yml
artevelde_frontoffice_security_check:
pattern: /login_check
artevelde_frontoffice_security_logout:
pattern: /logout
Nu moeten we de routes nog activeren door dit nieuwe bestand als resource toe te voegen aan
app/config/routing.yml
artevelde_front_office_extensions:
resource: "@ArteveldeFrontOfficeBundle/Resources/config/routing.yml"
prefix: /security
Uiteindelijk zouden we deze routes moeten hebben:
artevelde_frontoffice_security_login ANY ANY ANY /security/login
artevelde_frontoffice_security_check ANY ANY ANY /security/login_check
artevelde_frontoffice_security_logout ANY ANY ANY /security/logout
We kopieren src/Artevelde/CommonBundle/Form/UserType.php
naar
src/Artevelde/FrontOfficeBundle/Form/UserLoginType.php
en passen het aan:
<?php
namespace Artevelde\FrontOfficeBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class UserLoginType extends AbstractType
{
/**
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('username', 'text', [
'label' => 'Username',
'attr' => ['placeholder' => 'Enter your username.'],
])
->add('password', 'password', [
'label' => 'Password',
'attr' => ['placeholder' => 'Enter your password.'],
])
->add('remember', 'checkbox', [
'label' => 'Remember me',
'data' => true,
])
->add('btn_login', 'submit', [
'label' => 'Sign on',
])
;
}
/**
* @param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults([
'data_class' => 'Artevelde\CommonBundle\Entity\User'
]);
}
/**
* Name of the form.
*
* @return string
*/
public function getName()
{
return 'artevelde_frontoffice_security_login_form';
}
}
<?php
namespace Artevelde\FrontOfficeBundle\Controller;
use Artevelde\CommonBundle\Entity\User;
use Artevelde\FrontOfficeBundle\Form\UserLoginType;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\SecurityContext;
/**
* Class SecurityController
* @package Artevelde\FrontOfficeBundle\Controller
*
* @route("/security")
*/
class SecurityController extends Controller
{
/**
* @Route("/login")
* @Template()
*/
public function loginAction(Request $request)
{
$entity = new User();
$form = $this->createForm(new UserLoginType(), $entity, [
'action' => $this->generateUrl('artevelde_frontoffice_security_check'),
'method' => 'POST',
]);
if ($request->attributes->has(SecurityContext::AUTHENTICATION_ERROR)) {
$error = $request->attributes->get(SecurityContext::AUTHENTICATION_ERROR);
} else {
$session = $request->getSession();
$error = $session->get(SecurityContext::AUTHENTICATION_ERROR);
$session->remove(SecurityContext::AUTHENTICATION_ERROR);
}
/**
* Return array with variables for Twig.
*/
return [
'form' => $form->createView(),
'error' => $error,
];
}
}
Zie ook:
src/Artevelde/FrontOfficeBundle/Resources/views/Security/login.html.twig
:
{% extends "::base.html.twig" %}
{% block title %}ArteveldeFrontOfficeBundle:Security:login{% endblock %}
{% block body %}
<h1>Welcome to the Security:login page</h1>
{% if error %}
<div>{{ error.message }}</div>
{% endif %}
{{ form(form) }}
{% endblock %}
Bekijk het formulier op http://dev.nmdad-iii.arteveldehogeschool.be/security/login
Let op de waarden in de name
-attributen van de formulierelementen, die hebben we straks nodig om de Security Firewall te configureren.
Nu de routes en het formulier gekend zijn kunnen we de Symfony Security Firewall configureren om deze routes te gebruiken.
app/config/security.yml
:
# …
firewalls:
# …
artevelde_frontoffice_secured_area:
pattern: ^/
anonymous: ~
form_login:
login_path: artevelde_frontoffice_security_login
check_path: artevelde_frontoffice_security_check
username_parameter: artevelde_frontoffice_security_login_form[username]
password_parameter: artevelde_frontoffice_security_login_form[password]
remember_me:
key: "%secret%"
lifetime: 31536000 # 365 days in seconds
path: /
domain: ~ # Defaults to the current domain from $_SERVER
remember_me_parameter: artevelde_frontoffice_security_login_form[remember]
logout:
path: artevelde_frontoffice_security_logout
target: /
# …
Er moet nog een publiek toegankelijke membervariabele aangemaakt worden voor de Remember Me functionaliteit.
// …
/**
* Remember me.
*
* @var bool
*/
public $remember = true;
// …
- Maak een gebruiker aan op http://dev.nmdad-iii.arteveldehogeschool.be/user/new
- Meld de gebruiker aan op http://dev.nmdad-iii.arteveldehogeschool.be/security/login
- Als het aanmelden succesvol was, dan moet de gebruikersnaam in de Symfony Web Debug Toolbar verschijnen.
- Afmelden kan eenvoudigweg door naar http://dev.nmdad-iii.arteveldehogeschool.be/security/logout te gaan.
Zie ook:
Standaardmethodes voor de Entity-repository:
find()
findAll()
findBy()
findOneBy()
Daarnaast maak je je eigen methodes in Repository-klasse die aan de Entity gelinkt is via deze annotatie: @ORM\Entity(repositoryClass="")
.