• Название:

    Symfony

  • Размер: 3.4 Мб
  • Формат: PDF
  • или

    Symfony Documentation
    Âûïóñê 2.0

    Fabien Potencier

    28 September 2011

    Îãëàâëåíèå

    I

    Êðàòêèé òóð

    3

    1

    Îáùàÿ êàðòèíà
    1.1 Çàãðóçêà Symfony2 . . .
    1.2 Ïðîâåðêà êîíôèãóðàöèè
    1.3 Ïîíèìàíèå îñíîâ . . . .
    1.4 Ðàáîòà ñ îêðóæåíèÿìè .
    1.5 Çàêëþ÷èòåëüíîå ñëîâî .

    2

    Âèä
    2.1
    2.2
    2.3
    2.4
    2.5

    3

    Êîíòðîëëåð
    3.1 Èñïîëüçîâàíèå ôîðìàòîâ . . . . . . . . .
    3.2 Ïåðåìåùåíèÿ è ïåðåíàïðàâëåíèÿ . . . .
    3.3 Ïîëó÷åíèå èíôîðìàöèè î çàïðîñå . . .
    3.4 Ñîõðàíåíèå è ïîëó÷åíèå èíôîðìàöèè èç
    3.5 Securing Resources . . . . . . . . . . . . .
    3.6 Caching Resources . . . . . . . . . . . . .
    3.7 Çàêëþ÷èòåëüíîå ñëîâî . . . . . . . . . .

    4

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    5
    5
    6
    7
    12
    14

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    15
    15
    17
    18
    20
    20

    . . . . .
    . . . . .
    . . . . .
    ñåññèè .
    . . . . .
    . . . . .
    . . . . .

    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .

    21
    21
    22
    23
    23
    24
    25
    26

    Àðõèòåêòóðà
    4.1 Ñòðóêòóðà ïàïîê . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
    4.2 Ñèñòåìà áàíäëîâ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
    4.3 Using Vendors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

    27
    27
    29
    33

    Twig, êðàòêèé îáçîð . . . .
    Äåêîðèðîâàíèå øàáëîíîâ .
    Òåãè, ôèëüòðû è ôóíêöèè .
    Ýêðàíèðîâàíèå ïåðåìåííûõ
    Çàêëþ÷èòåëüíîå ñëîâî . . .

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    .
    .
    .
    .
    .

    i

    4.4
    4.5
    4.6
    II

    5

    III

    6

    ii

    Êýøèðîâàíèå è Ëîãè . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
    Èíòåðôåéñ êîìàíäíîé ñòðîêè . . . . . . . . . . . . . . . . . . . . . . . . .
    Çàêëþ÷èòåëüíîå ñëîâî . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

    Ðóêîâîäñòâà

    Êíèãà
    5.1 Îñíîâû Symfony2 è ïðîòîêîëà HTTP
    5.2 Symfony2 versus Flat PHP . . . . . . .
    5.3 Installing and Conguring Symfony . . .
    5.4 Ñîçäàíèå ñòðàíèö â Symfony2 . . . . .
    5.5 Controller . . . . . . . . . . . . . . . . .
    5.6 Routing . . . . . . . . . . . . . . . . . .
    5.7 Creating and using Templates . . . . . .
    5.8 Áàçû äàííûõ è Doctrine (Ìîäåëè) .
    5.9 Òåñòèðîâàíèå . . . . . . . . . . . . . .
    5.10 Âàëèäàöèÿ . . . . . . . . . . . . . . . .
    5.11 Ôîðìû . . . . . . . . . . . . . . . . . .
    5.12 Security . . . . . . . . . . . . . . . . . .
    5.13 HTTP Cache . . . . . . . . . . . . . . .
    5.14 Translations . . . . . . . . . . . . . . .
    5.15 Service Container . . . . . . . . . . . . .
    5.16 Performance . . . . . . . . . . . . . . .
    5.17 Internals . . . . . . . . . . . . . . . . .
    5.18 Ñòàáèëüíûé API äëÿ Symfony2 . . . .

    33
    34
    34
    35

    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .

    Ðåöåïòû

    Ðåöåïòû
    6.1 How to Create and store a Symfony2 Project in git . . . . . . . . .
    6.2 Êàê ñîçäàòü ñîáñòâåííûå ñòðàíèöû îøèáîê . . . . . . . . . . . .
    6.3 How to dene Controllers as Services . . . . . . . . . . . . . . . . .
    6.4 How to force routes to always use HTTPS . . . . . . . . . . . . . .
    6.5 How to allow a / character in a route parameter . . . . . . . . . .
    6.6 How to Use Assetic for Asset Management . . . . . . . . . . . . . .
    6.7 How to Minify JavaScripts and Stylesheets with YUI Compressor .
    6.8 How to Use Assetic For Image Optimization with Twig Functions .
    6.9 How to Apply an Assetic Filter to a Specic File Extension . . . .
    6.10 How to handle File Uploads with Doctrine . . . . . . . . . . . . . .
    6.11 Doctrine Extensions: Timestampable: Sluggable, Translatable, etc.
    6.12 Registering Event Listeners and Subscribers . . . . . . . . . . . . .
    6.13 How to generate Entities from an Existing Database . . . . . . . .
    6.14 How to use Doctrine's DBAL Layer . . . . . . . . . . . . . . . . .

    39
    39
    49
    63
    68
    87
    100
    121
    143
    170
    185
    199
    226
    260
    278
    293
    312
    314
    334
    337

    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .

    339
    339
    341
    343
    344
    346
    347
    352
    354
    359
    363
    370
    371
    373
    376

    6.15
    6.16
    6.17
    6.18
    6.19
    6.20
    6.21
    6.22
    6.23
    6.24
    6.25
    6.26
    6.27
    6.28
    6.29
    6.30
    6.31
    6.32
    6.33
    6.34
    6.35
    6.36
    6.37
    6.38
    6.39
    6.40
    6.41
    6.42
    6.43
    6.44
    6.45
    6.46
    6.47
    6.48
    6.49
    6.50
    6.51
    6.52
    6.53
    6.54
    6.55
    6.56
    6.57
    6.58
    6.59

    How to work with Multiple Entity Managers . . . . . . . . . . . . . .
    Registering Custom DQL Functions . . . . . . . . . . . . . . . . . .
    How to customize Form Rendering . . . . . . . . . . . . . . . . . . .
    How to Create a Custom Form Field Type . . . . . . . . . . . . . . .
    How to create a Custom Validation Constraint . . . . . . . . . . . .
    How to Master and Create new Environments . . . . . . . . . . . . .
    How to Set External Parameters in the Service Container . . . . . .
    How to Use a Factory to Create Services . . . . . . . . . . . . . . . .
    How to Manage Common Dependencies with Parent Services . . . .
    How to work with Scopes . . . . . . . . . . . . . . . . . . . . . . . .
    How to use PdoSessionStorage to store Sessions in the Database . . .
    Bundle Structure and Best Practices . . . . . . . . . . . . . . . . . .
    How to use Bundle Inheritance to Override parts of a Bundle . . . .
    How to expose a Semantic Conguration for a Bundle . . . . . . . .
    Êàê îòïðàâëÿòü ïèñüìà . . . . . . . . . . . . . . . . . . . . . . . .
    Êàê èñïîëüçîâàòü Gmail äëÿ îòïðàâêè ïèñåì . . . . . . . . . . . .
    How to Work with Emails During Development . . . . . . . . . . . .
    How to Spool Email . . . . . . . . . . . . . . . . . . . . . . . . . . .
    How to simulate HTTP Authentication in a Functional Test . . . . .
    How to test the Interaction of several Clients . . . . . . . . . . . . .
    How to use the Proler in a Functional Test . . . . . . . . . . . . . .
    How to test Doctrine Repositories . . . . . . . . . . . . . . . . . . .
    How to add Remember Me Login Functionality . . . . . . . . . . .
    How to implement your own Voter to blacklist IP Addresses . . . . .
    Access Control Lists (ACLs) . . . . . . . . . . . . . . . . . . . . . .
    Advanced ACL Concepts . . . . . . . . . . . . . . . . . . . . . . . .
    How to force HTTPS or HTTP for Dierent URLs . . . . . . . . . .
    How to customize your Form Login . . . . . . . . . . . . . . . . . . .
    How to secure any Service or Method in your Application . . . . . .
    How to load Security Users from the Database (the entity Provider)
    How to create a custom User Provider . . . . . . . . . . . . . . . . .
    How to create a custom Authentication Provider . . . . . . . . . . .
    How to change the Default Target Path Behavior . . . . . . . . . . .
    How to use Varnish to speedup my Website . . . . . . . . . . . . . .
    How to use PHP instead of Twig for Templates . . . . . . . . . . . .
    How to autoload Classes . . . . . . . . . . . . . . . . . . . . . . . . .
    How to locate Files . . . . . . . . . . . . . . . . . . . . . . . . . . . .
    How to create Console/Command-Line Commands . . . . . . . . . .
    How to optimize your development Environment for debugging . . .
    Êàê èñïîëüçîâàòü Monolog äëÿ æóðíàëèðîâàíèÿ . . . . . . . . . .
    How to extend a Class without using Inheritance . . . . . . . . . . .
    How to customize a Method Behavior without using Inheritance . . .
    How to register a new Request Format and Mime Type . . . . . . .
    How to create a custom Data Collector . . . . . . . . . . . . . . . . .
    How to Create a SOAP Web Service in a Symfony2 Controller . . . .

    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .

    380
    381
    383
    399
    399
    401
    408
    411
    415
    426
    430
    433
    438
    439
    449
    451
    453
    455
    457
    457
    458
    459
    463
    467
    470
    474
    478
    479
    487
    492
    493
    493
    503
    504
    506
    511
    513
    517
    523
    524
    529
    531
    532
    534
    537
    iii

    6.60 Symfony2 äëÿ ïîëüçîâàòåëåé symfony 1 . . . . . . . . . . . . . . . . . . . . 541
    IV

    7

    V

    8
    VI

    9

    Ñïðàâî÷íûå äîêóìåíòû

    Reference Documents
    7.1 FrameworkBundle Conguration (framework)
    7.2 AsseticBundle Conguration Reference . . . . .
    7.3 Conguration Reference . . . . . . . . . . . . .
    7.4 Security Conguration Reference . . . . . . . .
    7.5 SwiftmailerBundle Conguration . . . . . . . .
    7.6 TwigBundle Conguration Reference . . . . . .
    7.7 Conguration Reference . . . . . . . . . . . . .
    7.8 WebProlerBundle Conguration . . . . . . . .
    7.9 Form Types Reference . . . . . . . . . . . . . .
    7.10 Twig Template Form Function Reference . . . .
    7.11 Validation Constraints Reference . . . . . . . .
    7.12 The Dependency Injection Tags . . . . . . . . .
    7.13 YAML . . . . . . . . . . . . . . . . . . . . . . .
    7.14 Requirements for running Symfony2 . . . . . .
    Bundles

    Symfony SE Bundles
    Ó÷àñòèå â ïðîåêòå

    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .

    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .
    .

    557
    557
    563
    564
    570
    574
    575
    576
    578
    579
    645
    647
    704
    710
    717
    721

    725
    727

    Contributing
    731
    9.1 Contributing Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 731
    9.2 Contributing Documentation . . . . . . . . . . . . . . . . . . . . . . . . . . 742
    9.3 Community . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 748

    Àëôàâèòíûé óêàçàòåëü

    iv

    553

    753

    Symfony Documentation, Âûïóñê 2.0
    Áûñòðûé ñòàðò ñ Symfony2 Êðàòêèé Îáçîð:

    Îãëàâëåíèå

    1

    Symfony Documentation, Âûïóñê 2.0

    2

    Îãëàâëåíèå

    ×àñòü I
    Êðàòêèé òóð

    3

    Ãëàâà 1

    Îáùàÿ êàðòèíà
    Èòàê âû õîòèòå ïîïðîáîâàòü Symfony2, íî â íàëè÷èè ó âàñ íå áîëåå 10 ìèíóò? Ïåðâàÿ
    ÷àñòü ýòîãî ó÷åáíèêà íàïèñàíà äëÿ âàñ. Îíà îáúÿñíèò êàê áûñòðî íà÷àòü ñ Symfony2,
    ïîêàçàâ ñòðóêòóðó ïðîñòîãî ãîòîâîãî ïðîåêòà.
    Åñëè âû êîãäà-íèáóäü èñïîëüçîâàëè êàêîé-ëèáî âåá-ôðåéìâîðê ïðåæäå, âû áóäåòå ÷óâñòâîâàòü ñåáÿ â Symfony2 êàê äîìà.
    Ñîâåò: Õîòèòå óçíàòü çà÷åì è ïî÷åìó ñòîèò èñïîëüçîâàòü ôðåéìâîðê? Ïðî÷òèòå
    Symfony çà 5 ìèíóò.

    1.1 Çàãðóçêà Symfony2

     ïåðâóþ î÷åðåäü, óáåäèòåñü ÷òî ó âàñ óñòàíîâëåí êàê ìèíèìóì PHP 5.3.2 è îí íàñòðîåí
    äëÿ ðàáîòû ñ web ñåðâåðîì, òàêèì êàê Apache.
    Ãîòîâû? Äàâàéòå íà÷íåì ñ çàãðóçêè Symfony2 Standard Edition, äèñòðèáóòèâà
    (distribution) Symfony íàñòðîåííîãî äëÿ áîëüøèíñòâà ïîòðåáíîñòåé, à òàê æå ñîäåðæàùèé êîä, ïîêàçûâàþùèé êàê èñïîëüçîâàòü Symfony2 (çàãðóçèòå àðõèâ ñ áèáèëîòåêàìè
    (vendors) ÷òîáû íà÷àòü áûñòðåå).
    Ïîñëå ðàñïàêîâêè àðõèâà â êîðíåâóþ äèðåêòîðèþ âåá-ñåðâåðà, âû äîëæíû ïîëó÷èòü
    ïàïêó Symfony/, â êîòîðîé ñîäåðæèòñÿ ñëåäóþùåå:

    www/ <- âàø êîðíåâîé êàòàëîã
    Symfony/ <- ðàñïàêîâàííûõ àðõèâ
    app/
    cache/
    cong/
    logs/
    5

    Symfony Documentation, Âûïóñê 2.0

    Resources/
    bin/
    src/
    Acme/
    DemoBundle/
    Controller/
    Resources/
    ...
    vendor/
    symfony/
    doctrine/
    ...
    web/
    app.php
    ...
    Ïðèìå÷àíèå: Åñëè âû çàãðóçèëè ñòàíäàðòíîå èçäàíèå áåç áèáèëîòåê (without vendors),
    ïðîñòî çàïóñòèòå êîìàíäó ÷òîáû ïîëó÷èòü âñå áèáëèîòåêè:

    php bin/vendors install

    1.2 Ïðîâåðêà êîíôèãóðàöèè

    Äëÿ òîãî ÷òîáû èçáåæàòü ãîëîâíîé áîëè â áóäóùåì, ïðîâåðüòå, ñìîæåò ëè âàøà ñèñòåìà
    çàïóñòèòü Symfony2 áåç ïðîáëåì  äëÿ ýòîãî îòêðîéòå ñëåäóþùèé URL:

    http://localhost/Symfony/web/cong.php
    Åñëè â ñïèñêå åñòü îøèáêè - èñïðàâüòå èõ. Âû òàê æå ìîæåòå íàñòðîèòü ñåðâåð, ñëåäóÿ
    ðåêîìåíäàöèÿì. Êîãäà âñå áóäåò èñïðàâëåíî, êëèêíèòå íà Bypass conguration and go
    to the Welcome page ÷òîáû ïåðåéòè íà âàøó ïåðâóþ ðåàëüíóþ ñòðàíèöó íà Symfony2:

    http://localhost/Symfony/web/app_dev.php/
    Symfony2 äîëæåí ïîáëàãîäàðèòü âàñ çà ïðèëîæåííûå óñèëèÿ!

    6

    Ãëàâà 1. Îáùàÿ êàðòèíà

    Symfony Documentation, Âûïóñê 2.0

    1.3 Ïîíèìàíèå îñíîâ

    Îäíà èç ãëàâíûõ öåëåé ôðåéìâîðêà - ñëåäîâàòü êîíöåïöèè ðàçäåëåíèÿ îòâåòñòâåííîñòè. Ýòî äåëàåò âàø êîä îðãàíèçîâàííûì è ïîçâîëÿåò ýâîëþöèîíèðîâàòü ïðèëîæåíèþ,
    èçáåãàÿ ñìåñè çàïðîñîâ ê áàçå, HTML-òýãîâ è áèçíåñ-ëîãèêè â îäíîì ñêðèïòå. ×òîáû äîñòè÷ü ýòó öåëü ñ Symfony2 âû äîëæíû óçíàòü íåñêîëüêî ôóíäàìåíòàëüíûõ êîíöåïöèé
    è òåðìèíîâ.
    Ñîâåò: Õîòèòå äîêàçàòåëüñòâ, ÷òî èñïîëüçîâàíèå ôðåéìâîðêà ëó÷øå ÷åì ñìåñè âñåãî â
    îäíîì ñêðèïòå? Ïðî÷òèòå ãëàâó Symfony2 versus Flat PHP
    Áàçîâîå èçäàíèå èäåò ñ ïðèìåðîì êîäà, ÷òî ïîçâîëÿåò óçíàòü âàì áîëüøå î ãëàâíûõ êîíöåïöèÿõ Symfony2. Ïåðåéäèòå ïî ñëåäóþùåìó URL, ÷òîáû Symfony2 ïîïðèâåòñòâîâàëà
    âàñ (çàìåíèòå Fabien ñâîèì èìåíåì):

    http://localhost/Symfony/web/app_dev.php/demo/hello/Fabien

    1.3. Ïîíèìàíèå îñíîâ

    7

    Symfony Documentation, Âûïóñê 2.0

    ×òî ïðîèñõîäèò â ýòîì ìåñòå? Äàâàéòå ðàçáåð¼ì URL:

    • app_dev.php: Ýòî front controller. Óíèêàëüíàÿ òî÷êà âõîäà äëÿ ïðèëîæåíèÿ, êîòîðàÿ îòâå÷àåò íà âñå çàïðîñû ïîëüçîâàòåëÿ;
    • /demo/hello/Fabien: Ýòî âèðòóàëüíûé ïóòü ðåñóðñà, ê êîòîðîìó ïîëüçîâàòåëü õî÷åò ïîëó÷èòü äîñòóï.
    Îò âàñ êàê îò ðàçðàáîò÷èêà òðåáóåòñÿ íàïèñàòü êîä, êîòîðûé ñîïîñòàâèò ïîëüçîâàòåëüñêèé çàïðîñ (/hello/Fabien) è àññîöèèðîâàííûé ñ íèì ðåñóðñ (Ñòðàíèöà Hello Fabien!).
    1.3.1 Ìàðøðóòèçàöèÿ

    Symfony2 íàïðàâëÿåò çàïðîñ íà êîä, êîòîðûé ñðàâíèâàåò òåêóùèé URL ñ íàñòðîåííûìè øàáëîíàìè. Ïî óìîë÷àíèþ øàáëîíû (íàçûâàåìûå ìàðøðóòàìè) çàäàþòñÿ â ôàéëå
    app/cong/routing.yml. Åñëè âû â dev îêðóæåíèè - íà ýòî óêàçûâàåò front-êîíòðîëëåð
    app_**dev**.php - ôàéë app/cong/routing_dev.yml òàê æå áóäåò çàãðóæåí. Â ñòàíäàðòíîé ïîñòàâêå, ìàðøðóòû demo-ñòðàíèö óêàçûâàþòñÿ â ýòîì ôàéëå:

    # app/cong/routing_dev.yml
    _welcome:
    pattern: /
    defaults: { _controller: AcmeDemoBundle:Welcome:index }
    _demo:
    resource: "@AcmeDemoBundle/Controller/DemoController.php"
    type: annotation
    prex: /demo
    8

    Ãëàâà 1. Îáùàÿ êàðòèíà

    Symfony Documentation, Âûïóñê 2.0

    # ...
    Òðè ïåðâûå ëèíèè (ïîñëå êîììåíòàðèÿ) çàäàþò êîä, êîòîðûé áóäåò çàïóùåí ïðè çàïðîñå ðåñóðñà / (ò.å. ñòðàíèöû ïðèâåòñòâèÿ). Ïîñëå çàïðîñà áóäåò çàïóùåí êîíòðîëëåð
    AcmeDemoBundle:Welcome:index.
    Ñîâåò:  ñòàíäàðòíîé ïîñòàâêå Symfony2 èñïîëüçóåò YAML äëÿ ôàéëîâ êîíôèãóðàöèè, íî Symfony2 òàê æå ïîääåðæèâàåò XML, PHP è àííîòàöèè èç êîðîáêè. Ðàçëè÷íûå ôîðìàòû ñîâìåñòèìû è ìîãóò áûòü âçàèìîçàìåíÿåìû âíóòðè ïðèëîæåíèÿ. Òàê æå
    áûñòðîäåéñòâèå âàøåãî ïðèëîæåíèÿ íå çàâèñèò îò ôîðìàòà êîíôèãóðàöèè, êîòîðûé âû
    âûáåðåòå - âñå áóäåò çàêåøèðîâàíî ïðè ïåðâîì çàïðîñå.

    1.3.2 Êîíòðîëëåðû

    Êîíòðîëëåð îáðàáàòûâàåò âõîäÿùèé çàïðîñ è âîçâðàùàåò îòâåò (÷àùå âñåãî HTML-êîä). Âìåñòî èñïîëüçîâàíèÿ ãëîáàëüíûõ ïåðåìåííûõ è ôóíêöèé (òàêèõ êàê $_GET èëè header()) äëÿ óïðàâëåíèÿ HTTP-ñîîáùåíèÿìè
    Symfony èñïîëüçóåò îáúåêòû Symfony\Component\HttpFoundation\Request è
    Symfony\Component\HttpFoundation\Response. Ïðîñòåéøèé êîíòðîëëåð, êîòîðûé
    ñîçäàåò îòâåò íà áàçå çàïðîñà:

    use Symfony\Component\HttpFoundation\Response;
    $name = $request->query->get('name');
    return new Response('Hello '.$name, 200, array('Content-Type' => 'text/plain'));
    Ïðèìå÷àíèå: Symfony2 èñïîëüçóåò ñïåöåôèêàöèè ïðîòîêîëà HTTP, êîòîðûå ÿâëÿþòñÿ
    ïðàâèëàìè äëÿ âñåõ êîììóíèêàöèé â Web. Ïðî÷òèòå ãëàâó Îñíîâû Symfony2 è ïðîòîêîëà HTTP ÷òîáû áîëüøå óçíàòü îá ýòîì.
    Symfony2 âûáèðàåò êîíòðîëëåð áàçèðóÿñü íà çíà÷åíèè _controller èç êîíôèãóðàöèè
    ìàðøðóòèçàöèè: AcmeDemoBundle:Welcome:index. Ýòî - ëîãè÷åñêîå èìÿ è îíî óêàçûâàåò íà ìåòîä indexAction êëàññà Acme\DemoBundle\Controller\WelcomeController:

    // src/Acme/DemoBundle/Controller/WelcomeController.php
    namespace Acme\DemoBundle\Controller;
    use Symfony\Bundle\FrameworkBundle\Controller\Controller;
    class WelcomeController extends Controller
    {
    1.3. Ïîíèìàíèå îñíîâ

    9

    Symfony Documentation, Âûïóñê 2.0

    }

    public function indexAction()
    {
    return $this->render('AcmeDemoBundle:Welcome:index.html.twig');
    }

    Ñîâåò: Âû ìîãëè áû èñïîëüçîâàòü Acme\DemoBundle\Controller\WelcomeController::indexAction
    äëÿ çíà÷åíèÿ _controller, íî åñëè ñëåäîâàòü ïðîñòûì ñîãëàøåíèÿì ëîãè÷åñêîå èìÿ
    ìîæåò áûòü áîëåå ïðîñòûì è áîëåå ãèáêèì.
    Êëàññ
    WelcomeController
    ðàñøèðÿåò
    âñòðîåííûé
    êëàññ
    Controller
    êîòîðûé
    ïðåäñòàâëÿåò
    óäîáíûå
    ìåòîäû,
    òàêîé
    êàê
    :method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller::render`
    êîòîðûé çàãðóæàåò è îòîáðàæàåò øàáëîí (AcmeDemoBundle:Welcome:index.html.twig).
    Âîçâðàùàåìîå çíà÷åíèå - ýòî îáúåêò Response, íàïîëíåííûé îòîáðàæàåìûì êîíòåíòîì.
    Òàê, åñëè âàì íóæíî, Response ìîæåò áûòü íàñòðîåí äî îòïðàâêè áðàóçåðó:

    public function indexAction()
    {
    $response = $this->render('AcmeDemoBundle:Welcome:index.txt.twig');
    $response->headers->set('Content-Type', 'text/plain');
    }

    return $response;

    Íå âàæíî êàê âû ýòî ñäåëàåòå, êîíå÷íàÿ öåëü â òîì, ÷òî êîíòðîëëåð âñåãäà âîçðàùàåò
    îáúåêò Response, êîòîðûé äîëæåí áûòü âîçðàùåí ïîëüçîâàòåëþ îáðàòíî. Ýòîò îáúåêò
    ìîæåò áûòü íàïîëíåí HTML-êîäîì, ïðåäñòàâëÿòü èç ñåáÿ ïåðåíàïðàâëåíèå íà äðóãóþ
    ñòðàíèöó èëè âîçðàùàòü ñîäåðæèìîå JPG- èçîáðàæåíèÿ ñ çàãîëîâêîì Content-Type
    image/jpg.
    Ñîâåò: Ðàñøèðÿòü êëàññ Controller íå îáÿçàòåëüíî. Â ñóùíîñòè, êîíòðîëëåð ìîæåò áûòü
    ôóíêöèåé íà ïëîñêîì PHP èëè äàæå PHP-çàìûêàíèåì. Ãëàâà Êîíòðîëëåð ðàññêàæåò
    âàì âñå î êîíòðîëëåðàõ Symfony2.
    Èìÿ øàáëîíà AcmeDemoBundle:Welcome:index.html.twig - ýòî ëîãè÷åñêîå èìÿ è
    îíî óêàçûâàåò íà ôàéë Resources/views/Welcome/index.html.twig âíóòðè ïàêåòà
    AcmeDemoBundle (ðàñïîëîæåííîãî â src/Acme/DemoBundle). Ãëàâà î ïàêåòàõ ðàññêàæåò âàì ïî÷åìó ýòî óäîáíî.
    À ñåé÷àñ, äàâàéòå ñíîâà âçãëÿíåì íà êîíôèãóðàöèþ ìàðøðóòèçàöèè:

    # app/cong/routing_dev.yml
    _demo:
    10

    Ãëàâà 1. Îáùàÿ êàðòèíà

    Symfony Documentation, Âûïóñê 2.0

    resource: "@AcmeDemoBundle/Controller/DemoController.php"
    type: annotation
    prex: /demo
    Symfony2 ìîæåò ÷èòàòü èíôîðìàöèþ î ìàðøðóòèçàöèè â ôîðìàòàõ YAML,
    XML, PHP èëè äàæå âñòðîåííûõ â PHP àííîòàöèé. Çäåñü ëîãè÷åñêîå èìÿ
    @AcmeDemoBundle/Controller/DemoController.php
    ññûëàåòñÿ
    íà
    ôàéë
    src/
    Acme/DemoBundle/Controller/DemoController.php. Â ýòîì ôàéëå ìàðøðóòû çàäàíû êàê àííîòàöèè ê ìåòîäàì:

    // src/Acme/DemoBundle/Controller/DemoController.php
    use Sensio\Bundle\FrameworkExtraBundle\Conguration\Route;
    use Sensio\Bundle\FrameworkExtraBundle\Conguration\Template;
    class DemoController extends Controller
    {
    /**
    * @Route("/hello/{name}", name="_demo_hello")
    * @Template()
    */
    public function helloAction($name)
    {
    return array('name' => $name);
    }
    }

    // ...

    Àííîòàöèÿ @Route() çàäàåò íîâûé ìàðøðóò ñ øàáëîíîì /hello/{name}, êîòîðûé çàïóñêàåò ìåòîä helloAction ïðè ñîâïàäåíèè. Ñòðîêà, îáåðíóòàÿ â ôèãóðíûå ñêîáêè, òàêàÿ
    êàê {name} íàçûâàåòñÿ placeholder. Êàê âû ìîæåòå âèäåòü åå çíà÷åíèå äîñòóïíî ÷åðåç
    àðãóìåíò $name.
    Ïðèìå÷àíèå: Äàæå åñëè àííîòàöèè íå ïîääåðæèâàþòñÿ PHP, âû ìîæåòå èõ øèðîêî
    èñïîëüçîâàòü â Symfony2 êàê óäîáíûé ñïîñîá õðàíèòü íàñòðîéêè ðÿäîì ñ êîäîì.
    Åñëè âû âíèìàòåëüíî ïîñìîòðèòå íà êîä äåéñòâèÿ, òî ñìîæåòå óâèäåòü, ÷òî âìåñòî
    âûâîäà øàáëîíà êàê ðàíüøå, òåïåðü ìû ïðîñòî âîçâðàùàåì ìàññèâ ïàðàìåòðîâ. Àííîòàöèÿ @Template() ãîâîðèò Symfony2 îòîáðàçèòü øàáëîí, ïåðåäàâàÿ êàæäûé ïàðàìåòð ìàññèâà â øàáëîí. Íàçâàíèå øàáëîíà ñëåäóåò èç èìåíè êîíòðîëëåðà. Òàê, â ýòîì
    ïðèìåðå, îòîáðàçèòñÿ øàáëîí AcmeDemoBundle:Demo:hello.html.twig (ðàñïîëîæåííûé
    â src/Acme/DemoBundle/Resources/views/Demo/hello.html.twig).
    Ñîâåò: Àííîòàöèè @Route() è @Template() áîëåå ìîùíûå ÷åì ïîêàçàíî â ýòîì ïðèìåðå.
    Óçíàéòå áîëüøå î àííîòàöèÿõ â êîíòðîëëåðàõ â îôèöèàëüíîé äîêóìåíòàöèè.
    1.3. Ïîíèìàíèå îñíîâ

    11

    Symfony Documentation, Âûïóñê 2.0

    1.3.3 Øàáëîíû

    Êîíòðîëëåð îòîáðàæàåò øàáëîí src/Acme/DemoBundle/Resources/views/Demo/hello.html.twig
    (èëè AcmeDemoBundle:Demo:hello.html.twig åñëè âû ïðåäïî÷èòàåòå ëîãè÷åñêèå èìåíà):

    {# src/Acme/DemoBundle/Resources/views/Demo/hello.html.twig #}
    {% extends "AcmeDemoBundle::layout.html.twig" %}
    {% block title "Hello " ~ name %}
    {% block content %}
    <h1>Hello {{ name }}!</h1>
    {% endblock %}
    Ïî óìîë÷àíèþ, Symfony2 èñïîëüçóåò Twig â êà÷åñòâå øàáëíèçàòîðà, íî âû òàê æå ìîæåòå èñïîëüçîâàòü îáû÷íûé PHP åñëè âàì òàê áîëüøå íðàâèòñÿ.  ñëåäóþùèõ ãëàâàõ
    ìû ïîãîâîðèì î òîì êàê øàáëîíû ðàáîòàþò â Symfony2.
    1.3.4 Ïàêåòû (bundles)

    Âû äîëæíî áûòü óäèâëåíû, òåì ÷òî âèäèòå ñëîâî ïàêåò òàê ÷àñòî. Âåñü êîä âàøåãî
    ïðèëîæåíèÿ íàõîäèòñÿ â ïàêåòàõ. Â òåðìèíîëîãèè Symfony2 ïàêåò ïðåäñòàâëÿåò ñîáîé ñòðóêòóðèðîâàííûé íàáîð ôàéëîâ (ôàéëû PHP, ñòèëè, JavaScript'û, êàðòèíêè,
    ...) êîòîðûå âûïîëíÿþò îäíó ôóíêöèþ (áëîã, ôîðóì, ...) è êîòîðûìè ìîæíî ëåãêî
    ïîäåëèòüñÿ ñ äðóãèìè ðàçðàáîò÷èêàìè. Ïîêà ìû ðàáîòàëè òîëüêî ñ îäíèì ïàêåòîì AcmeDemoBundle. Âû óçíàåòå áîëüøå î ïàêåòàõ â ïîñëåäíåé ãëàâå äàííîãî óðîêà.

    1.4 Ðàáîòà ñ îêðóæåíèÿìè

    Ñåé÷àñ, êîãäà âû èìååòå ëó÷øåå ïîíèìàíèå ðàáîòû Symfony2, îáðàòèòå âíèìàíèå íà
    íèæíèþ ÷àñòü ñòðàíèöû; âû óâèäèòå ìàëåíüêóþ ïàíåëü ñ ëîãîòèïîì Symfony2. Îíà
    íàçûâàåòñÿ Âåá-ïàíåëüþ îòëàäêè (Web Debug Toolbar) è ýòî ëó÷øèé äðóã ðàçðàáîò÷èêà.

    12

    Ãëàâà 1. Îáùàÿ êàðòèíà

    Symfony Documentation, Âûïóñê 2.0

    Íî ýòà òîëüêî âåðøèíà àéñáåðãà; íàæìèòå íà ñòðàííîå øåñòíàäöàòåðè÷íîå ÷èñëî ÷òîáû
    îòêðûòü åùå îäèí î÷åíü ïîëåçíûé èíñòðóìåíò îòëàäêè: ïðîôàéëåð.

    Êîíå÷íî, âû íå õîòèòå âèäåòü ýòè èíñòðóìåíòû, êîãäà âû ðàçâåðòûâàåòå ïðèëîæåíèå
    íà ðàáî÷èé ñåðâåð. Âîò ïî÷åìó âû íàéäåòå åùå îäèí êîíòðîëëåð âõîäà â ïàïêå web/
    (app.php), îí îïòèìèçèðîâàí äëÿ ðàáîòû â ðàáî÷åì îêðóæåíèè:

    http://localhost/Symfony/web/app.php/demo/hello/Fabien
    È åñëè âû èñïîëüçóåòå Apache ñ âêëþ÷åííûì mod_rewrite, âû ìîæåòå îïóñòèòü ÷àñòü
    URL ñ app.php:

    1.4. Ðàáîòà ñ îêðóæåíèÿìè

    13

    Symfony Documentation, Âûïóñê 2.0

    http://localhost/Symfony/web/demo/hello/Fabien
    Íî ãîðàçäî ëó÷øå, íà ðàáî÷åì ñåðâåðå, óñòàíîâèòü êîðíåì âåá-ñåðâåðà ïàïêó web/,
    ÷òîáû çàùèòèòü ôàéëû è ñäåëàòü áîëåå êðàñèâûé URL:

    http://localhost/demo/hello/Fabien
    ×òîáû ñäåëàòü îòâåò ïðèëîæåíèå áûñòðûì, Symfony2 ñîõðàíÿåò êåø â ïàïêó
    app/cache/.  îêðóæåíèè ðàçðàáîòêè (app_dev.php), êýø î÷èùàåòñÿ àâòîìàòè÷åñêè,
    êîãäà âû ïðîèçâîäèòå êàêîå-ëèáî èçìåíåíèå â êîäå. Íî â ðàáî÷åì îêðóæåíèè (app.php),
    ãäå ïðîèçâîäèòåëüíîñòü ýòî ñàìîå âàæíîå, ýòîãî íå ïðîèñõîäèò. Âîò ïî÷åìó âû âñåãäà
    äîëæíû èñïîëüçîâàòü îêðóæåíèå ðàçðàáîòêè, êîãäà ñîçäàåòå âàøå ïðèëîæåíèå.
    Ðàçíûå îêðóæåíèÿ îäíîãî ïðèëîæåíèÿ ðàçëè÷àþòñÿ òîëüêî â ñâîåé êîíôèãóðàöèè. Íà
    ñàìîì äåëå, îäíà êîíôèãóðàöèÿ ìîæåò íàñëåäîâàòüñÿ îò äðóãîé:

    # app/cong/cong_dev.yml
    imports:
    - { resource: cong.yml }
    web_proler:
    toolbar: true
    intercept_redirects: false
    Îêðóæåíèå dev (çàäàííîå â cong_dev.yml) íàñëåäóåòñÿ îò ãëîáàëüíîãî ôàéëà
    cong.yml è ðàñøèðÿåò åãî, âêëþ÷àÿ âåá-ïàíåëü îòëàäêè.

    1.5 Çàêëþ÷èòåëüíîå ñëîâî

    Ïîçäðàâëÿþ! Âû ïî÷óâñòâîâàëè âêóñ êîäà Symfony2. Ýòî áûëî íå òÿæåëî, ïðàâäà? Âû
    äîëæíû óçíàòü åùå ìíîãîå, íî âû óæå ìîæåòå âèäåòü êàê Symfony2 ïîçâîëÿåò äåëàòü
    ñàéòû ëó÷øå è áûñòðåå. Åñëè âû õîòèòå óçíàòü áîëüøå î Symfony2 ïîãðóçèòåñü â ñëåäóþùóþ ãëàâó: Âèä.

    14

    Ãëàâà 1. Îáùàÿ êàðòèíà

    Ãëàâà 2

    Âèä
    Ïðî÷èòàâ ïåðâóþ ÷àñòü, âû ðåøèëè ÷òî Symfony2 çàñëóæèâàåò åù¼ 10 ìèíóò. Õîðîøî. Âî âòîðîé ÷àñòè âû óçíàåòå áîëüøå î äâèæêå øàáëîíîâ Twig â Symfony2. Twig ýòî
    ãèáêèé, áûñòðûé è áåçîïàñíûé øàáëîíèçàòîð äëÿ PHP. Îí äåëàåò øàáëîíû óäîáî÷èòàåìûìè è âûðàçèòåëüíûìè, à òàêæå áîëåå äðóæåñòâåííûìè äëÿ web äèçàéíåðîâ.
    Ïðèìå÷àíèå: Âìåñòî Twig, ìîæåòå èñïîëüçîâàòü äëÿ øàáëîíîâ PHP.Îáà øàáëîííûõ
    äâèæêà ïîääåðæèâàþòñÿ Symfony2.

    2.1 Twig, êðàòêèé îáçîð

    Ñîâåò: Åñëè õîòèòå èçó÷èòü Twig, ìû íàñòîÿòåëüíî ðåêîìåíäóåì ïðî÷åñòü ýòó îôèöèàëüíóþ äîêóìåíòàöèþ. Ýòîò ðàçäåë ëèøü êðàòêî îïèñûâàåò îñíîâíûå êîíöåïöèè.
    Øàáëîí Twig ýòî òåêñòîâûé ôàéë, êîòîðûé ìîæåò ãåíåðèðîâàòü ëþáîé ôîðìàò, îñíîâàííûé íà òåêñòå (HTML, XML, CSV, LaTeX, ...). Twig óñòàíàâëèâàåò äâà âèäà ðàçäåëèòåëåé:

    • {{ ... }}: Âûâîäèò ïåðåìåííóþ èëè ðåçóëüòàò âûðàæåíèÿ â øàáëîí;
    • {% ... %}: Òåã, óïðàâëÿþùèé ëîãèêîé øàáëîíà; íàïðèìåð, èñïîëüçóåòñÿ äëÿ âûïîëíåíèÿ for öèêëîâ èëè if óñëîâèé.
    Íèæå ïðèâåäåí ìèíèìàëüíûé øàáëîí, èëëþñòðèðóþùèé îñíîâû, èñïîëüçóþùèé äâå
    ïåðåìåííûå page_title è navigation, êîòîðûå ïåðåäàíû â øàáëîí:

    <!DOCTYPE html>
    <html>
    15

    Symfony Documentation, Âûïóñê 2.0

    <head>
    <title>My Webpage</title>
    </head>
    <body>
    <h1>{{ page_title }}</h1>
    <ul id="navigation">
    {% for item in navigation %}
    <li><a href="{{ item.href }}">{{ item.caption }}</a></li>
    {% endfor %}
    </ul>
    </body>
    </html>
    Ñîâåò: Êîììåíòàðèè ìîãóò áûòü âêëþ÷åíû â øàáëîí èñïîëüçóÿ ðàçäåëèòåëü {# ... #}.
    Äëÿ òîãî, ÷òîáû îòîáðàçèòü øàáëîí â Symfony èñïîëüçóéòå ìåòîä render èç êîíòðîëëåðà
    è ïåðåäàéòå ëþáûå ïåðåìåííûå, íóæíûå âàì â øàáëîíå:

    $this->render('AcmeDemoBundle:Demo:hello.html.twig', array(
    'name' => $name,
    ));
    Ïåðåìåííûå, ïåðåäàííûå â øàáëîí, ìîãóò áûòü ñòðîêàìè, ìàññèâàìè èëè äàæå îáúåêòàìè. Twig àáñòðàãèðóåò ðàçíèöó ìåæäó íèìè è äà¼ò âàì äîñòóï ê àòðèáóòàì ïåðåìåííîé, îáîçíà÷åííûì ÷åðåç òî÷êó (.):

    {# array('name' => 'Fabien') #}
    {{ name }}
    {# array('user' => array('name' => 'Fabien')) #}
    {{ user.name }}
    {# force array lookup #}
    {{ user['name'] }}
    {# array('user' => new User('Fabien')) #}
    {{ user.name }}
    {{ user.getName }}
    {# force method name lookup #}
    {{ user.name() }}
    {{ user.getName() }}
    {# pass arguments to a method #}
    {{ user.date('Y-m-d') }}
    16

    Ãëàâà 2. Âèä

    Symfony Documentation, Âûïóñê 2.0

    Ïðèìå÷àíèå: Âàæíî çíàòü ÷òî ôèãóðíûå ñêîáêè ýòî íå ÷àñòü ïåðåìåííîé, à îïåðàòîð
    ïå÷àòè. Åñëè âû èñïîëüçóåòå ïåðåìåííûå âíóòðè òåãîâ, íå ñòàâüòå ñêîáêè âîêðóã íèõ.

    2.2 Äåêîðèðîâàíèå øàáëîíîâ

    ×àñòî øàáëîíû â ïðîåêòå ðàçäåëÿþò îáùèå ýëåìåíòû, òàêèå êàê âñåì èçâåñòíûå header
    è footer.  Symfony2, ìû ñìîòðèì íà ýòó ïðîáëåìó èíà÷å: îäèí øàáëîí ìîæåò áûòü
    äåêîðèðîâàí äðóãèì. Ýòî ïîõîæå íà êëàññû â PHP: íàñëåäîâàíèå øàáëîíà ïîçâîëÿåò
    ñîçäàòü åãî áàçîâûé ìàêåò, ñîäåðæàùèé îáùèå ýëåìåíòû âàøåãî ñàéòà è óñòàíàâëèâàþùèé áëîêè, êîòîðûå ìîãóò áûòü ïåðåîïðåäåëåíû äî÷åðíèìè øàáëîíàìè.
    Øàáëîí hello.html.twig íàñëåäóåòñÿ îò layout.html.twig áëàãîäàðÿ òåãó extends:

    {# src/Acme/DemoBundle/Resources/views/Demo/hello.html.twig #}
    {% extends "AcmeDemoBundle::layout.html.twig" %}
    {% block title "Hello " ~ name %}
    {% block content %}
    <h1>Hello {{ name }}!</h1>
    {% endblock %}
    Îáîçíà÷åíèå AcmeDemoBundle::layout.html.twig âûãëÿäèò çíàêîìî, íå òàê ëè? Îáîçíà÷àåòñÿ òàê æå êàê ññûëêà íà îáû÷íûé øàáëîí. Ýòà ÷àñòü :: âñåãî ëèøü îáîçíà÷àåò ÷òî
    êîíòðîëëåð íå óêàçàí, ò.î. ñîîòâåñòâóþùèé ôàéë õðàíèòñÿ ïðÿìî â Resources/views/.
    Ðàññìîòðèì ôàéë layout.html.twig:

    {# src/Acme/DemoBundle/Resources/views/layout.html.twig #}
    <div class="symfony-content">
    {% block content %}
    {% endblock %}
    </div>
    Òåã {% block %} óñòàíàâëèâàåò äâà áëîêà (body è content), êîòîðûå äî÷åðíèå øàáëîíû ñìîãóò çàïîëíèòü. Âñ¼ ÷òî äåëàåò ýòîò òåã, ýòî ñîîáùàåò äâèæêó øàáëîíîâ, ÷òî
    äî÷åðíèé øàáëîí ìîæåò ïåðåîïðåäåëèòü ýòè ó÷àñòêè.
    Øàáëîí hello.html.twig ïåðåîïðåäåëÿåò áëîê content, ýòî çíà÷èò, ÷òî òåêñò Hello Fabien
    áóäåò îòîáðàæåí âíóòðè ýëåìåíòà div.symfony-content.

    2.2. Äåêîðèðîâàíèå øàáëîíîâ

    17

    Symfony Documentation, Âûïóñê 2.0

    2.3 Òåãè, ôèëüòðû è ôóíêöèè

    Îäíà èç ëó÷øèõ îñîáåííîñòåé Twig åãî ðàñøèðÿåìîñòü ÷åðåç òåãè, ôèëüòðû è ôóíêöèè;
    Ìíîãèå èç íèõ ïîñòàâëÿåòñÿ âìåñòå ñ Symfony2, îáëåã÷àÿ ðàáîòó web äèçàéíåðà.
    2.3.1 Âêëþ÷åíèå äðóãèõ øàáëîíîâ

    Ëó÷øèé ñïîñîá ðàñïðåäåëèòü ôðàãìåíò êîäà ìåæäó íåñêîëüêèìè ðàçëè÷íûìè øàáëîíàìè ýòî îïðåäåëèòü øàáëîí, ïîäêëþ÷àåìûé â äðóãèå.
    Ñîçäàéòå øàáëîí embedded.html.twig:

    {# src/Acme/DemoBundle/Resources/views/Demo/embedded.html.twig #}
    Hello {{ name }}
    Èçìåíèòå øàáëîí index.html.twig òàêèì îáðàçîì, ÷òîáû ïîäêëþ÷èòü åãî:

    {# src/Acme/DemoBundle/Resources/views/Demo/hello.html.twig #}
    {% extends "AcmeDemoBundle::layout.html.twig" %}
    {# override the body block from embedded.html.twig #}
    {% block content %}
    {% include "AcmeDemoBundle:Demo:embedded.html.twig" %}
    {% endblock %}

    2.3.2 Âëîæåíèå äðóãèõ êîíòðîëëåðîâ

    ×òî åñëè âû çàõîòèòå âëîæèòü ðåçóëüòàò äðóãîãî êîíòðîëëåðà â øàáëîí? Ýòî î÷åíü
    óäîáíî êîãäà ðàáîòàåøü ñ Ajax èëè êîãäà âñòðîåííîìó øàáëîíó íåîáõîäèìû ïåðåìåííûå, êîòîðûå íå äîñòóïíû â ãëàâíîì øàáëîíå.
    Åñëè âû ñîçäàëè äåéñòâèå fancy è õîòèòå âêëþ÷èòü åãî â øàáëîí index, èñïîëüçóéòå òåã
    render:

    {# src/Acme/DemoBundle/Resources/views/Demo/index.html.twig #}
    {% render "AcmeDemoBundle:Demo:fancy" with { 'name': name, 'color': 'green' } %}
    Èìååì ñòðîêó HelloBundle:Hello:fancy, îáðàùàþùóþñÿ ê äåéñòâèþ fancy êîíòðîëëåðà
    Hello è àðãóìåíò, èñïîëüçóåìûé äëÿ èìèòèðîâàíèÿ çàïðîñà äëÿ çàäàííîãî ïóòè:

    // src/Acme/DemoBundle/Controller/DemoController.php
    class DemoController extends Controller
    {
    public function fancyAction($name, $color)
    18

    Ãëàâà 2. Âèä

    Symfony Documentation, Âûïóñê 2.0

    {

    }
    }

    // create some object, based on the $color variable
    $object = ...;

    return $this->render('AcmeDemoBundle:Demo:fancy.html.twig', array('name' => $name, 'object' => $obje

    // ...

    2.3.3 Ñîçäàíèå ññûëîê ìåæäó ñòðàíèöàìè

    Ãîâîðÿ î web ïðèëîæåíèÿõ, íåëüçÿ íå óïîìÿíóòü î ññûëêàõ. Âìåñòî æ¼ñòêèõ URLîâ â øàáëîíàõ, ôóíêöèÿ path ïîìîæåò ñäåëàòü URL-û, îñíîâàííûå íà êîíôèãóðàöèè
    ìàðøðóòèçàòîðà. Òàêèì îáðàçîì URL-û ìîãóò áûòü ëåãêî îáíîâëåíû, åñëè èçìåíèòü
    êîíôèãóðàöèþ:

    <a href="{{ path('_demo_hello', { 'name': 'Thomas' }) }}">Greet Thomas!</a>
    Ôóíêöèÿ path èñïîëüçóåò èìÿ ìàðøðóòà è ìàññèâ ïàðàìåòðîâ êàê àðãóìåíòû. Èìÿ
    ìàðøðóòà ýòî îñíîâà, â ñîîòâåñòâèè ñ êîòîðîé âûáèðàþòñÿ ìàðøðóòû, à ïàðàìåòðû ýòî
    çíà÷åíèÿ çàïîëíèòåëåé, îáúÿâëåííûõ â ïàòòåðíå ìàðøðóòà:

    // src/Acme/DemoBundle/Controller/DemoController.php
    use Sensio\Bundle\FrameworkExtraBundle\Conguration\Route;
    use Sensio\Bundle\FrameworkExtraBundle\Conguration\Template;
    /**
    * @Route("/hello/{name}", name="_demo_hello")
    * @Template()
    */
    public function helloAction($name)
    {
    return array('name' => $name);
    }
    Ñîâåò: Ôóíêöèÿ url ñîçäàåò àáñîëþòíûå URL-û: {{ url('_demo_hello', { 'name':
    'Thomas' }) }}.

    2.3.4 Ïîäêëþ÷åíèå àêòèâîâ: èçîáðàæåíèé, JavaScript-îâ è òàáëèö ñòèëåé

    Êàê âûãëÿäåë áû èíòåðíåò áåç èçîáðàæåíèé, JavaScript-îâ è òàáëèö ñòèëåé? Symfony2
    ïðåäëàãàåò ôóíêöèþ asset äëÿ ðàáîòû ñ íèìè:
    2.3. Òåãè, ôèëüòðû è ôóíêöèè

    19

    Symfony Documentation, Âûïóñê 2.0

    <link href="{{ asset('css/blog.css') }}" rel="stylesheet" type="text/css" />
    <img src="{{ asset('images/logo.png') }}" />
    Îñíîâíàÿ öåëü ôóíêöèè asset ñäåëàòü ïðèëîæåíèå áîëåå ïåðåíîñèìûì. Áëàãîäàðÿ åé,
    ìîæíî ïåðåìåñòèòü êîðíåâóþ ïàïêó ïðèëîæåíèÿ êóäà óãîäíî âíóòðè âàøåé êîðíåâîé
    web äèðåêòîðèè áåç èçìåíåíèÿ øàáëîíà.

    2.4 Ýêðàíèðîâàíèå ïåðåìåííûõ

    Èçíà÷àëüíî Twig íàñòðîåí ýêðàíèðîâàòü âåñü âûâîä. Ïðî÷òèòå Twig documentation ÷òîáû óçíàòü áîëüøå îá ýêðàíèðîâàíèè è ðàñøèðåíèè Escaper.

    2.5 Çàêëþ÷èòåëüíîå ñëîâî

    Twig ïðîñòîé è ìîùíûé. Áëàãîäàðÿ ìàêåòàì, áëîêàì, øàáëîíàì è âíåäðåíèÿì äåéñòâèé,
    ñòàíîâèòñÿ äåéñòâèòåëüíî ïðîñòî îðãàíèçîâàòü âàøè øàáëîíû ëîãè÷åñêè è ñäåëàòü èõ
    ðàñøèðÿåìûìè.
    Ïðîðàáîòàâ ñ Symfony2 îêîëî 20 ìèíóò, âû óæå ìîæåòå äåëàòü óäèâèòåëüíûå âåùè. Â
    ýòîì ñèëà Symfony2. Èçó÷àòü îñíîâû ëåãêî, âñêîðå âû óçíàåòå ÷òî ýòà ïðîñòîòà ñêðûòà
    â î÷åíü ãèáêîé àðõèòåêòóðå.
    ß íåìíîãî ïîñïåøèë. Âî-ïåðâûõ, âû äîëæíû óçíàòü áîëüøå î êîíòðîëëåðå, èìåííî îí
    ñòàíåò òåìîé ñëåäóþùåé ÷àñòè ó÷åáíèêà. Ãîòîâû ê ñëåäóþùèì 10 ìèíóòàì ñ Symfony2?

    20

    Ãëàâà 2. Âèä

    Ãëàâà 3

    Êîíòðîëëåð
    Âñ¼ åù¼ ñ íàìè ïîñëå ïåðâûõ äâóõ ÷àñòåé? Âû ñòàíîâèòåñü ÿðûì ïðèâåðæåíöåì
    Symfony2! Äàâàéòå, áåç ëèøíåé ñóåòû, óçíàåì ÷òî êîíòðîëëåðû ìîãóò ñäåëàòü äëÿ âàñ.

    3.1 Èñïîëüçîâàíèå ôîðìàòîâ

     íàøè äíè, web ïðèëîæåíèå äîëæíî óìåòü âûäàâàòü íå òîëüêî HTML ñòðàíèöû. Íà÷èíàÿ ñ XML äëÿ RSS êàíàëîâ è Web ñëóæá, çàêàí÷èâàÿ JSON äëÿ Ajax çàïðîñîâ,
    ñóùåñòâóåò ìíîæåñòâî ðàçëè÷íûõ ôîðìàòîâ. Ïîääåðæêà ýòèõ ôîðìàòîâ â Symfony2
    ïðîñòà. Èçìåíèòå ìàðøðóòû äîáàâèâ çíàíà÷åíèå ïî-óìîë÷àíèþ xml äëÿ ïåðåìåííîé
    _format:

    // src/Acme/DemoBundle/Controller/DemoController.php
    use Sensio\Bundle\FrameworkExtraBundle\Conguration\Route;
    use Sensio\Bundle\FrameworkExtraBundle\Conguration\Template;
    /**
    * @Route("/hello/{name}", defaults={"_format"="xml"}, name="_demo_hello")
    * @Template()
    */
    public function helloAction($name)
    {
    return array('name' => $name);
    }
    By using the request format (as dened by the _format value), Symfony2 automatically
    selects the right template, here hello.xml.twig:

    <!-- src/Acme/DemoBundle/Resources/views/Demo/hello.xml.twig -->
    <hello>
    21

    Symfony Documentation, Âûïóñê 2.0

    <name>{{ name }}</name>
    </hello>
    Âîò è âñ¼ ÷òî äëÿ ýòîãî íóæíî. Äëÿ ñòàíäàðòíûõ ôîðìàòîâ Symfony2 àâòîìàòè÷åñêè
    ïîäáèðàåò çàãîëîâîê Content-Type äëÿ îòâåòà. Åñëè õîòèòå ïîääåðæêó ôîðìàòîâ ëèøü
    äëÿ îäíîãî äåéñòâèÿ, òîãäà èñïîëüçóéòå çàïîëíèòåëü {_format} â ïàòòåðíå:

    // src/Acme/DemoBundle/Controller/DemoController.php
    use Sensio\Bundle\FrameworkExtraBundle\Conguration\Route;
    use Sensio\Bundle\FrameworkExtraBundle\Conguration\Template;

    /**
    * @Route("/hello/{name}.{_format}", defaults={"_format"="html"}, requirements={"_format"="html|xml|js
    * @Template()
    */
    public function helloAction($name)
    {
    return array('name' => $name);
    }
    The controller will now
    /demo/hello/Fabien.json.

    be

    called

    for

    URLs

    like

    /demo/hello/Fabien.xml

    or

    The requirements entry denes regular expressions that placeholders must match. In this
    example, if you try to request the /demo/hello/Fabien.js resource, you will get a 404 HTTP
    error, as it does not match the _format requirement.

    3.2 Ïåðåìåùåíèÿ è ïåðåíàïðàâëåíèÿ

    Åñëè âû õîòèòå ïåðåìåñòèòü ïîëüçîâàòåëÿ íà äðóãóþ ñòðàíèöó, èñïîëüçóéòå ìåòîä
    redirect():

    return $this->redirect($this->generateUrl('_demo_hello', array('name' => 'Lucas')));
    The generateUrl() is the same method as the path() function we used in templates. It takes
    the route name and an array of parameters as arguments and returns the associated friendly
    URL.
    Òàêæå âû ìîæåòå ëåãêî ïåðåìåñòèòü îäíî äåéñòâèå íà äðóãîå ñ ïîìîùüþ ìåòîäà
    forward(). Êàê è äëÿ õåëïåðà actions, îí ïðèìåíÿåò âíóòðåííèé ïîäçàïðîñ, íî âîçâðàùàåò îáúåêò Response, ÷òî ïîçâîëÿåò â äàëüíåéøåì åãî èçìåíèòü:

    $response = $this->forward('AcmeDemoBundle:Hello:fancy', array('name' => $name, 'color' => 'green'));
    // do something with the response or return it directly

    22

    Ãëàâà 3. Êîíòðîëëåð

    Symfony Documentation, Âûïóñê 2.0

    3.3 Ïîëó÷åíèå èíôîðìàöèè î çàïðîñå

    Ïîìèìî çíà÷åíèé çàïîëíèòåëåé äëÿ ìàðøðóòèçàöèè, êîíòðîëëåð èìååò äîñòóï ê îáúåêòó Request:

    $request = $this->getRequest();
    $request->isXmlHttpRequest(); // is it an Ajax request?
    $request->getPreferredLanguage(array('en', 'fr'));
    $request->query->get('page'); // get a $_GET parameter
    $request->request->get('page'); // get a $_POST parameter
     øàáëîíå ïîëó÷èòü äîñòóï ê îáúåêòó Request ìîæíî ÷åðåç ïåðåìåííóþ app.request:

    {{ app.request.query.get('page') }}
    {{ app.request.parameter('page') }}

    3.4 Ñîõðàíåíèå è ïîëó÷åíèå èíôîðìàöèè èç ñåññèè

    Ïðîòîêîë HTTP íå èìååò ñîñòîÿíèé, íî Symfony2 ïðåäîñòàâëÿåò óäîáíûé îáúåêò ñèññèè, êîòîðûé ïðåäñòàâëÿåò êëèåíòà (áóäü îí ÷åëîâåêîì, èñïîëüçóþùèì áðàóçåð, áîòîì
    èëè web ñëóæáîé). Ìåæäó äâóìÿ çàïðîñàìè Symfony2 õðàíèò àòðèáóòû â cookie, èñïîëüçóÿ ðîäíûå ñåññèè èç PHP.
    Ñîõðàíåíèå è ïîëó÷åíèå èíôîðìàöèè èç ñåññèè ëåãêî âûïîëíÿåòñÿ èç ëþáîãî êîíòðîëëåðà:

    $session = $this->getRequest()->getSession();
    // store an attribute for reuse during a later user request
    $session->set('foo', 'bar');
    // in another controller for another request
    $foo = $session->get('foo');
    // set the user locale
    $session->setLocale('fr');
    Òàêæå ìîæíî õðàíèòü íåáîëüøèå ñîîáùåíèÿ, êîòîðûå áóäóò äîñòóïíû äëÿ ñëåäóþùåãî
    çàïðîñà:

    3.3. Ïîëó÷åíèå èíôîðìàöèè î çàïðîñå

    23

    Symfony Documentation, Âûïóñê 2.0

    // store a message for the very next request (in a controller)
    $session->setFlash('notice', 'Congratulations, your action succeeded!');
    // display the message back in the next request (in a template)
    {{ app.session.ash('notice') }}
    This is useful when you need to set a success message before redirecting the user to another
    page (which will then show the message).

    3.5 Securing Resources

    The Symfony Standard Edition comes with a simple security conguration that ts most
    common needs:

    # app/cong/security.yml
    security:
    encoders:
    Symfony\Component\Security\Core\User\User: plaintext
    role_hierarchy:
    ROLE_ADMIN:
    ROLE_USER
    ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
    providers:
    in_memory:
    users:
    user: { password: userpass, roles: [ 'ROLE_USER' ] }
    admin: { password: adminpass, roles: [ 'ROLE_ADMIN' ] }
    rewalls:
    dev:
    pattern: ^/(_(proler|wdt)|css|images|js)/
    security: false
    login:
    pattern: ^/demo/secured/login$
    security: false
    secured_area:
    pattern: ^/demo/secured/
    form_login:
    check_path: /demo/secured/login_check
    login_path: /demo/secured/login
    logout:
    path: /demo/secured/logout
    24

    Ãëàâà 3. Êîíòðîëëåð

    Symfony Documentation, Âûïóñê 2.0

    target: /demo/
    This conguration requires users to log in for any URL starting with /demo/secured/ and
    denes two valid users: user and admin. Moreover, the admin user has a ROLE_ADMIN
    role, which includes the ROLE_USER role as well (see the role_hierarchy setting).
    Ñîâåò: For readability, passwords are stored in clear text in this simple conguration, but
    you can use any hashing algorithm by tweaking the encoders section.
    Going to the http://localhost/Symfony/web/app_dev.php/demo/secured/hello URL will
    automatically redirect you to the login form because this resource is protected by a rewall.
    You can also force the action to require a given role by using the @Secure annotation on the
    controller:

    use Sensio\Bundle\FrameworkExtraBundle\Conguration\Route;
    use Sensio\Bundle\FrameworkExtraBundle\Conguration\Template;
    use JMS\SecurityExtraBundle\Annotation\Secure;
    /**
    * @Route("/hello/admin/{name}", name="_demo_secured_hello_admin")
    * @Secure(roles="ROLE_ADMIN")
    * @Template()
    */
    public function helloAdminAction($name)
    {
    return array('name' => $name);
    }
    Now, log in as user (who does not have the ROLE_ADMIN role) and from the secured hello
    page, click on the Hello resource secured link. Symfony2 should return a 403 HTTP status
    code, indicating that the user is forbidden from accessing that resource.
    Ïðèìå÷àíèå: The Symfony2 security layer is very exible and comes with many dierent
    user providers (like one for the Doctrine ORM) and authentication providers (like HTTP
    basic, HTTP digest, or X509 certicates). Read the Security chapter of the book for more
    information on how to use and congure them.

    3.6 Caching Resources

    As soon as your website starts to generate more trac, you will want to avoid generating
    the same resource again and again. Symfony2 uses HTTP cache headers to manage resources
    cache. For simple caching strategies, use the convenient @Cache() annotation:
    3.6. Caching Resources

    25

    Symfony Documentation, Âûïóñê 2.0

    use Sensio\Bundle\FrameworkExtraBundle\Conguration\Route;
    use Sensio\Bundle\FrameworkExtraBundle\Conguration\Template;
    use Sensio\Bundle\FrameworkExtraBundle\Conguration\Cache;
    /**
    * @Route("/hello/{name}", name="_demo_hello")
    * @Template()
    * @Cache(maxage="86400")
    */
    public function helloAction($name)
    {
    return array('name' => $name);
    }
    In this example, the resource will be cached for a day. But you can also use validation instead
    of expiration or a combination of both if that ts your needs better.
    Resource caching is managed by the Symfony2 built-in reverse proxy. But because caching
    is managed using regular HTTP cache headers, you can replace the built-in reverse proxy
    with Varnish or Squid and easily scale your application.
    Ïðèìå÷àíèå: But what if you cannot cache whole pages? Symfony2 still has the solution via
    Edge Side Includes (ESI), which are supported natively. Learn more by reading the HTTP
    Cache chapter of the book.

    3.7 Çàêëþ÷èòåëüíîå ñëîâî

    Âîò è âñ¼ ÷òî õîòåëîñü ðàññêàçàòü, è ÿ äàæå óâåðåí, ÷òî ìû íå èñïîëüçîâàëè âñå îòâåä¼ííûå 10 ìèíóò. Ìû êîðîòêî ðàññìîòðåëè áàíäëû â ïåðâîé ÷àñòè, è âñå îñîáåííîñòè
    î êîòîðûõ ìû óçíàëè ÿâëÿþòñÿ ÷àñòüþ áàíäëîâ ÿäðà ôðåéìâîðêà. Íî áëàãîäàðÿ áàíäëàì, â Symfony2 âñ¼ ìîæåò áûòü ðàñøèðåíî èëè çàìåíåíî. Ýòî è åñòü òåìà ñëåäóþùåé
    ÷àñòè ðóêîâîäñòâà.

    26

    Ãëàâà 3. Êîíòðîëëåð

    Ãëàâà 4

    Àðõèòåêòóðà
    Âû ìîé ãåðîé! Êòî áû ìîã ïîäóìàòü, ÷òî âû âñå åùå áóäåòå çäåñü ïîñëå ïåðâûõ òðåõ
    ÷àñòåé? Âàøè óñèëèÿ ñêîðî áóäóò âîçíàãðàæäåíû.  ïåðâûõ ÷àñòÿõ ìû ãëóáîêî íå
    ðàññìàòðèâàëè àðõèòåêòóðó ôðåéìâîðêà. Ïîñêîëüêó ýòî îäíà èç îòëè÷èòåëüíûõ îñîáåííîñòåé Symfony, äàâàéòå-êà îñòàíîâèìñÿ íà ýòîì ïîäðîáíåå.

    4.1 Ñòðóêòóðà ïàïîê

    Ñòðóêòóðà äèðåêòîðèé ïðèëîæåíèÿ î÷åíü ãèáêàÿ, but the directory structure of the
    Standard Edition distribution reects the typical and recommended structure of a Symfony2
    application:

    • app/: Ýòà ïàïêà ñîäåðæèò êîíôèãóðàöèþ ïðèëîæåíèÿ;
    • src/: Âåñü PHP êîä õðàíèòñÿ çäåñü;
    • vendor/: The third-party dependencies;
    • web/: Ýòà ïàïêà äîëæíà áûòü êîðíåâîé web äèðåêòîðèåé.
    4.1.1 Êàòàëîã web/

    Êîðíåâàÿ web äèðåêòîðèÿ - ýòî äîì äëÿ âñåõ ïóáëè÷íûõ è ñòàòè÷íûõ ôàéëîâ, òàêèõ êàê
    èçîáðàæåíèÿ, òàáëèöû ñòèëåé è ôàéëû JavaScript. Çäåñü òàêæå îáèòàåò front controller:

    // web/app.php
    require_once __DIR__.'/../app/bootstrap.php.cache';
    require_once __DIR__.'/../app/AppKernel.php';
    use Symfony\Component\HttpFoundation\Request;
    27

    Symfony Documentation, Âûïóñê 2.0

    $kernel = new AppKernel('prod', false);
    $kernel->loadClassCache();
    $kernel->handle(Request::createFromGlobals())->send();
    The kernel rst requires the bootstrap.php.cache le, which bootstraps the framework and
    registers the autoloader (see below).
    Like any front controller, app.php uses a Kernel Class, AppKernel, to bootstrap the
    application.
    4.1.2 Ïàïêà app/

    Êëàññ AppKernel ýòî ãëàâíàÿ âõîäíàÿ òî÷êà êîíôèãóðàöèè ïðèëîæåíèÿ, ïîýòîìó îí
    ñîäåðæèòñÿ â äèðåêòîðèè app/.
    Ýòîò êëàññ äîëæåí ðåàëèçîâûâàòü äâà ìåòîäà:

    • registerBundles() âîçâðàùàåò ìàññèâ âñåõ áàíäëîâ, íåîáõîäèìûõ äëÿ çàïóñêà ïðèëîæåíèÿ;
    • registerContainerConguration() çàãðóæàåò êîíôèãóðàöèþ (îá ýòîì ÷óòü ïîçæå);
    PHP autoloading can be congured via app/autoload.php:

    // app/autoload.php
    use Symfony\Component\ClassLoader\UniversalClassLoader;
    $loader = new UniversalClassLoader();
    $loader->registerNamespaces(array(
    'Symfony'
    => array(__DIR__.'/../vendor/symfony/src', __DIR__.'/../vendor/bundles'),
    'Sensio'
    => __DIR__.'/../vendor/bundles',
    'JMS'
    => __DIR__.'/../vendor/bundles',
    'Doctrine\\Common' => __DIR__.'/../vendor/doctrine-common/lib',
    'Doctrine\\DBAL' => __DIR__.'/../vendor/doctrine-dbal/lib',
    'Doctrine'
    => __DIR__.'/../vendor/doctrine/lib',
    'Monolog'
    => __DIR__.'/../vendor/monolog/src',
    'Assetic'
    => __DIR__.'/../vendor/assetic/src',
    'Metadata'
    => __DIR__.'/../vendor/metadata/src',
    ));
    $loader->registerPrexes(array(
    'Twig_Extensions_' => __DIR__.'/../vendor/twig-extensions/lib',
    'Twig_'
    => __DIR__.'/../vendor/twig/lib',
    ));
    // ...
    $loader->registerNamespaceFallbacks(array(
    28

    Ãëàâà 4. Àðõèòåêòóðà

    Symfony Documentation, Âûïóñê 2.0

    __DIR__.'/../src',
    ));
    $loader->register();
    The Symfony\Component\ClassLoader\UniversalClassLoader is used to autoload les that
    respect either the technical interoperability standards for PHP 5.3 namespaces or the PEAR
    naming convention for classes. As you can see here, all dependencies are stored under the
    vendor/ directory, but this is just a convention. You can store them wherever you want,
    globally on your server or locally in your projects.
    Ïðèìå÷àíèå: If you want to learn more about the exibility of the Symfony2 autoloader,
    read the How to autoload Classes recipe in the cookbook.

    4.2 Ñèñòåìà áàíäëîâ

    Ýòîò ðàçäåë êðàòêî ïîâåäàåò âàì îá îäíîé èç ñóùåñòâåííåéøèõ è íàèáîëåå ìîùíûõ
    îñîáåííîñòåé Symfony2, î ñèñòåìå ïàêåòîâ.
    Áàíäë â íåêîòîðîì ðîäå êàê ïëàãèí â äðóãèõ ïðîãðàììàõ. Ïî÷åìó åãî íàçâàëè áàíäë, à
    íå ïëàãèí? Ïîòîìó ÷òî âñ¼ ÷òî óãîäíî â Symfony2 ýòî áàíäë, îò êëþ÷åâûõ îñîáåííîñòåé
    ôðåéìâîðêà äî êîäà, êîòîðûé âû ïèøåòå äëÿ ïðèëîæåíèÿ. Áàíäëû ýòî âûñøàÿ êàñòà
    â Symfony2. Ýòî äà¼ò âàì ãèáêîñòü â ïðèìåíåíèè êàê óæå âñòðîåííûõ îñîáåííîñòåé
    ñòîðîííèõ áàíäëîâ, òàê è â íàïèñàíèè ñâîèõ ñîáñòâåííûõ. Áàíäë ïîçâîëÿåò âûáðàòü
    íåîáõîäèìûå äëÿ ïðèëîæåíèÿ îñîáåííîñòè è îïòèìèçèðîâàòü èõ êàê âû ýòîãî õîòèòå.
    4.2.1 Registering a Bundle

    An application is made up of bundles as dened in the registerBundles() method of the
    AppKernel class. Each bundle is a directory that contains a single Bundle class that describes
    it:

    // app/AppKernel.php
    public function registerBundles()
    {
    $bundles = array(
    new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
    new Symfony\Bundle\SecurityBundle\SecurityBundle(),
    new Symfony\Bundle\TwigBundle\TwigBundle(),
    new Symfony\Bundle\MonologBundle\MonologBundle(),
    new Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle(),
    new Symfony\Bundle\DoctrineBundle\DoctrineBundle(),
    new Symfony\Bundle\AsseticBundle\AsseticBundle(),
    4.2. Ñèñòåìà áàíäëîâ

    29

    Symfony Documentation, Âûïóñê 2.0

    );

    new Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(),
    new JMS\SecurityExtraBundle\JMSSecurityExtraBundle(),

    if (in_array($this->getEnvironment(), array('dev', 'test'))) {
    $bundles[] = new Acme\DemoBundle\AcmeDemoBundle();
    $bundles[] = new Symfony\Bundle\WebProlerBundle\WebProlerBundle();
    $bundles[] = new Sensio\Bundle\DistributionBundle\SensioDistributionBundle();
    $bundles[] = new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle();
    }
    }

    return $bundles;

    In addition to the AcmeDemoBundle that we have already talked about, notice that
    the kernel also enables other bundles such as the FrameworkBundle, DoctrineBundle,
    SwiftmailerBundle, and AsseticBundle bundle. They are all part of the core framework.
    4.2.2 Conguring a Bundle

    Êàæäûé áàíäë ìîæåò áûòü íàñòðîåí ïðè ïîìîùè êîíôèãóðàöèîííûõ ôàéëîâ, íàïèñàííûõ íà YAML, XML, èëè PHP. Âçãëÿíèòå íà êîíôèãóðàöèþ ïî óìîë÷àíèþ:

    # app/cong/cong.yml
    imports:
    - { resource: parameters.ini }
    - { resource: security.yml }
    framework:
    secret:
    %secret%
    charset:
    UTF-8
    router:
    { resource: "%kernel.root_dir%/cong/routing.yml" }
    form:
    true
    csrf_protection: true
    validation:
    { enable_annotations: true }
    templating:
    { engines: ['twig'] } #assets_version: SomeVersionScheme
    session:
    default_locale: %locale%
    auto_start: true
    # Twig Conguration
    twig:
    debug:
    %kernel.debug%
    strict_variables: %kernel.debug%
    # Assetic Conguration
    30

    Ãëàâà 4. Àðõèòåêòóðà

    Symfony Documentation, Âûïóñê 2.0

    assetic:
    debug:
    %kernel.debug%
    use_controller: false
    lters:
    cssrewrite: ~
    # closure:
    # jar: %kernel.root_dir%/java/compiler.jar
    # yui_css:
    # jar: %kernel.root_dir%/java/yuicompressor-2.4.2.jar
    # Doctrine Conguration
    doctrine:
    dbal:
    driver: %database_driver%
    host: %database_host%
    dbname: %database_name%
    user: %database_user%
    password: %database_password%
    charset: UTF8
    orm:
    auto_generate_proxy_classes: %kernel.debug%
    auto_mapping: true
    # Swiftmailer Conguration
    swiftmailer:
    transport: %mailer_transport%
    host:
    %mailer_host%
    username: %mailer_user%
    password: %mailer_password%
    jms_security_extra:
    secure_controllers: true
    secure_all_services: false
    Each entry like framework denes the conguration for a specic bundle. For
    example, framework congures the FrameworkBundle while swiftmailer congures the
    SwiftmailerBundle.
    Êàæäîå îêðóæåíèå (environment) ìîæåò ïåðåîïðåäåëÿòü ñòàíäàðòíóþ êîíôèãóðàöèþ,
    çàäàâàÿ ñïåöèôè÷íûé êîíôèãóðàöèîííûé ôàéë. For example, the dev environment loads
    the cong_dev.yml le, which loads the main conguration (i.e. cong.yml) and then
    modies it to add some debugging tools:

    # app/cong/cong_dev.yml
    imports:
    - { resource: cong.yml }
    4.2. Ñèñòåìà áàíäëîâ

    31

    Symfony Documentation, Âûïóñê 2.0

    framework:
    router: { resource: "%kernel.root_dir%/cong/routing_dev.yml" }
    proler: { only_exceptions: false }
    web_proler:
    toolbar: true
    intercept_redirects: false
    monolog:
    handlers:
    main:
    type: stream
    path: %kernel.logs_dir%/%kernel.environment%.log
    level: debug
    rephp:
    type: rephp
    level: info
    assetic:
    use_controller: true

    4.2.3 Extending a Bundle

    In addition to being a nice way to organize and congure your code, a bundle can extend
    another bundle. Bundle inheritance allows you to override any existing bundle in order to
    customize its controllers, templates, or any of its les. This is where the logical names (e.g.
    @AcmeDemoBundle/Controller/SecuredController.php) come in handy: they abstract where
    the resource is actually stored.
    Logical File Names
    When you want to reference a le from a bundle, use this notation:
    @BUNDLE_NAME/path/to/le;
    Symfony2
    will
    resolve
    @BUNDLE_NAME
    to
    the
    real
    path
    to
    the
    bundle.
    For
    instance,
    the
    logical
    path
    @AcmeDemoBundle/Controller/DemoController.php
    would
    be
    converted
    to
    src/Acme/DemoBundle/Controller/DemoController.php, because Symfony knows the
    location of the AcmeDemoBundle.
    Logical Controller Names
    For controllers, you need to reference method names
    BUNDLE_NAME:CONTROLLER_NAME:ACTION_NAME.
    32

    using
    For

    the

    format
    instance,

    Ãëàâà 4. Àðõèòåêòóðà

    Symfony Documentation, Âûïóñê 2.0
    AcmeDemoBundle:Welcome:index maps to the indexAction
    Acme\DemoBundle\Controller\WelcomeController class.

    method

    from

    the

    Logical Template Names
    For templates, the logical name AcmeDemoBundle:Welcome:index.html.twig is converted to
    the le path src/Acme/DemoBundle/Resources/views/Welcome/index.html.twig. Templates
    become even more interesting when you realize they don't need to be stored on the lesystem.
    You can easily store them in a database table for instance.
    Extending Bundles
    If you follow these conventions, then you can use bundle inheritance to override
    les, controllers or templates. For example, you can create a bundle - AcmeNewBundle
    - and specify that its parent is AcmeDemoBundle. When Symfony loads the
    AcmeDemoBundle:Welcome:index controller, it will rst look for the WelcomeController
    class in AcmeNewBundle and then look inside AcmeDemoBundle. This means that one
    bundle can override almost any part of another bundle!
    Do you understand now why Symfony2 is so exible? Share your bundles between
    applications, store them locally or globally, your choice.

    4.3 Using Vendors

    Ñêîðåå âñåãî âàøå ïðèëîæåíèå áóäåò çàâèñåòü è îò ñòîðîííèõ áèáëèîòåê. Îíè äîëæíû õðàíèòñÿ â ïàïêå vendor/. Îíà óæå ñîäåðæèò áèáëèîòåêè Symfony2, áèáëèîòåêó
    SwiftMailer, Doctrine ORM, ñèñòåìó øàáëîíèçàöèè Twig è íåêîòîðûå äðóãèå ñòîðîííèå
    áèáëèîòåêè.

    4.4 Êýøèðîâàíèå è Ëîãè

    Symfony2 is probably one of the fastest full-stack frameworks around. But how can it be
    so fast if it parses and interprets tens of YAML and XML les for each request? The speed
    is partly due to its cache system. The application conguration is only parsed for the very
    rst request and then compiled down to plain PHP code stored in the app/cache/ directory.
    In the development environment, Symfony2 is smart enough to ush the cache when you
    change a le. But in the production environment, it is your responsibility to clear the cache
    when you update your code or change its conguration.

    4.3. Using Vendors

    33

    Symfony Documentation, Âûïóñê 2.0
    When developing a web application, things can go wrong in many ways. The log les in
    the app/logs/ directory tell you everything about the requests and help you x the problem
    quickly.

    4.5 Èíòåðôåéñ êîìàíäíîé ñòðîêè

    Âñå ïðèëîæåíèÿ èäóò ñ èíòåðôåéñîì êîìàíäíîé ñòðîêè (app/console), êîòîðûé ïîìîãàåò îáñëóæèâàòü ïðèëîæåíèå. Îí ïðåäîñòàâëÿåò êîìàíäû, êîòîðûå óâåëè÷èâàþò âàøó
    ïðîäóêòèâíîñòü, àâòîìàòèçèðóÿ ÷àñòûå è ïîâòîðÿþùèåñÿ çàäà÷è.
    Çàïóñòèòå êîíñîëü áåç àãðóìåíòîâ, ÷òîáû ïîëó÷èòü ïðåäñòàâëåíèå î å¼ âîçìîæíîñòÿõ:

    php app/console
    Îïöèÿ --help ïîìîæåò âàì óòî÷íèòü âîçìîæíîñòè èñïîëüçîâàíèÿ êîìàíäû:

    php app/console router:debug --help

    4.6 Çàêëþ÷èòåëüíîå ñëîâî

    Íàçûâàéòå ìåíÿ ñóìàñøåäøèì, íî ïîñëå ïðî÷òåíèÿ ýòîé ÷àñòè, âàì äîëæíî áûòü êîìôîðòíî ïåðåìåùàòü ëþáûå âåùè è ïðè ýòîì çàñòàâèòü Symfony2 ðàáîòàòü íà âàñ. Â
    Symfony2 âñ¼ ñäåëàíî òàê, ÷òîáû âû ñìîãëè íàñòðîèòü åãî íà âàøå óñìîòðåíèå. Òàê
    ÷òî, ïåðåèìåíîâûâàéòå è ïåðåìåùàéòå äèðåêòîðèè êàê âàì óãîäíî.
    Äëÿ íà÷àëà ýòîãî äîñòàòî÷íî. Âàì åù¼ ïðåäñòîèò ìíîãîìó íàó÷èòüñÿ, îò òåñòèðîâàíèÿ
    äî îòïðàâêè ïî÷òû, ÷òîáû ñòàòü ìàñòåðîì Symfony2. Ãîòîâû ïîãðóçèòüñÿ â ÷òåíèå ñåé÷àñ? Ñëåäóéòå íà îôèöèàëüíóþ ñòðàíèöó ðóêîâîäñòâ Êíèãà è âûáèðàéòå ëþáóþ òåìó.

    • Îáùàÿ êàðòèíà >
    • Âèä >
    • Êîíòðîëëåð >
    • Àðõèòåêòóðà

    34

    Ãëàâà 4. Àðõèòåêòóðà

    ×àñòü II
    Ðóêîâîäñòâà

    35

    Symfony Documentation, Âûïóñê 2.0
    Ïîãðóçèòåñü â ìèð Symfony2 ñ ïîìîùüþ òåìàòè÷åñêèõ ðóêîâîäñòâ:

    37

    Symfony Documentation, Âûïóñê 2.0

    38

    Ãëàâà 5

    Êíèãà

    5.1 Îñíîâû Symfony2 è ïðîòîêîëà HTTP

    Congratulations! By learning about Symfony2, you're well on your way towards being a more
    productive, well-rounded and popular web developer (actually, you're on your own for the
    last part). Symfony2 is built to get back to basics: to develop tools that let you develop faster
    and build more robust applications, while staying out of your way. Symfony is built on the
    best ideas from many technologies: the tools and concepts you're about to learn represent
    the eorts of thousands of people, over many years. In other words, you're not just learning
    Symfony, you're learning the fundamentals of the web, development best practices, and
    how to use many amazing new PHP libraries, inside or independent of Symfony2. So, get
    ready.
    True to the Symfony2 philosophy, this chapter begins by explaining the fundamental
    concept common to web development: HTTP. Regardless of your background or preferred
    programming language, this chapter is a must-read for everyone.
    5.1.1 HTTP is Simple

    HTTP (Hypertext Transfer Protocol to the geeks) is a text language that allows two machines
    to communicate with each other. That's it! For example, when checking for the latest xkcd
    comic, the following (approximate) conversation takes place:

    39

    Symfony Documentation, Âûïóñê 2.0

    And while the actual language used is a bit more formal, it's still dead-simple. HTTP is the
    term used to describe this simple text-based language. And no matter how you develop on
    the web, the goal of your server is always to understand simple text requests, and return
    simple text responses.
    Symfony2 is built from the ground-up around that reality. Whether you realize it or not,
    HTTP is something you use everyday. With Symfony2, you'll learn how to master it.
    Øàã 1: Êëèåíò îòïðàâëÿåò çàïðîñ
    Every conversation on the web starts with a request. The request is a text message created
    by a client (e.g. a browser, an iPhone app, etc) in a special format known as HTTP. The
    client sends that request to a server, and then waits for the response.
    Take a look at the rst part of the interaction (the request) between a browser and the xkcd
    web server:

    In HTTP-speak, this HTTP request would actually look something like this:

    40

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    GET / HTTP/1.1
    Host: xkcd.com
    Accept: text/html
    User-Agent: Mozilla/5.0 (Macintosh)
    This simple message communicates everything necessary about exactly which resource the
    client is requesting. The rst line of an HTTP request is the most important and contains
    two things: the URI and the HTTP method.
    The URI (e.g. /, /contact, etc) is the unique address or location that identies the resource
    the client wants. The HTTP method (e.g. GET) denes what you want to do with the
    resource. Ìåòîäû HTTP ÿâëÿþòñÿ ãëàãîëàìè HTTP çàïðîñà è îïðåäåëÿþò íåñêîëüêî
    ïóòåé âçàèìîäåéñòâèÿ ñ ðåñóðñàìè:
    GET
    POST
    PUT
    DELETE

    Ïîëó÷èòü ðåñóðñ ñ ñåðâåðà
    Ñîçäàòü ðåñóðñ íà ñåðâåðå
    Îáíîâèòü ðåñóðñ íà ñåðâåð
    Óäàëèòü ðåñóðñ ñ ñåðâåðà

    Òåïåðü ìû ìîæåì ïðåäñòàâèòü, êàê ìîæåò âûãëÿäåòü HTTP çàïðîñ íà óäàëåíèå, ê
    ïðèìåðó, çàïèñè â áëîãå:

    DELETE /blog/15 HTTP/1.1
    Ïðèìå÷àíèå: Íà ñàìîì äåëå ñóùåñòâóþò äåâÿòü HTTP ìåòîäîâ, îïðåäåëåííûõ ñïåöèôèêàöèåé HTTP, íî ìíîãèå èç íèõ íå ðàñïðîñòðàíåíû øèðîêî èëè íå ñïîëüçóþòñÿ
    ïîïóëÿðíîñòüþ. Â ðåàëüíîñòè ìíîãèå ñîâðåìåííûå áðàóçåðû íå ïîääåðæèâàþò ìåòîäû
    PUT è DELETE.
    HTTP-çàïðîñ òàêæå ìîæåò ñîäåðæàòü è äðóãóþ èíôîðìàöèþ - HTTP çàãîëîâêè. Îíè
    ìîãóò ïðåäîñòàâëÿòü ìíîãî äîïîëíèòåëüíîé èíôîðìàöèè, òàêîé êàê çàïðîøåííûé óçåë
    (Host), ôîðìàòû îòâåüà, êîòîðûå ìîæåò ïðèíèìàòü êëèåíò (Accept) è ïðèëîæåíèå, êîòîðîå èñïîëüçóåòñÿ êëèåíòîì äëÿ ïåðåäà÷è çàïðîñà (User-Agent). Äðóãèå çàãîëîâêè îïèñàíû â ìàòåðèàëå èç Âèêèïåäèè `Ñïèñîê çàãîëîâêîâ HTTP`_.
    Øàã 2: Ñåðâåð âîçâðàùàåò Îòâåò
    Òåïåðü ñåðâåð, ïðî÷èòàâ çàãîëîâêè çàïðîñà, îáåðíóòûå â ôîðìàò HTTP, çíàåò, êàêîé
    êîíêðåòíî ðåñóðñ íóæåí êëèåíòó (ïî URI) è ÷òî êëèåíò ñîáèðàåòñÿ ñ íèì äåëàòü (ìåòîä
    HTTP).  ñëó÷àå ñ GET-çàïðîñîì, ñåðâåð ïîäãîòèàâëèâàåò ðåñóðñ è âîçâðàùàåò åãî êàê
    HTTP-îòâåò. Consider the response from the xkcd web server:

    5.1. Îñíîâû Symfony2 è ïðîòîêîëà HTTP

    41

    Symfony Documentation, Âûïóñê 2.0

    Translated into HTTP, the response sent back to the browser will look something like this:

    HTTP/1.1 200 OK
    Date: Sat, 02 Apr 2011 21:05:05 GMT
    Server: lighttpd/1.4.19
    Content-Type: text/html
    <html>
    <!-- HTML for the xkcd comic -->
    </html>
    The HTTP response contains the requested resource (the HTML content in this case), as well
    as other information about the response. The rst line is especially important and contains
    the HTTP response status code (200 in this case). The status code communicates the overall
    outcome of the request back to the client. Was the request successful? Was there an error?
    Dierent status codes exist that indicate success, an error, or that the client needs to do
    something (e.g. redirect to another page). A full list can be found on Wikipedia's List of
    HTTP status codes article.
    Like the request, an HTTP response contains additional pieces of information known as
    HTTP headers. For example, one important HTTP response header is Content-Type. The
    body of the same resource could be returned in multiple dierent formats including HTML,
    XML, or JSON to name a few. The Content-Type header tells the client which format is
    being returned.
    Many other headers exist, some of which are very powerful. For example, certain headers
    can be used to create a powerful caching system.
    Requests, Responses and Web Development
    This request-response conversation is the fundamental process that drives all communication
    on the web. And as important and powerful as this process is, it's inescapably simple.
    42

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    The most important fact is this: regardless of the language you use, the type of application
    you build (web, mobile, JSON API), or the development philosophy you follow, the end goal
    of an application is always to understand each request and create and return the appropriate
    response.
    Symfony is architected to match this reality.
    Ñîâåò: To learn more about the HTTP specication, read the original HTTP 1.1 RFC or
    the HTTP Bis, which is an active eort to clarify the original specication. A great tool
    to check both the request and response headers while browsing is the Live HTTP Headers
    extension for Firefox.

    5.1.2 Requests and Responses in PHP

    So how do you interact with the request and create a response when using PHP? In
    reality, PHP abstracts you a bit from the whole process:

    <?php
    $uri = $_SERVER['REQUEST_URI'];
    $foo = $_GET['foo'];
    header('Content-type: text/html');
    echo 'The URI requested is: '.$uri;
    echo 'The value of the "foo" parameter is: '.$foo;
    As strange as it sounds, this small application is in fact taking information from the HTTP
    request and using it to create an HTTP response. Instead of parsing the raw HTTP request
    message, PHP prepares superglobal variables such as $_SERVER and $_GET that contain
    all the information from the request. Similarly, instead of returning the HTTP-formatted
    text response, you can use the header() function to create response headers and simply print
    out the actual content that will be the content portion of the response message. PHP will
    create a true HTTP response and return it to the client:

    HTTP/1.1 200 OK
    Date: Sat, 03 Apr 2011 02:14:33 GMT
    Server: Apache/2.2.17 (Unix)
    Content-Type: text/html
    The URI requested is: /testing?foo=symfony
    The value of the "foo" parameter is: symfony

    5.1. Îñíîâû Symfony2 è ïðîòîêîëà HTTP

    43

    Symfony Documentation, Âûïóñê 2.0
    5.1.3 Requests and Responses in Symfony

    Symfony provides an alternative to the raw PHP approach via two classes that
    allow you to interact with the HTTP request and response in an easier way.
    The Symfony\Component\HttpFoundation\Request class is a simple object-oriented
    representation of the HTTP request message. With it, you have all the request information
    at your ngertips:

    use Symfony\Component\HttpFoundation\Request;
    $request = Request::createFromGlobals();
    // the URI being requested (e.g. /about) minus any query parameters
    $request->getPathInfo();
    // retrieve GET and POST variables respectively
    $request->query->get('foo');
    $request->request->get('bar');
    // retrieves an instance of UploadedFile identied by foo
    $request->les->get('foo');
    $request->getMethod();
    $request->getLanguages();

    // GET, POST, PUT, DELETE, HEAD
    // an array of languages the client accepts

    As a bonus, the Request class does a lot of work in the background that you'll never need to
    worry about. For example, the isSecure() method checks the three dierent values in PHP
    that can indicate whether or not the user is connecting via a secured connection (i.e. https).
    Symfony also provides a Response class: a simple PHP representation of an HTTP response
    message. This allows your application to use an object-oriented interface to construct the
    response that needs to be returned to the client:

    use Symfony\Component\HttpFoundation\Response;
    $response = new Response();
    $response->setContent('<html><body><h1>Hello world!</h1></body></html>');
    $response->setStatusCode(200);
    $response->headers->set('Content-Type', 'text/html');
    // prints the HTTP headers followed by the content
    $response->send();
    If Symfony oered nothing else, you would already have a toolkit for easily accessing request
    information and an object-oriented interface for creating the response. Even as you learn the
    many powerful features in Symfony, keep in mind that the goal of your application is always
    to interpret a request and create the appropriate response based on your application logic.
    44

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    Ñîâåò: The Request and Response classes are part of a standalone component included
    with Symfony called HttpFoundation. This component can be used entirely independent of
    Symfony and also provides classes for handling sessions and le uploads.

    5.1.4 The Journey from the Request to the Response

    Like HTTP itself, the Request and Response objects are pretty simple. The hard part of
    building an application is writing what comes in between. In other words, the real work
    comes in writing the code that interprets the request information and creates the response.
    Your application probably does many things, like sending emails, handling form submissions,
    saving things to a database, rendering HTML pages and protecting content with security.
    How can you manage all of this and still keep your code organized and maintainable?
    Symfony was created to solve these problems so that you don't have to.
    The Front Controller
    Traditionally, applications were built so that each page of a site was its own physical le:

    index.php
    contact.php
    blog.php
    There are several problems with this approach, including the inexibility of the URLs (what
    if you wanted to change blog.php to news.php without breaking all of your links?) and the
    fact that each le must manually include some set of core les so that security, database
    connections and the look of the site can remain consistent.
    A much better solution is to use a front controller: a single PHP le that handles every
    request coming into your application. For example:
    /index.php
    /index.php/contact
    /index.php/blog

    executes index.php
    executes index.php
    executes index.php

    Ñîâåò: Using Apache's mod_rewrite (or equivalent with other web servers), the URLs can
    easily be cleaned up to be just /, /contact and /blog.
    Now, every request is handled exactly the same. Instead of individual URLs executing
    dierent PHP les, the front controller is always executed, and the routing of dierent URLs
    to dierent parts of your application is done internally. This solves both problems with the
    original approach. Almost all modern web apps do this - including apps like WordPress.
    5.1. Îñíîâû Symfony2 è ïðîòîêîëà HTTP

    45

    Symfony Documentation, Âûïóñê 2.0
    Stay Organized
    But inside your front controller, how do you know which page should be rendered and how
    can you render each in a sane way? One way or another, you'll need to check the incoming
    URI and execute dierent parts of your code depending on that value. This can get ugly
    quickly:

    // index.php
    $request = Request::createFromGlobals();
    $path = $request->getPathInfo(); // the URL being requested
    if (in_array($path, array('', '/')) {
    $response = new Response('Welcome to the homepage.');
    } elseif ($path == '/contact') {
    $response = new Response('Contact us');
    } else {
    $response = new Response('Page not found.', 404);
    }
    $response->send();
    Solving this problem can be dicult. Fortunately it's exactly what Symfony is designed to
    do.
    The Symfony Application Flow
    When you let Symfony handle each request, life is much easier. Symfony follows the same
    simple pattern for every request:

    Ðèñ. 5.1: Incoming requests are interpreted by the routing and passed to controller functions
    that return Response objects.
    46

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    Each page of your site is dened in a routing conguration le that maps dierent URLs
    to dierent PHP functions. The job of each PHP function, called a controller, is to use
    information from the request - along with many other tools Symfony makes available - to
    create and return a Response object. In other words, the controller is where your code goes:
    it's where you interpret the request and create a response.
    It's that easy! Let's review:

    • Each request executes a front controller le;
    • The routing system determines which PHP function should be executed based on
    information from the request and routing conguration you've created;
    • The correct PHP function is executed, where your code creates and returns the
    appropriate Response object.
    A Symfony Request in Action
    Without diving into too much detail, let's see this process in action. Suppose you want to add
    a /contact page to your Symfony application. First, start by adding an entry for /contact to
    your routing conguration le:

    contact:
    pattern: /contact
    defaults: { _controller: AcmeDemoBundle:Main:contact }
    Ïðèìå÷àíèå: This example uses YAML to dene the routing conguration. Routing
    conguration can also be written in other formats such as XML or PHP.
    When someone visits the /contact page, this route is matched, and the specied controller is
    executed. As you'll learn in the routing chapter, the AcmeDemoBundle:Main:contact string
    is a short syntax that points to a specic PHP method contactAction inside a class called
    MainController:

    class MainController
    {
    public function contactAction()
    {
    return new Response('<h1>Contact us!</h1>');
    }
    }
    In this very simple example, the controller simply creates a Response object with the HTML
    <h1>Contact us!</h1>. In the controller chapter, you'll learn how a controller can render
    templates, allowing your presentation code (i.e. anything that actually writes out HTML)
    to live in a separate template le. This frees up the controller to worry only about the hard
    stu: interacting with the database, handling submitted data, or sending email messages.
    5.1. Îñíîâû Symfony2 è ïðîòîêîëà HTTP

    47

    Symfony Documentation, Âûïóñê 2.0
    5.1.5 Symfony2: Build your App, not your Tools.

    You now know that the goal of any app is to interpret each incoming request and create
    an appropriate response. As an application grows, it becomes more dicult to keep your
    code organized and maintainable. Invariably, the same complex tasks keep coming up over
    and over again: persisting things to the database, rendering and reusing templates, handling
    form submissions, sending emails, validating user input and handling security.
    The good news is that none of these problems is unique. Symfony provides a framework full
    of tools that allow you to build your application, not your tools. With Symfony2, nothing is
    imposed on you: you're free to use the full Symfony framework, or just one piece of Symfony
    all by itself.
    Standalone Tools: The Symfony2 Components
    So what is Symfony2? First, Symfony2 is a collection of over twenty independent libraries
    that can be used inside any PHP project. These libraries, called the Symfony2 Components,
    contain something useful for almost any situation, regardless of how your project is developed.
    To name a few:

    • HttpFoundation - Contains the Request and Response classes, as well as other classes
    for handling sessions and le uploads;
    • Routing - Powerful and fast routing system that allows you to map a specic URI (e.g.
    /contact) to some information about how that request should be handled (e.g. execute
    the contactAction() method);
    • Form - A full-featured and exible framework for creating forms and handing form
    submissions;
    • Validator A system for creating rules about data and then validating whether or not
    user-submitted data follows those rules;
    • ClassLoader An autoloading library that allows PHP classes to be used without needing
    to manually require the les containing those classes;
    • Templating A toolkit for rendering templates, handling template inheritance (i.e. a
    template is decorated with a layout) and performing other common template tasks;
    • Security - A powerful library for handling all types of security inside an application;
    • Translation A framework for translating strings in your application.
    Each and every one of these components is decoupled and can be used in any PHP project,
    regardless of whether or not you use the Symfony2 framework. Every part is made to be
    used if needed and replaced when necessary.

    48

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    The Full Solution: The Symfony2 Framework
    So then, what is the Symfony2 Framework? The Symfony2 Framework is a PHP library that
    accomplishes two distinct tasks:
    1. Provides a selection of components (i.e. the Symfony2 Components) and third-party
    libraries (e.g. Swiftmailer for sending emails);
    2. Provides sensible conguration and a glue library that ties all of these pieces together.
    The goal of the framework is to integrate many independent tools in order to provide a
    consistent experience for the developer. Even the framework itself is a Symfony2 bundle (i.e.
    a plugin) that can be congured or replaced entirely.
    Symfony2 provides a powerful set of tools for rapidly developing web applications without
    imposing on your application. Normal users can quickly start development by using a
    Symfony2 distribution, which provides a project skeleton with sensible defaults. For more
    advanced users, the sky is the limit.

    5.2 Symfony2 versus Flat PHP

    Why is Symfony2 better than just opening up a le and writing at PHP?
    If you've never used a PHP framework, aren't familiar with the MVC philosophy, or just
    wonder what all the hype is around Symfony2, this chapter is for you. Instead of telling you
    that Symfony2 allows you to develop faster and better software than with at PHP, you'll
    see for yourself.
    In this chapter, you'll write a simple application in at PHP, and then refactor it to be more
    organized. You'll travel through time, seeing the decisions behind why web development has
    evolved over the past several years to where it is now.
    By the end, you'll see how Symfony2 can rescue you from mundane tasks and let you take
    back control of your code.
    5.2.1 A simple Blog in at PHP

    In this chapter, you'll build the token blog application using only at PHP. To begin, create
    a single page that displays blog entries that have been persisted to the database. Writing in
    at PHP is quick and dirty:

    <?php
    // index.php
    $link = mysql_connect('localhost', 'myuser', 'mypassword');
    mysql_select_db('blog_db', $link);
    5.2. Symfony2 versus Flat PHP

    49

    Symfony Documentation, Âûïóñê 2.0

    $result = mysql_query('SELECT id, title FROM post', $link);
    ?>
    <html>
    <head>
    <title>List of Posts</title>
    </head>
    <body>
    <h1>List of Posts</h1>
    <ul>
    <?php while ($row = mysql_fetch_assoc($result)): ?>
    <li>
    <a href="/show.php?id=<?php echo $row['id'] ?>">
    <?php echo $row['title'] ?>
    </a>
    </li>
    <?php endwhile; ?>
    </ul>
    </body>
    </html>
    <?php
    mysql_close($link);
    That's quick to write, fast to execute, and, as your app grows, impossible to maintain. There
    are several problems that need to be addressed:

    • No error-checking: What if the connection to the database fails?
    • Poor organization: If the application grows, this single le will become increasingly
    unmaintainable. Where should you put code to handle a form submission? How can
    you validate data? Where should code go for sending emails?
    • Dicult to reuse code: Since everything is in one le, there's no way to reuse any part
    of the application for other pages of the blog.
    Ïðèìå÷àíèå: Another problem not mentioned here is the fact that the database is tied to
    MySQL. Though not covered here, Symfony2 fully integrates Doctrine, a library dedicated
    to database abstraction and mapping.
    Let's get to work on solving these problems and more.

    50

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    Isolating the Presentation
    The code can immediately gain from separating the application logic from the code that
    prepares the HTML presentation:

    <?php
    // index.php
    $link = mysql_connect('localhost', 'myuser', 'mypassword');
    mysql_select_db('blog_db', $link);
    $result = mysql_query('SELECT id, title FROM post', $link);
    $posts = array();
    while ($row = mysql_fetch_assoc($result)) {
    $posts[] = $row;
    }
    mysql_close($link);
    // include the HTML presentation code
    require 'templates/list.php';
    The HTML code is now stored in a separate le (templates/list.php), which is primarily an
    HTML le that uses a template-like PHP syntax:

    <html>
    <head>
    <title>List of Posts</title>
    </head>
    <body>
    <h1>List of Posts</h1>
    <ul>
    <?php foreach ($posts as $post): ?>
    <li>
    <a href="/read?id=<?php echo $post['id'] ?>">
    <?php echo $post['title'] ?>
    </a>
    </li>
    <?php endforeach; ?>
    </ul>
    </body>
    </html>
    By convention, the le that contains all of the application logic - index.php - is known as
    a controller. The term controller is a word you'll hear a lot, regardless of the language or
    framework you use. It refers simply to the area of your code that processes user input and
    prepares the response.
    5.2. Symfony2 versus Flat PHP

    51

    Symfony Documentation, Âûïóñê 2.0
    In this case, our controller prepares data from the database and then includes a template
    to present that data. With the controller isolated, you could easily change just the template
    le if you needed to render the blog entries in some other format (e.g. list.json.php for JSON
    format).
    Isolating the Application (Domain) Logic
    So far the application contains only one page. But what if a second page needed to use the
    same database connection, or even the same array of blog posts? Refactor the code so that
    the core behavior and data-access functions of the application are isolated in a new le called
    model.php:

    <?php
    // model.php
    function open_database_connection()
    {
    $link = mysql_connect('localhost', 'myuser', 'mypassword');
    mysql_select_db('blog_db', $link);
    }

    return $link;

    function close_database_connection($link)
    {
    mysql_close($link);
    }
    function get_all_posts()
    {
    $link = open_database_connection();
    $result = mysql_query('SELECT id, title FROM post', $link);
    $posts = array();
    while ($row = mysql_fetch_assoc($result)) {
    $posts[] = $row;
    }
    close_database_connection($link);
    }

    return $posts;

    Ñîâåò: The lename model.php is used because the logic and data access of an application
    is traditionally known as the model layer. In a well-organized application, the majority of
    the code representing your business logic should live in the model (as opposed to living in
    52

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    a controller). And unlike in this example, only a portion (or none) of the model is actually
    concerned with accessing a database.
    The controller (index.php) is now very simple:

    <?php
    require_once 'model.php';
    $posts = get_all_posts();
    require 'templates/list.php';
    Now, the sole task of the controller is to get data from the model layer of the application
    (the model) and to call a template to render that data. This is a very simple example of the
    model-view-controller pattern.
    Isolating the Layout
    At this point, the application has been refactored into three distinct pieces oering various
    advantages and the opportunity to reuse almost everything on dierent pages.
    The only part of the code that can't be reused is the page layout. Fix that by creating a new
    layout.php le:

    <!-- templates/layout.php -->
    <html>
    <head>
    <title><?php echo $title ?></title>
    </head>
    <body>
    <?php echo $content ?>
    </body>
    </html>
    The template (templates/list.php) can now be simplied to extend the layout:

    <?php $title = 'List of Posts' ?>
    <?php ob_start() ?>
    <h1>List of Posts</h1>
    <ul>
    <?php foreach ($posts as $post): ?>
    <li>
    <a href="/read?id=<?php echo $post['id'] ?>">
    <?php echo $post['title'] ?>
    </a>
    </li>
    5.2. Symfony2 versus Flat PHP

    53

    Symfony Documentation, Âûïóñê 2.0

    <?php endforeach; ?>
    </ul>
    <?php $content = ob_get_clean() ?>
    <?php include 'layout.php' ?>
    You've now introduced a methodology that allows for the reuse of the layout. Unfortunately,
    to accomplish this, you're forced to use a few ugly PHP functions (ob_start(),
    ob_get_clean()) in the template. Symfony2 uses a Templating component that allows this
    to be accomplished cleanly and easily. You'll see it in action shortly.
    5.2.2 Adding a Blog show Page

    The blog list page has now been refactored so that the code is better-organized and reusable.
    To prove it, add a blog show page, which displays an individual blog post identied by an
    id query parameter.
    To begin, create a new function in the model.php le that retrieves an individual blog result
    based on a given id:

    // model.php
    function get_post_by_id($id)
    {
    $link = open_database_connection();
    $id = mysql_real_escape_string($id);
    $query = 'SELECT date, title, body FROM post WHERE id = '.$id;
    $result = mysql_query($query);
    $row = mysql_fetch_assoc($result);
    close_database_connection($link);
    }

    return $row;

    Next, create a new le called show.php - the controller for this new page:

    <?php
    require_once 'model.php';
    $post = get_post_by_id($_GET['id']);
    require 'templates/show.php';
    Finally, create the new template le - templates/show.php - to render the individual blog
    post:
    54

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    <?php $title = $post['title'] ?>
    <?php ob_start() ?>
    <h1><?php echo $post['title'] ?></h1>
    <div class="date"><?php echo $post['date'] ?></div>
    <div class="body">
    <?php echo $post['body'] ?>
    </div>
    <?php $content = ob_get_clean() ?>
    <?php include 'layout.php' ?>
    Creating the second page is now very easy and no code is duplicated. Still, this page
    introduces even more lingering problems that a framework can solve for you. For example,
    a missing or invalid id query parameter will cause the page to crash. It would be better if
    this caused a 404 page to be rendered, but this can't really be done easily yet. Worse, had
    you forgotten to clean the id parameter via the mysql_real_escape_string() function, your
    entire database would be at risk for an SQL injection attack.
    Another major problem is that each individual controller le must include the model.php
    le. What if each controller le suddenly needed to include an additional le or perform
    some other global task (e.g. enforce security)? As it stands now, that code would need to
    be added to every controller le. If you forget to include something in one le, hopefully it
    doesn't relate to security...
    5.2.3 A Front Controller to the Rescue

    The solution is to use a front controller: a single PHP le through which all requests are
    processed. With a front controller, the URIs for the application change slightly, but start to
    become more exible:

    Without a front controller
    /index.php
    => Blog post list page (index.php executed)
    /show.php
    => Blog post show page (show.php executed)
    With index.php as the front controller
    /index.php
    => Blog post list page (index.php executed)
    /index.php/show => Blog post show page (index.php executed)
    Ñîâåò: The index.php portion of the URI can be removed if using Apache rewrite rules (or
    equivalent). In that case, the resulting URI of the blog show page would be simply /show.
    When using a front controller, a single PHP le (index.php in this case) renders every request.
    5.2. Symfony2 versus Flat PHP

    55

    Symfony Documentation, Âûïóñê 2.0
    For the blog post show page, /index.php/show will actually execute the index.php le, which
    is now responsible for routing requests internally based on the full URI. As you'll see, a front
    controller is a very powerful tool.
    Creating the Front Controller
    You're about to take a big step with the application. With one le handling all requests, you
    can centralize things such as security handling, conguration loading, and routing. In this
    application, index.php must now be smart enough to render the blog post list page or the
    blog post show page based on the requested URI:

    <?php
    // index.php
    // load and initialize any global libraries
    require_once 'model.php';
    require_once 'controllers.php';
    // route the request internally
    $uri = $_SERVER['REQUEST_URI'];
    if ($uri == '/index.php') {
    list_action();
    } elseif ($uri == '/index.php/show' && isset($_GET['id'])) {
    show_action($_GET['id']);
    } else {
    header('Status: 404 Not Found');
    echo '<html><body><h1>Page Not Found</h1></body></html>';
    }
    For organization, both controllers (formerly index.php and show.php) are now PHP functions
    and each has been moved into a separate le, controllers.php:

    function list_action()
    {
    $posts = get_all_posts();
    require 'templates/list.php';
    }
    function show_action($id)
    {
    $post = get_post_by_id($id);
    require 'templates/show.php';
    }
    As a front controller, index.php has taken on an entirely new role, one that includes
    loading the core libraries and routing the application so that one of the two controllers
    56

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    (the list_action() and show_action() functions) is called. In reality, the front controller is
    beginning to look and act a lot like Symfony2's mechanism for handling and routing requests.
    Ñîâåò: Another advantage of a front controller is exible URLs. Notice that the URL to
    the blog post show page could be changed from /show to /read by changing code in only
    one location. Before, an entire le needed to be renamed. In Symfony2, URLs are even more
    exible.
    By now, the application has evolved from a single PHP le into a structure that is organized
    and allows for code reuse. You should be happier, but far from satised. For example, the
    routing system is ckle, and wouldn't recognize that the list page (/index.php) should be
    accessible also via / (if Apache rewrite rules were added). Also, instead of developing the blog,
    a lot of time is being spent working on the architecture of the code (e.g. routing, calling
    controllers, templates, etc.). More time will need to be spent to handle form submissions,
    input validation, logging and security. Why should you have to reinvent solutions to all these
    routine problems?
    Add a Touch of Symfony2
    Symfony2 to the rescue. Before actually using Symfony2, you need to make sure PHP knows
    how to nd the Symfony2 classes. This is accomplished via an autoloader that Symfony
    provides. An autoloader is a tool that makes it possible to start using PHP classes without
    explicitly including the le containing the class.
    First, download symfony and place it into a vendor/symfony/ directory. Next, create an
    app/bootstrap.php le. Use it to require the two les in the application and to congure the
    autoloader:

    <?php
    // bootstrap.php
    require_once 'model.php';
    require_once 'controllers.php';
    require_once 'vendor/symfony/src/Symfony/Component/ClassLoader/UniversalClassLoader.php';
    $loader = new Symfony\Component\ClassLoader\UniversalClassLoader();
    $loader->registerNamespaces(array(
    'Symfony' => __DIR__.'/vendor/symfony/src',
    ));
    $loader->register();
    This tells the autoloader where the Symfony classes are. With this, you can start using
    Symfony classes without using the require statement for the les that contain them.
    Core to Symfony's
    job is to interpret

    philosophy is
    each request

    5.2. Symfony2 versus Flat PHP

    the
    and

    idea that an application's main
    return a response. To this end,
    57

    Symfony Documentation, Âûïóñê 2.0
    Symfony2 provides both a Symfony\Component\HttpFoundation\Request and a
    Symfony\Component\HttpFoundation\Response class. These classes are object-oriented
    representations of the raw HTTP request being processed and the HTTP response being
    returned. Use them to improve the blog:

    <?php
    // index.php
    require_once 'app/bootstrap.php';
    use Symfony\Component\HttpFoundation\Request;
    use Symfony\Component\HttpFoundation\Response;
    $request = Request::createFromGlobals();
    $uri = $request->getPathInfo();
    if ($uri == '/') {
    $response = list_action();
    } elseif ($uri == '/show' && $request->query->has('id')) {
    $response = show_action($request->query->get('id'));
    } else {
    $html = '<html><body><h1>Page Not Found</h1></body></html>';
    $response = new Response($html, 404);
    }
    // echo the headers and send the response
    $response->send();
    The controllers are now responsible for returning a Response object. To make this easier,
    you can add a new render_template() function, which, incidentally, acts quite a bit like the
    Symfony2 templating engine:

    // controllers.php
    use Symfony\Component\HttpFoundation\Response;
    function list_action()
    {
    $posts = get_all_posts();
    $html = render_template('templates/list.php', array('posts' => $posts));
    }

    return new Response($html);

    function show_action($id)
    {
    $post = get_post_by_id($id);
    $html = render_template('templates/show.php', array('post' => $post));
    return new Response($html);
    58

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    }
    // helper function to render templates
    function render_template($path, array $args)
    {
    extract($args);
    ob_start();
    require $path;
    $html = ob_get_clean();
    }

    return $html;

    By bringing in a small part of Symfony2, the application is more exible and reliable.
    The Request provides a dependable way to access information about the HTTP request.
    Specically, the getPathInfo() method returns a cleaned URI (always returning /show and
    never /index.php/show). So, even if the user goes to /index.php/show, the application is
    intelligent enough to route the request through show_action().
    The Response object gives exibility when constructing the HTTP response, allowing HTTP
    headers and content to be added via an object-oriented interface. And while the responses
    in this application are simple, this exibility will pay dividends as your application grows.
    The Sample Application in Symfony2
    The blog has come a long way, but it still contains a lot of code for such a simple application.
    Along the way, we've also invented a simple routing system and a method using ob_start()
    and ob_get_clean() to render templates. If, for some reason, you needed to continue building
    this framework from scratch, you could at least use Symfony's standalone Routing and
    Templating components, which already solve these problems.
    Instead of re-solving common problems, you can let Symfony2 take care of them for you.
    Here's the same sample application, now built in Symfony2:

    <?php
    // src/Acme/BlogBundle/Controller/BlogController.php
    namespace Acme\BlogBundle\Controller;
    use Symfony\Bundle\FrameworkBundle\Controller\Controller;
    class BlogController extends Controller
    {
    public function listAction()
    {
    $posts = $this->get('doctrine')->getEntityManager()
    ->createQuery('SELECT p FROM AcmeBlogBundle:Post p')
    5.2. Symfony2 versus Flat PHP

    59

    Symfony Documentation, Âûïóñê 2.0

    ->execute();
    }

    return $this->render('AcmeBlogBundle:Post:list.html.php', array('posts' => $posts));

    public function showAction($id)
    {
    $post = $this->get('doctrine')
    ->getEntityManager()
    ->getRepository('AcmeBlogBundle:Post')
    ->nd($id);
    if (!$post) {
    // cause the 404 page not found to be displayed
    throw $this->createNotFoundException();
    }

    }

    }

    return $this->render('AcmeBlogBundle:Post:show.html.php', array('post' => $post));

    The two controllers are still lightweight. Each uses the Doctrine ORM library to retrieve
    objects from the database and the Templating component to render a template and return
    a Response object. The list template is now quite a bit simpler:

    <!-- src/Acme/BlogBundle/Resources/views/Blog/list.html.php -->
    <?php $view->extend('::layout.html.php') ?>
    <?php $view['slots']->set('title', 'List of Posts') ?>
    <h1>List of Posts</h1>
    <ul>
    <?php foreach ($posts as $post): ?>
    <li>
    <a href="<?php echo $view['router']->generate('blog_show', array('id' => $post->getId())) ?>">
    <?php echo $post->getTitle() ?>
    </a>
    </li>
    <?php endforeach; ?>
    </ul>
    The layout is nearly identical:

    <!-- app/Resources/views/layout.html.php -->
    <html>
    <head>
    <title><?php echo $view['slots']->output('title', 'Default title') ?></title>
    60

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    </head>
    <body>
    <?php echo $view['slots']->output('_content') ?>
    </body>
    </html>
    Ïðèìå÷àíèå: We'll leave the show template as an exercise, as it should be trivial to create
    based on the list template.
    When Symfony2's engine (called the Kernel) boots up, it needs a map so that it knows
    which controllers to execute based on the request information. A routing conguration map
    provides this information in a readable format:

    # app/cong/routing.yml
    blog_list:
    pattern: /blog
    defaults: { _controller: AcmeBlogBundle:Blog:list }
    blog_show:
    pattern: /blog/show/{id}
    defaults: { _controller: AcmeBlogBundle:Blog:show }
    Now that Symfony2 is handling all the mundane tasks, the front controller is dead simple.
    And since it does so little, you'll never have to touch it once it's created (and if you use a
    Symfony2 distribution, you won't even need to create it!):

    <?php
    // web/app.php
    require_once __DIR__.'/../app/bootstrap.php';
    require_once __DIR__.'/../app/AppKernel.php';
    use Symfony\Component\HttpFoundation\Request;
    $kernel = new AppKernel('prod', false);
    $kernel->handle(Request::createFromGlobals())->send();
    The front controller's only job is to initialize Symfony2's engine (Kernel) and pass it a
    Request object to handle. Symfony2's core then uses the routing map to determine which
    controller to call. Just like before, the controller method is responsible for returning the nal
    Response object. There's really not much else to it.
    For a visual representation of how Symfony2 handles each request, see the request ow
    diagram.

    5.2. Symfony2 versus Flat PHP

    61

    Symfony Documentation, Âûïóñê 2.0
    Where Symfony2 Delivers
    In the upcoming chapters, you'll learn more about how each piece of Symfony works and the
    recommended organization of a project. For now, let's see how migrating the blog from at
    PHP to Symfony2 has improved life:

    • Your application now has clear and consistently organized code (though Symfony
    doesn't force you into this). This promotes reusability and allows for new developers
    to be productive in your project more quickly.
    • 100% of the code you write is for your application. You don't need to develop or
    maintain low-level utilities such as autoloading, routing, or rendering controllers.
    • Symfony2 gives you access to open source tools such as Doctrine and the Templating,
    Security, Form, Validation and Translation components (to name a few).
    • The application now enjoys fully-exible URLs thanks to the Routing component.
    • Symfony2's HTTP-centric architecture gives you access to powerful tools such as HTTP
    caching powered by Symfony2's internal HTTP cache or more powerful tools such as
    Varnish. This is covered in a later chapter all about caching.
    And perhaps best of all, by using Symfony2, you now have access to a whole set of highquality open source tools developed by the Symfony2 community! For more information,
    check out Symfony2Bundles.org
    5.2.4 Better templates

    If you choose to use it, Symfony2 comes standard with a templating engine called Twig that
    makes templates faster to write and easier to read. It means that the sample application
    could contain even less code! Take, for example, the list template written in Twig:

    {# src/Acme/BlogBundle/Resources/views/Blog/list.html.twig #}
    {% extends "::layout.html.twig" %}
    {% block title %}List of Posts{% endblock %}
    {% block body %}
    <h1>List of Posts</h1>
    <ul>
    {% for post in posts %}
    <li>
    <a href="{{ path('blog_show', { 'id': post.id }) }}">
    {{ post.title }}
    </a>
    </li>
    {% endfor %}

    62

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    </ul>
    {% endblock %}
    The corresponding layout.html.twig template is also easier to write:

    {# app/Resources/views/layout.html.twig #}
    <html>
    <head>
    <title>{% block title %}Default title{% endblock %}</title>
    </head>
    <body>
    {% block body %}{% endblock %}
    </body>
    </html>
    Twig is well-supported in Symfony2. And while PHP templates will always be supported in
    Symfony2, we'll continue to discuss the many advantages of Twig. For more information, see
    the templating chapter.
    5.2.5 Learn more from the Cookbook

    • How to use PHP instead of Twig for Templates
    • How to dene Controllers as Services

    5.3 Installing and Conguring Symfony

    The goal of this chapter is to get you up and running with a working application built on
    top of Symfony. Fortunately, Symfony oers distributions, which are functional Symfony
    starter projects that you can download and begin developing in immediately.
    Ñîâåò: If you're looking for instructions on how best to create a new project and store it
    via source control, see Using Source Control.

    5.3.1 Downloading a Symfony2 Distribution

    Ñîâåò: First, check that you have installed and congured a Web server (such as Apache)
    with PHP 5.3.2 or higher. For more information on Symfony2 requirements, see the
    requirements reference.
    5.3. Installing and Conguring Symfony

    63

    Symfony Documentation, Âûïóñê 2.0

    Symfony2 packages distributions, which are fully-functional applications that include the
    Symfony2 core libraries, a selection of useful bundles, a sensible directory structure and some
    default conguration. When you download a Symfony2 distribution, you're downloading
    a functional application skeleton that can be used immediately to begin developing your
    application.
    Start by visiting the Symfony2 download page at http://symfony.com/download. On this
    page, you'll see the Symfony Standard Edition, which is the main Symfony2 distribution.
    Here, you'll need to make two choices:

    • Download either a .tgz or .zip archive - both are equivalent, download whatever you're
    more comfortable using;
    • Download the distribution with or without vendors. If you have Git installed on your
    computer, you should download Symfony2 without vendors, as it adds a bit more
    exibility when including third-party/vendor libraries.
    Download one of the archives somewhere under your local web server's root directory and
    unpack it. From a UNIX command line, this can be done with one of the following commands
    (replacing ### with your actual lename):

    # for .tgz le
    tar zxvf Symfony_Standard_Vendors_2.0.###.tgz
    # for a .zip le
    unzip Symfony_Standard_Vendors_2.0.###.zip
    When you're nished, you should have a Symfony/ directory that looks something like this:

    www/ <- your web root directory
    Symfony/ <- the unpacked archive
    app/
    cache/
    cong/
    logs/
    src/
    ...
    vendor/
    ...
    web/
    app.php
    ...

    64

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    Updating Vendors
    Finally, if you downloaded the archive without vendors, install the vendors by running the
    following command from the command line:

    php bin/vendors install
    This command downloads all of the necessary vendor libraries - including Symfony itself into the vendor/ directory. For more information on how third-party vendor libraries are
    managed inside Symfony2, see Managing Vendor Libraries with bin/vendors and deps.
    Conguration and Setup
    At this point, all of the needed third-party libraries now live in the vendor/ directory. You
    also have a default application setup in app/ and some sample code inside the src/ directory.
    Symfony2 comes with a visual server conguration tester to help make sure your Web server
    and PHP are congured to use Symfony. Use the following URL to check your conguration:

    http://localhost/Symfony/web/cong.php
    If there are any issues, correct them now before moving on.

    5.3. Installing and Conguring Symfony

    65

    Symfony Documentation, Âûïóñê 2.0
    Setting up Permissions
    One common issue is that the app/cache and app/logs directories must be writable both
    by the web server and the command line user. On a UNIX system, if your web server
    user is dierent from your command line user, you can run the following commands
    just once in your project to ensure that permissions will be setup properly. Change
    www-data to the web server user and yourname to your command line user:
    1. Using ACL on a system that supports chmod +a
    Many systems allow you to use the chmod +a command. Try this rst, and if you get
    an error - try the next method:

    rm -rf app/cache/*
    rm -rf app/logs/*
    sudo chmod +a "www-data allow delete,write,append,le_inherit,directory_inherit" app/cache app/logs
    sudo chmod +a "yourname allow delete,write,append,le_inherit,directory_inherit" app/cache app/logs
    2. Using Acl on a system that does not support chmod +a
    Some systems don't support chmod +a, but do support another utility called setfacl.
    You may need to enable ACL support on your partition and install setfacl before using
    it (as is the case with Ubuntu), like so:

    sudo setfacl -R -m u:www-data:rwx -m u:yourname:rwx app/cache app/logs
    sudo setfacl -dR -m u:www-data:rwx -m u:yourname:rwx app/cache app/logs
    3. Without using ACL
    If you don't have access to changing the ACL of the directories, you will need to change
    the umask so that the cache and log directories will be group-writable or world-writable
    (depending if the web server user and the command line user are in the same group
    or not). To achieve this, put the following line at the beginning of the app/console,
    web/app.php and web/app_dev.php les:

    umask(0002); // This will let the permissions be 0775
    // or
    umask(0000); // This will let the permissions be 0777
    Note that using the ACL is recommended when you have access to them on your server
    because changing the umask is not thread-safe.
    When everything is ne, click on Go to the Welcome page to request your rst real
    Symfony2 webpage:

    http://localhost/Symfony/web/app_dev.php/
    Symfony2 should welcome and congratulate you for your hard work so far!

    66

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    5.3.2 Beginning Development

    Now that you have a fully-functional Symfony2 application, you can begin development!
    Your distribution may contain some sample code - check the README.rst le included with
    the distribution (open it as a text le) to learn about what sample code was included with
    your distribution and how you can remove it later.
    If you're new to Symfony, join us in the Ñîçäàíèå ñòðàíèö â Symfony2, where you'll learn
    how to create pages, change conguration, and do everything else you'll need in your new
    application.
    5.3.3 Using Source Control

    If you're using a version control system like Git or Subversion, you can setup your version
    control system and begin committing your project to it as normal. The Symfony Standard
    edition is the starting point for your new project.
    For specic instructions on how best to setup your project to be stored in git, see How to
    Create and store a Symfony2 Project in git.

    5.3. Installing and Conguring Symfony

    67

    Symfony Documentation, Âûïóñê 2.0
    Ignoring the vendor/ Directory
    If you've downloaded the archive without vendors, you can safely ignore the entire vendor/
    directory and not commit it to source control. With Git, this is done by creating and adding
    the following to a .gitignore le:

    vendor/
    Now, the vendor directory won't be committed to source control. This is ne (actually, it's
    great!) because when someone else clones or checks out the project, he/she can simply run
    the php bin/vendors install script to download all the necessary vendor libraries.

    5.4 Ñîçäàíèå ñòðàíèö â Symfony2

    Ñîçäàíèå íîâîé ñòðàíèöû â Symfony2 ýòî ïðîñòîé ïðîöåññ, ñîñòîÿùèé èç 2 øàãîâ:

    • Ñîçäàíèå ìàðøðóòà: Ìàðøðóò îïðåäåëÿåò URI (íàïðèìåð /about) äëÿ âàøåé ñòðàíèöû, à òàêæå êîíòðîëëåð (PHP ôóíêöèÿ), êîòîðûé Symfony2 äîëæåí âûïîëíèòü,
    êîãäà URI âõîäÿùåãî çàïðîñà ñîâïàäåò øàáëîíîì ìàðøðóòà;
    • Ñîçäàíèå êîíòðîëëåðà: Êîíòðîëëåð  ýòî PHP ôóíêöèÿ, êîòîðàÿ ïðèíèìàåò âõîäÿùèé çàïðîñ è òðàíñôîðìèðóåò åãî â îáúåêò Response.
    Íàì íðàâèòñÿ òàêîé ïîäõîä, ïîòîìó ÷òî îí ñîîòâåòñòâóåò òîìó êàê ðàáîòàåò Web. Êàæäîå âçàèìîäåéñòâèå â Web èíèöèàëèçèðóåòñÿ HTTP çàïðîñîì. Çàáîòà âàøåãî ïðèëîæåíèÿ  èíòåðïðåòèðîâàòü çàïðîñ è âåðíóòü ñîîòâåòñòâóþùèé îòâåò.
    Symfony2 ñëåäóåò ýòîé ôèëîñîôèè è ïðåäëàãàåò âàì èíñòðóìåíòû è ñîãëàøåíèÿ, äëÿ
    òîãî ÷òîáû âàøå ïðèëîæåíèå îñòàâàëîñü ñòðóêòóðèðîâàííûì ïðè ðîñòå åãî ñëîæíîñòè.
    Çâó÷èò ïðîñòî? Äàâàéòå ïîïðîáóåì!
    5.4.1 Ñòðàíèöà Hello Symfony!

    Äàâàéòå íà÷íåì ñ êëàññè÷åñêîãî ïðèëîæåíèÿ Hello World!. Êîãäà ìû çàêîí÷èì, ïîëüçîâàòåëü áóäåò èìåòü âîçìîæíîñòü ïîëó÷èòü ïåðñîíàëüíîå ïðèâåòñòâèå, ïåðåéäÿ ïî ñëåäóþùåìó URL:

    http://localhost/app_dev.php/hello/Symfony
    Âû òàêæå ñìîæåòå çàìåíèòü Symfony íà äðóãîå èìÿ è ïîëó÷èòü íîâîå ïðèâåòñòâèå. Äëÿ
    ñîçäàíèÿ ýòîé ñòðàíèöû ìû ïðîéäåì ïðîñòîé ïóòü èç äâóõ øàãîâ.
    Ïðèìå÷àíèå: Äàííîå ðóêîâîäñòâî ïîäðàçóìåâàåò, ÷òî âû óæå ñêà÷àëè Symfony2 è íàñòðîèëè âàø âåáñåðâåð. URL, óêàçàííûé âûøå, ïîäðàçóìåâàåò, ÷òî localhost óêàçûâàåò
    68

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    íà web-äèðåêòîðèþ âàøåãî íîâîãî Symfony2 ïðîåêòà. Åñëè æå âû åù¼ íå âûïîëíèëè
    ýòèõ øàãîâ, ðåêîìåíäóåòñÿ èõ âûïîëíèòü, ïðåæäå ÷åì âû ïðîäîëæèòå ÷èòàòü. Äëÿ ïîäðîáíîé èíôîðìàöèè ïðî÷òèòå Óñòàíîâêà Symfony2.

    Ïðåæäå ÷åì âû íà÷íåòå: Ñîçäàíèå ïàêåòà (bundle)
    Ïðåæäå ÷åì íà÷àòü, âàì íåîáõîäèìî ñîçäàòü ïàêåò (bundle).  Symfony2 ïàêåò íàïîìèíàåò ïëàãèí, çà èñêëþ÷åíèåì òîãî, ÷òî âåñü êîä âàøåãî ïðèëîæåíèÿ áóäåò ðàñïîëîæåí
    âíóòðè òàêîãî ïàêåòà.
    Âîîáùå ãîâîðÿ, ïàêåò  ýòî íå áîëåå ÷åì äèðåêòîðèÿ (ñîîòâåòñòâóþùàÿ òåì íå ìåíåå
    ïðîñòðàíñòâó èìåí PHP), êîòîðàÿ ñîäåðæèò âñå ÷òî îòíîñèòñÿ ê êàêîé-òî ñïåöèôè÷åñêîé ôóíêöèè, âêëþ÷àÿ PHP-êëàññû, êîíôèãóðàöèþ è äàæå ñòèëè è Javascript-ôàéëû
    (ñì. Ñèñòåìà ïàêåòîâ).
    Äëÿ ñîçäàíèÿ ïàêåòà ñ èìåíåì AcmeHelloBundle (äåìî-ïàêåò, êîòîðûé ìû ñîçäàäèì â
    õîäå ïðî÷òåíèÿ äàííîé ñòàòüè), âûïîëíèòå ñëåäóþùóþ êîìàíäó è ñëåäóéòå èíñòðóêöèÿì íà ýêðàíå (èñïîëüçóéòå íàñòðîéêè ïî-óìîë÷àíèþ):

    php app/console generate:bundle --namespace=Acme/HelloBundle --format=yml
    Êîìàíäà ñîçäàåò äèðåêòîðèþ äëÿ ïàêåòà src/Acme/HelloBundle. Òàê æå îíà äîáàâëÿåò
    ñòðîêó â ôàéë app/AppKernel.php äëÿ ðåãèñòðàöèè ïàêåòà â ÿäðå ïðèëîæåíèÿ:

    // app/AppKernel.php
    public function registerBundles()
    {
    $bundles = array(
    // ...
    new Acme\HelloBundle\AcmeHelloBundle(),
    );
    // ...
    }

    return $bundles;

    Òåïåðü, êîãäà ìû ñîçäàëè è ïîäêëþ÷èëè ïàêåò, ìû ìîæåì íà÷àòü ñîçäàíèå íàøåãî
    ïðèëîæåíèÿ â í¼ì.
    Øàã 1: Ñîçäàíèå ìàðøðóòà
    Ïî óìîë÷àíèþ, êîíôèãóðàöèîííûé ôàéë ìàðøðóòèçàòîðà â ïðèëîæåíèè Symfony2,
    ðàñïîëàãàåòñÿ â app/cong/routing.yml. Äëÿ êîíôèãóðèðîâàíèÿ ìàðøðóòèçàòîðà, à òàêæå ëþáûõ ïðî÷èõ êîíôèãóðàöèé Symfony2, âû ìîæåòå òàêæå èñïîëüçîâàòü XML èëè
    PHP ôîðìàò.
    5.4. Ñîçäàíèå ñòðàíèö â Symfony2

    69

    Symfony Documentation, Âûïóñê 2.0
    Åñëè âû ïîñìîòðèòå íà ãëàâíûé ôàéë ìàðøðóòèçàöèè òî óâèäèòå, ÷òî Symfony óæå
    äîáàâèëà ïóíêò ïðè ãåíåðàöèè AcmeHelloBundle:

    • YAML

    # app/cong/routing.yml
    AcmeHelloBundle:
    resource: "@AcmeHelloBundle/Resources/cong/routing.yml"
    prex: /
    • XML

    <!-- app/cong/routing.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>

    <routes xmlns="http://symfony.com/schema/routing"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing
    <import resource="@AcmeHelloBundle/Resources/cong/routing.xml" prex="/" />
    </routes>
    • PHP

    // app/cong/routing.php
    use Symfony\Component\Routing\RouteCollection;
    use Symfony\Component\Routing\Route;
    $collection = new RouteCollection();
    $collection->addCollection(
    $loader->import('@AcmeHelloBundle/Resources/cong/routing.php'),
    '/',
    );
    return $collection;
    Ýòîò ïóíêò î÷åíü ïðîñò: îí ãîâîðèò Symfony çàãðóæàòü ôàéë íàñòðîåê èç
    Resources/cong/routing.yml, êîòîðûé íàõîäèòñÿ âíóòðè ïàêåòà AcmeHelloBundle. Ýòî
    çíà÷èò, ÷òî âû ìîæåòå ïèñàòü ìàðøðóòû ïðÿìî â app/cong/routing.yml èëè îðãàíèçîâàòü èõ ïî âàøåìó ïðèëîæåíèÿ è çàãðóçèòü èõ îòñþäà.
    Êîãäà ôàéë routing.yml èç ïàêåòû áûë çàãðóæåí, äîáàâüòå íîâûå ìàðøðóòû äëÿ ñòðàíèöû, êîòîðóþ ìû õîòèì ñîçäàòü:

    • YAML

    # src/Acme/HelloBundle/Resources/cong/routing.yml
    hello:
    pattern: /hello/{name}
    70

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    defaults: { _controller: AcmeHelloBundle:Hello:index }
    • XML

    <!-- src/Acme/HelloBundle/Resources/cong/routing.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>

    <routes xmlns="http://symfony.com/schema/routing"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing
    <route id="hello" pattern="/hello/{name}">
    <default key="_controller">AcmeHelloBundle:Hello:index</default>
    </route>
    </routes>
    • PHP

    // src/Acme/HelloBundle/Resources/cong/routing.php
    use Symfony\Component\Routing\RouteCollection;
    use Symfony\Component\Routing\Route;
    $collection = new RouteCollection();
    $collection->add('hello', new Route('/hello/{name}', array(
    '_controller' => 'AcmeHelloBundle:Hello:index',
    )));
    return $collection;
    Ìàðøðóò ñîñòîèò èç äâóõ îñíîâíûõ ÷àñòåé: pattern, ñ êîòîðûì ñðàâíèâàåòñÿ URI, à
    òàêæå ìàññèâ defaults â êîòîðîì óêàçûâàåòñÿ êîíòðîëëåð, êîòîðûé íåîáõîäèìî âûïîëíèòü. Ñèíòàêñèñ óêàçàòåëÿ ìåñòà çàïîëíåíèÿ (placeholder) â øàáëîíå ({name})  ýòî
    ãðóïïîâîé ñèìâîë (wildcard). Îí îçíà÷àåò, ÷òî URI /hello/Ryan, /hello/Fabien, à òàêæå
    ïðî÷èå, ïîõîäèå íà íèõ, áóäóò ñîîòâåòñòâîâàòü ýòîìó ìàðøðóòó. Ïàðàìåòð, îïðåäåë¼ííûé óêàçàòåëåì {name} òàêæå áóäåò ïåðåäàí â íàø êîíòðîëëåð, òàê ÷òî ìû ñìîæåì
    èñïîëüçîâàòü åãî, ÷òîáû ïîïðèâåòñòâîâàòü ïîëüçîâàòåëÿ.
    Ïðèìå÷àíèå: Ñèñòåìà ìàðøðóòèçàöèè èìååò åùå ìíîæåñòâî çàìå÷àòåëüíûõ ôóíêöèé
    äëÿ ñîçäàíèÿ ãèáêèõ è ôóíêöèîíàëüíûõ ñòðóêòóð URI â íàøåì ïðèëîæåíèè. Çà äîïîëíèòåëüíîé èíôîðìàöèåé âû ìîæåòå îáðàòèòüñÿ ê ãëàâå Ìàðøðóòèçàöèÿ.

    Øàã 2: Ñîçäàíèå Êîíòðîëëåðà
    Êîãäà URI âèäà /hello/Ryan îáíàðóæèâàåòñÿ ïðèëîæåíèåì â çàïðîñå, ìàðøðóò hello
    ñðàáîòàåò è áóäåò âûçâàí êîíòðîëëåð AcmeHelloBundle:Hello:index. Ñëåäóþùèì íàøèì
    5.4. Ñîçäàíèå ñòðàíèö â Symfony2

    71

    Symfony Documentation, Âûïóñê 2.0
    øàãîì áóäåò ñîçäàíèå ýòîãî êîíòðîëëåðà.
    Êîíòðîëëåð AcmeHelloBundle:Hello:index - ýòî ëîãè÷åñêîå èìÿ êîíòðîëëåðà è îíî óêçàûâàåò íà ìåòîä indexAction PHP-êëàññà Acme\HelloBundle\Controller\Hello. Íà÷íèòå ñ
    ñîçäàíèÿ ýòîãî ôàéëà âíóòðè âàøåãî AcmeHelloBundle:

    // src/Acme/HelloBundle/Controller/HelloController.php
    namespace Acme\HelloBundle\Controller;
    use Symfony\Component\HttpFoundation\Response;
    class HelloController
    {
    }
    Â äåéñòâèòåëüíîñòè, êîíòðîëëåð  ýòî íå ÷òî èíîå, êàê ìåòîä PHP êëàññà, êîòîðûé ìû
    ñîçäàåì, à Symfony âûïîëíÿåò. Ýòî òî ìåñòî, ãäå ïðèëîæåíèå, èñïîëüçóÿ èíôîðìàöèþ
    èç çàïðîñà, ñîçäàåò çàïðîøåííûé ðåñóðñ. Çà èñêëþ÷åíèåì íåêîòîðûõ îñîáûõ ñëó÷àåâ,
    ðåçóëüòàòîì ðàáîòû êîíòðîëëåðà âñåãäà ÿâëÿåòñÿ îáúåêò Symfony2 Response.
    Ñîçäàéòå ìåòîä indexAction, êîòîðûé áóäåò çàïóùåí ïðè ñîâïàäåíèè ìàðøðóòà hello:

    // src/Acme/HelloBundle/Controller/HelloController.php
    // ...
    class HelloController
    {
    public function indexAction($name)
    {
    return new Response('<html><body>Hello '.$name.'!</body></html>');
    }
    }
    Ýòîò êîíòðîëëåð ïðåäåëüíî ïðîñò: îí ñîçäàåò íîâûé îáúåêò Response ÷üèì ïåðâûì
    àðãóìåíòîì ÿâëÿåòñÿ êîíòåíò, êîòîðûé áóäåò èñïîëüçîâàí äëÿ ñîçäàíèÿ îòâåòà (â íàøåì
    ñëó÷àå ýòî ìàëåíüêàÿ HTML-ñòðàíèöà, êîä êîòîðîé ìû óêàçàëè ïðÿìî â êîíòðîëëåðå).
    Ïðèìèòå ìîè ïîçäðàâëåíèÿ! Ïîñëå ñîçäàíèÿ ìàðøðóòà è êîíòðîëëåðà, âû óæå èìååòå
    ïîëíîöåííóþ ñòðàíèöó! Åñëè âû âñå íàñòðîèëè êîððåêòíî, âàøå ïðèëîæåíèå äîëæíî
    ïîïðèâåòñòâîâàòü âàñ:

    http://localhost/app_dev.php/hello/Ryan
    Ñîâåò: You can also view your app in the prod environment by visiting:

    http://localhost/app.php/hello/Ryan
    If you get an error, it's likely because you need to clear your cache by running:
    72

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    php app/console cache:clear --env=prod --no-debug
    Îïöèîíàëüíûì (íî êàê ïðàâèëî âîñòðåáîâàííûì) òðåòüèì øàãîì ÿâëÿåòñÿ ñîçäàíèå
    øàáëîíà.
    Ïðèìå÷àíèå: Êîíòðîëëåð  ýòî ãëàâíàÿ òî÷êà âõîäà äëÿ âàøåãî êîäà è êëþ÷åâîé èíãðèäèåò ïðè ñîçäàíèè ñòðàíèö. Áîëüøå èíôîðìàöèè î êîíòðîëëåðàõ âû ìîæåòå íàéòè
    â ãëàâå Êîíòðîëëåðû.

    Íåîáÿçàòåëüíûé øàã 3: Ñîçäàíèå øàáëîíà
    Øàáëîíû ïîçâîëÿþò íàì âûíåñòè ðàçìåòêó ñòðàíèö (HTML êîä êàê âðàâèëî) â îòäåëüíûé ôàéë è ïîâòîðíî èñïîëüçîâàòü ðàçëè÷íûå ÷àñòè øàáëîíà ñòðàíèöû. Âìåñòî òîãî
    ÷òîáû ïèñàòü êîä âíóòðè êîíòðîëëåðà, âîñïîëüçóåìñÿ øàáëîíîì:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    // src/Acme/HelloBundle/Controller/HelloController.php
    namespace Acme\HelloBundle\Controller;
    use Symfony\Bundle\FrameworkBundle\Controller\Controller;
    class HelloController extends Controller
    {
    public function indexAction($name)
    {
    return $this->render('AcmeHelloBundle:Hello:index.html.twig', array('name' => $name));

    11
    12
    13
    14
    15

    }

    }

    // render a PHP template instead
    // return $this->render('AcmeHelloBundle:Hello:index.html.php', array('name' => $name));

    Ïðèìå÷àíèå:
    Äëÿ òîãî, ÷òîáû èñïîëüçîâàòü ìåòîä render() íåîáõîäèìî îòíàñëåäîâàòüñÿ îò êëàññà Symfony\Bundle\FrameworkBundle\Controller\Controller (API
    Symfony\Bundle\FrameworkBundle\Controller\Controller), êîòîðûé äîáàâëÿåò íåñêîëüêî ìåòîäîâ äëÿ áûñòðîãî âûçîâà äëÿ ÷àñòî óïîòðåáëÿåìûõ ôóíêöèé â êîíòðîëëåðå. Â
    ïðèìåðå âûøå ýòî ñäåëàíî äîáàâëåíèåì use íà ëèíèè 4 è ðàñøèðåíèåì êëàññà Controller
    íà ëèíèè 6.
    Ìåòîä render() ñîçäàåò îáúåêò Response çàïîëíåííûé ñîäåðæàíèåì îáðàáîòàííîãî (ðåíäåðåíîãî) øàáëîíà. Êàê è ëþáîé äðóãîé êîíòðîëëåð, âû â êîíöå êîíöîâ âåðíåòå îáúåêò
    Response.

    5.4. Ñîçäàíèå ñòðàíèö â Symfony2

    73

    Symfony Documentation, Âûïóñê 2.0
    Îáðàòèòå âíèìàíèå, ÷òî åñòü äâå ðàçëè÷íûå âîçìîæíîñòè ðåíäåðèíãà øàáëîíîâ.
    Symfony2 ïî-óìîë÷àíèþ, ïîääåðæèâàåò 2 ÿçûêà øàáëîíîâ: êëàññè÷åñêèå PHP-øàáëîíû
    è ïðîñòîé, íî ìîùíûé ÿçûê øàáëîíîâ Twig. Íî íå ïóãàéòåñü, âû ñâîáîäíû â âûáîðå òîãî
    èëè èíîãî èç íèõ, êðîìå òîãî âû ìîæåòå èñïîëüçîâàòü îáà â ðàìêàõ îäíîãî ïðîåêòà.
    Êîíòðîëëåð îòîáðàæàåò øàáëîí AcmeHelloBundle:Hello:index.html.twig, êîòîðûé èñïîëüçóåò ñëåäóþùèå ñîãëàøåíèÿ:
    BundleName:ControllerName:TemplateName
    Ýòî ëîãè÷åñêîå èìÿ øàáëîíà, êîòîðîå óêàçûâàåò íà ôèçè÷åñêîå ïîëîæåíèå ñëåäóÿ ýòîìó
    ñîãëàøåíèþ.
    /path/to/BundleName/Resources/views/ControllerName/TemplateName
    Òàêèì îáðàçîì, AcmeHelloBundle  ýòî èìÿ ïàêåòà, Hello  ýòî êîíòðîëëåð è
    index.html.twig ýòî øàáëîí:

    • Twig
    1
    2
    3
    4
    5
    6

    {# src/Acme/HelloBundle/Resources/views/Hello/index.html.twig #}
    {% extends '::base.html.twig' %}
    {% block body %}
    Hello {{ name }}!
    {% endblock %}
    • PHP

    <!-- src/Acme/HelloBundle/Resources/views/Hello/index.html.php -->
    <?php $view->extend('::base.html.php') ?>
    Hello <?php echo $view->escape($name) ?>!
    Äàâàéòå ðàññìîòðèì ïîäðîáíåå øàáëîí Twig:

    • Ñòðîêà 2: Òîêåí extends îïðåäåëÿåò ðîäèòåëüñêèé øàáëîí. Òàêèì îáðàçîì ñàì
    øàáëîí îäíîçíà÷íûì îáðàçîì îïðåäåëÿåò ðîäèòåëÿ (layout) âíóòðü êîòîðîãî îí
    áóäåò ïîìåùåí.
    • Ñòðîêà 4: Òîêåí block îçíà÷àåò, ÷òî âñå âíóòðè íåãî áóäåò ïîìåùåíî â áëîê ñ
    èìåíåì body. Êàê ìû óâèäèì íèæå, ýòî óæå îáÿçàííîñòü ðîäèòåëüñêîãî øàáëîíà
    (base.html.twig) ïîëíîñòüþ îòîáðàçèòü áëîê body.
     ðîäèòåëüñêîì øàáëîíå, ::base.html.twig, îòñòóòñòâóþò îáå ÷àñòè BundleName è
    ControllerName (ïóñòîå äâîåòî÷èå (::) â íà÷àëå). Ýòî îçíà÷àåò, ÷òî øàáëîí íàõîäèòüñÿ çà ïðåäåëàìè ïàêåòà, â äèðåêòîðèè app:

    • Twig

    74

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    {# app/Resources/views/base.html.twig #}
    <!DOCTYPE html>
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>{% block title %}Welcome!{% endblock %}</title>
    {% block stylesheets %}{% endblock %}
    <link rel="shortcut icon" href="{{ asset('favicon.ico') }}" />
    </head>
    <body>
    {% block body %}{% endblock %}
    {% block javascripts %}{% endblock %}
    </body>
    </html>
    • PHP

    <!-- app/Resources/views/base.html.php -->
    <!DOCTYPE html>
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title><?php $view['slots']->output('title', 'Welcome!') ?></title>
    <?php $view['slots']->output('stylesheets') ?>
    <link rel="shortcut icon" href="<?php echo $view['assets']->getUrl('favicon.ico') ?>" />
    </head>
    <body>
    <?php $view['slots']->output('_content') ?>
    <?php $view['slots']->output('stylesheets') ?>
    </body>
    </html>
    Áàçîâûé øàáëîí îïðåäåëÿåò HTML ðàçìåòêó áëîêà body êîòîðûé ìû îïðåäåëèëè â
    øàáëîíå index.html.twig. Îí òàêæå îòîáðàæàåò áëîê title êîòîðûé ìû òàêæå ìîæåì
    îïðåäåëèòü â index.html.twig. Òàê êàê ìû íå îïðåäåëèëè áëîê title â äî÷åðíåì øàáëîíå,
    îí ïðèìåò çíà÷åíèå ïî óìîë÷àíèþ  Welcome!.
    Øàáëîíû ÿâëÿþòñÿ ìîùíûì èíñòðóìåíòîì ïî îðãàíèçàöèè è îòîáðàæåíèþ êîíòåíòà
    âàøèõ ñòðàíèö  HTML ðàçìåòêè, CSS ñòèëåé, à òàêæå âñåãî ïðî÷åãî, ÷òî ìîæåò ïîòðåáîâàòüñÿ âåðíóòü êîíòðîëëåðó.
    Íî øàáëîíèçàòîð  ýòî ïðîñòî ñðåäñòâî äëÿ äîñòèæåíèÿ öåëè. À öåëü ñîñòîèò â òîì,
    ÷òîáû êàæäûé êîíòðîëëåð âîçâðàùàë îáúåêò Response. Òàêèì îáðàçîì, øàáëîíû ìîùíûé, íî îïöèîíàëüíûé èíñòðóìåíò äëÿ ñîçäàíèÿ êîíòåíòà äëÿ îáúåêòà Response.

    5.4. Ñîçäàíèå ñòðàíèö â Symfony2

    75

    Symfony Documentation, Âûïóñê 2.0
    5.4.2 Ñòðóêòóðà äèðåêòîðèé

    Ìû ïðî÷èòàëè âñåãî ëèøü ïîñëå íåñêîëüêèõ êîðîòêèõ ñåêöèé, à âû óæå óÿñíèëè ôèëîñîôèþ ñîçäàíèÿ è îòîáðàæåíèÿ ñòðàíèö â Symfony2. Ïîýòîìó áåç ëèøíèõ ñëîâ ìû
    ïðèñòóïèì ê èçó÷åíèþ òîãî, êàê îðãàíèçîâàíû è ñòðóêòóðèðîâàíû ïðîåêòû Symfony2.
    Ê êîíöó ýòîé ñåêöèè âû áóäåòå çíàòü ãäå íàéòè è êóäà ïîìåñòèòü ðàçëè÷íûå òèïû
    ôàéëîâ. È áîëåå òîãî, áóäåò ïîíèìàòü  ïî÷åìó!
    Èçíà÷àëüíî ñîçäàííûé î÷åíü ãèáêèì, ïî óìîë÷àíèþ êàæäîå Symfony ïðèëîæåíèå èìååò
    îäíó è òó æå áàçîâóþ (è ðåêîìåíäóåìóþ) ñòðóêòóðó äèðåêòîðèé:

    • app/: Ýòà äèðåêòîðèÿ ñîäåðæèò íàñòðîéêè ïðèëîæåíèÿ;
    • src/: Âåñü PHP êîä ïðîåêòà íàõîäèòñÿ â ýòîé äèðåêòîðèè;
    • vendor/: Çäåñü ðàçìåùàþòñÿ ñòîðîííèå áèáëèîòåêè;
    • web/: Ýòî êîðíåâàÿ äèðåêòîðèÿ, âèäèìàÿ web-ñåðâåðó è ñîäåðæàùàÿ äîñòóïíûå
    ïîëüçîâàòåëÿì ôàéëû;
    Äèðåêòîðèÿ Web
    Web-äèðåêòîðèÿ  ýòî äîì äëÿ âñåõ ïóáëè÷íî-äîñòóïíûõ ñòàòè÷åñêèõ ôàéëîâ, òàêèõ
    êàê èçîáðàæåíèÿ, òàáëèöû ñòèëåé è JavaScript ôàéëû. Òóò òàêæå ðàñïîëàãàþòñÿ âñå
    ôðîíò-êîíòðîëëåðû:

    // web/app.php
    require_once __DIR__.'/../app/bootstrap.php.cache';
    require_once __DIR__.'/../app/AppKernel.php';
    use Symfony\Component\HttpFoundation\Request;
    $kernel = new AppKernel('prod', false);
    $kernel->loadClassCache();
    $kernel->handle(Request::createFromGlobals())->send();
    Ôàéë ôðîíò-êîíòðîëëåðà (â ïðèìåðå âûøå  app.php) - ýòî PHP ôàéë, êîòîðûé âûïîëíÿåòñÿ, êîãäà èñïîëüçóåòñÿ Symfony2 ïðèëîæåíèå è â åãî îáÿçàííîñòè âõîäèò èñïîëüçîâàíèå Kernel-êëàññà, AppKernel, äëÿ çàïóñêà ïðèëîæåíèÿ.
    Ñîâåò: Íàëè÷èå ôðîíò-êîíòðîëëåðà îçíà÷àåò âîçìîæíîñòü èñïîëüçîâàíèÿ áîëåå ãèáêèõ
    URL, îòëè÷íûõ îò òåõ, ÷òî èñïîëüçóþòñÿ â òèïè÷íîì ïëîñêîì PHP - ïðèëîæåíèè.
    Êîãäà èñïîëüçóåòñÿ ôðîíò-êîíòðîëëåð, URL ôîðìèðóåòñÿ ñëåäóþùèì îáðàçîì:

    http://localhost/app.php/hello/Ryan
    Ôðîíò-êîíòðîëëåð app.php âûïîëíÿåòñÿ è URL /hello/Ryan íàïðàâëÿåòñÿ âíóòðè ïðèëîæåíèÿ ñ èñïîëüçîâàíèåì êîíôèãóðàöèè ìàðøðóòèçàòîðà. Ñ èñïîëüçîâàíèåì ïðàâèë
    76

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    mod_rewrite äëÿ Apache âû ìîæåòå ïåðåíàïðàâëÿòü âñå çàïðîñû (íà ôèçè÷åñêè íå ñóùåñòâóþùèå URL) íà app.php, ÷òîáû ÿâíî íå óêàçûâàòü åãî â URL:

    http://localhost/hello/Ryan
    Õîòÿ ôðîíò-êîíòðîëëåðû èìåþò âàæíîå çíà÷åíèå ïðè îáðàáîòêå êàæäîãî çàïðîñà,âàì
    íå÷àñòî ïðèäåòñÿ ìîäèôèöèðîâàòü èõ èëè âîîáùå âñïîìèíàòü îá èõ ñóùåñòâîâàíèè. Ìû
    åùå âêðàòöå óïîìÿíåì î íèõ â ðàçäåëå, ãäå ãîâîðèòñÿ îá Îêðóæåíèÿ.
    Äèðåêòîðèÿ ïðèëîæåíèÿ (app)
    Êàê âû óæå âèäåëè âî ôðîíò-êîíòðîëëåðå, êëàññ AppKernel  ýòî òî÷êà âõîäà ïðèëîæåíèÿ è îí îòâå÷àåò çà åãî êîíôèãóðàöèþ. Êàê òàêîâîé, ýòîò êëàññ ðàñïîëîæåí â
    äèðåêòîðèè app/.
    Ýòîò êëàññ äîëæåí ðåàëèçîâûâàòü òðè ìåòîäà, êîòîðûå îïðåäåëÿþòñÿ âñå, ÷òî Symfony
    íåîáõîäèìî çíàòü î âàøåì ïðèëîæåíèè. Âàì äàæå íå íóæíî áåñïîêîèòüñÿ î ðåàëèçàöèè
    ýòèõ ìåòîäîâ, êîãäà íà÷èíàåòå ðàáîòó  îíè óæå ðåàëèçîâàíû ñ êîäîì ïî-óìîë÷àíèþ.

    • registerBundles(): Âîçâðàùàåò ìàññèâ âñåõ ïàêåòîâ, íåîáõîäèìûõ äëÿ çàïóñêà ïðèëîæåíèÿ (ñì. ñåêöèþ Ñèñòåìà ïàêåòîâ);
    • registerContainerConguration(): Çàãðóæàåò ãëàâíûé êîíôèãóðàöèîííûé ôàéë
    (ñì. ñåêöèþ Íàñòðîéêà ïðèëîæåíèÿ).
    Èçî äíÿ â äåíü âû áóäåòå èñïîëüçîâàòü äèðåêòîðèþ app/ â îñíîâíîì äëÿ òîãî, ÷òîáû ìîäèôèöèðîâàòü êîíôèãóðàöèþ è íàñòðîéêè ìàðøðóòèçàòîðà â äèðåêòîðèè app/cong/
    directory (ñì. Íàñòðîéêà ïðèëîæåíèÿ). Òàêæå â app/ ñîäåðæèòñÿ êåø (app/cache),
    äèðåêòîðèÿ äëÿ ëîãîâ (app/logs) è äèðåêòîðèÿ äëÿ ðåñóðñîâ óðîâíÿ ïðèëîæåíèÿ
    (app/Resources). Îá ýòèõ äèðåêòîðèÿõ ïîäðîáíåå áóäåò ðàññêàçàíî â äðóãèõ ãëàâàõ.

    5.4. Ñîçäàíèå ñòðàíèö â Symfony2

    77

    Symfony Documentation, Âûïóñê 2.0
    Àâòîçàãðóçêà
    Ïðè èíèöèàëèçàöèè ïðèëîæåíèÿ ïîäêëþ÷àåòñÿ îñîáûé ôàéë - app/autoload.php.
    Ýòîò ôàéë îòâå÷àåò çà àâòîçàãðóçêó âñåõ ôàéëîâ èç äèðåêòîðèé src/ è vendor/.
    Ñ èñïîëüçîâàíèåì àâòîçàãðóçêè âàì áîëüøå íå ïðèäåòñÿ áåñïîêîèòüñÿ îá èñïîëüçîâàíèè âûðàæåíèé include èëè require. Âìåñòî ýòîãî, Symfony2 èñïîëüçóåò ïðîñòðàíñòâà èìåí êëàññîâ, ÷òîáû îïðåäåëèòü èõ ðàñïîëîæåíèå è àâòîìàòè÷åñêè ïîäêëþ÷èòü ôàéë êëàññà, â ñëó÷àå åñëè êëàññ âàì ïîíàäîáèòñÿ.
    Ïðè òàêîé êîíôèãóðàöèè, Symfony2 áóäåò èñêàòü â äèðåêòîðèè src êëàññû èç ïðîñòðàíñòâà èìåí Acme (âû ñêîðåå âñåãî áóäåòå èñïîëüçîâàòü íàèìåíîâàíèå âàøåé
    êîìïàíèè). Äëÿ òîãî ÷òîáû ýòà ïàðàäèãìà ðàáîòàëà, íåîáõîäèìî ÷òîáû èìÿ êëàññà
    è ïóòü ê íåìó ñîîòâåòñòâîâàëè ñëåäóþùåìó øàáëîíó:

    Class Name:
    Acme\HelloBundle\Controller\HelloController
    Path:
    src/Acme/HelloBundle/Controller/HelloController.php
    Îáû÷íî, åäèíñòâåííîå âðåìÿ êîãäà âàì íàäî áåñïîêîèòüñÿ î ôàéëå
    app/autoload.php, ýòî êîãäà âû ïîäêëþ÷àåòå íîâóþ áèáëèîòåêó ñòîðîííèõ
    ðàçðàáîò÷èêîâ â ïàïêå vendor/. Äëÿ áîëåå ïîäðîáíîé èíôîðìàöèè î àâòîçàãðóçêå
    ñìîòðèòå Êàê àâòîçàãðóæàòü êëàññû.

    Äèðåêòîðèÿ èñõîäíûõ êîäîâ ïðîåêòà (src)
    Åñëè âêðàòöå, äèðåêòîðèÿ src/ ñîäåðæèò âåñü êîä ïðèëîæåíèÿ. Ôàêòè÷åñêè, âî âðåìÿ
    ðàçðàáîòêè, áîëüøóþ ÷àñòü ðàáîò âû áóäåòå ïðîèçâîäèòü èìåííî â ýòîé äèðåêòîðèè.
    Ïî óìîë÷àíèþ, äèðåêòîðèÿ src/ íîâîãî ïðîåêòà ïóñòà. Êîãäà âû íà÷èíàåòå ðàçðàáîòêó,
    âû ïîñòåïåííî íàïîëíÿåòå åå ïàêåòàìè, êîòîðûå ñîäåðæàò êîä ïðèëîæåíèÿ.
    Íî ÷òî æå ñîáñòâåííî èç ñåáÿ ïðåäñòàâëÿåò ñàì ïàêåò?
    5.4.3 Ñèñòåìà ïàêåòîâ

    Ïàêåò ÷åì-òî ñõîæ ñ ïëàãèíîì, íî îí åù¼ ëó÷øå. Êëþ÷åâîå îòëè÷èå ñîñòîèò â òîì, ÷òî
    âñå åñòü ïàêåò â Symfony2, âêëþ÷àÿ ôóíêöèîíàë ÿäðà è êîä âàøåãî ïðèëîæåíèÿ. Ïàêåòû
     ýòî ãðàæäàíå âûñøåãî ñîðòà â Symfony2. Îíè äàþò âàì âîçìîæíîñòü èñïîëüçîâàòü
    óæå ãîòîâûå ïàêåòû, êîòîðûå âû ìîæåòå íàéòè ïî àäðåñó third-party bundles.Âû òàêæå
    ìîæåòå òàì âûêëàäûâàòü ñâîè ïàêåòû. Îíè òàêæå äàþò âîçìîæíîñòü ëåãêî è ïðîñòî
    âûáðàòü, êàêèå èìåííî ôóíêöèè ïîäêëþ÷èòü â âàøåì ïðèëîæåíèè.
    Ïðèìå÷àíèå: Çäåñü ìû ðàññìîòðèì ëèøü îñíîâû, áîëåå äåòàëüíóþ èíôîðìàöèþ ïî
    ïàêåòàì âû ìîæåòå íàéòè â ãëàâå ïàêåòû.

    78

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    Ïàêåò - ýòî ñòðóêòóðèðîâàííàÿ êîëëåêöèÿ ôàéëîâ âíóòðè äèðåêòîðèè, êîòîðûå âûïîëíÿþò îäíó ôóíêöèþ. Âû ìîæåòå ñîçäàòü BlogBundle, ForumBundle èëè ïàêåò äëÿ
    óïðàâëåíèÿ ïîëüçîâàòåëÿìè (ìíîæåñòâî ïàêåòîâ óæå ñóùåñòâóåò). Êàæäàÿ ïàïêà ñîäåðæèò âñå, ÷òî îòíîñèòñÿ ê ôóíêöèè, âêëþ÷àÿ PHP-ôàéëû, øàáëîíû, ñòèëè, JavaScript-û,
    òåñòû è âñå îñòàëüíîå.
    Ïðèëîæåíèå ïîäêëþ÷àåò ïàêåòû, óêàçàííûå â ìåòîäå registerBundles() êëàññà
    AppKernel:

    // app/AppKernel.php
    public function registerBundles()
    {
    $bundles = array(
    new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
    new Symfony\Bundle\SecurityBundle\SecurityBundle(),
    new Symfony\Bundle\TwigBundle\TwigBundle(),
    new Symfony\Bundle\MonologBundle\MonologBundle(),
    new Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle(),
    new Symfony\Bundle\DoctrineBundle\DoctrineBundle(),
    new Symfony\Bundle\AsseticBundle\AsseticBundle(),
    new Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(),
    new JMS\SecurityExtraBundle\JMSSecurityExtraBundle(),
    );
    if (in_array($this->getEnvironment(), array('dev', 'test'))) {
    $bundles[] = new Acme\DemoBundle\AcmeDemoBundle();
    $bundles[] = new Symfony\Bundle\WebProlerBundle\WebProlerBundle();
    $bundles[] = new Sensio\Bundle\DistributionBundle\SensioDistributionBundle();
    $bundles[] = new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle();
    }
    }

    return $bundles;

    Ñ ìåòîäîì registerBundles() ó âàñ åñòü ïîëíûé êîíòðîëü íàä ïàêåòàìè, èñïîëüçóåìûìè
    ïðèëîæåíèåì (âêëþ÷àÿ ïàêåòû Symfony).
    Ñîâåò: Ïàêåò ìîæåò íàõîäèòüñÿ âåçäå, ïîêà îí ìîæåò áûòü çàãðóæåí (÷åðåç àâòîçàãðóç÷èê íàñòðîåííûé â app/autoload.php).

    Ñîçäàíèå ïàêåòà
    Symfony Standard Edition ïðåäîñòàâëÿåò óäîáíóþ êîìàíäó äëÿ ñîçäàíèÿ ïîëíîôóíêöèîíàëüíîãî ïàêåòà. Êîíå÷íî, âû ìîæåòå ïðîñòî ñîçäàòü ïàêåò âðó÷íóþ.

    5.4. Ñîçäàíèå ñòðàíèö â Symfony2

    79

    Symfony Documentation, Âûïóñê 2.0
    ×òîáû ïîêàçàòü âàì êàê ïðîñòà ñèñòåìà ïàêåòîâ, äàâàéòå ñîçäàäèì íîâûé ïàêåò, íàçîâ¼ì åãî AcmeTestBundle è àêòèâèðóåì åãî.
    Ñîâåò: ×àñòü Acme ýòî âûäóìàííîå èìÿ îðãàíèçàöèè è äîëæíî áûòü çàìåíåíî èìåíåì, êîòîðîå ïðåäñòàâëÿåò âàñ èëè âàøó îðãàíèçàöèþ (íàïðèìåð ABCTestBundle äëÿ
    êîìïàíèè ñ íàçâàíèåì ABC).
    Íà÷íèòå ñ ñîçäàíèÿ
    AcmeTestBundle.php:

    äèðåêòîðèè

    src/Acme/TestBundle/

    è

    äîáàâëåíèÿ

    ôàéëà

    // src/Acme/TestBundle/AcmeTestBundle.php
    namespace Acme\TestBundle;
    use Symfony\Component\HttpKernel\Bundle\Bundle;
    class AcmeTestBundle extends Bundle
    {
    }
    Ñîâåò: Íàèìåíîâàíèå AcmeTestBundle ñëåäóåò ñîãëàøåíèÿì ïî èìåíîâàíèþ ïàêåòîâ. Âû òàê æå ìîæåòå ñîêðàòèòü èìÿ ïàêåòû äî ïðîñòîãî TestBundle, íàçâàâ êëàññ
    TestBundle (è íàçâàâ ôàéë TestBundle.php).
    Ýòîò ïóñòîé êëàññ  åäèíñòâåííîå, ÷òî íåîáõîäèìî ñîçäàòü äëÿ ìèíèìàëüíîé êîìïëåêòàöèè ïàêåòà. Íå ñìîòðÿ íà òî, ÷òî êëàññ ïóñò, îí îáëàäàåò áîëüøèì ïîòåíöèàëîì è
    ïîçâîëÿåò íàñòðàèâàòü ïîâåäåíèå ïàêåòà.
    Òåïåðü, êîãäà ìû ñîçäàëè ïàêåò, åãî íóæíî àêòèâèðîâàòü â êëàññå AppKernel:

    // app/AppKernel.php
    public function registerBundles()
    {
    $bundles = array(
    // ...
    // register your bundles
    new Acme\TestBundle\AcmeTestBundle(),

    );
    // ...
    }

    return $bundles;

    È, õîòÿ íàø íîâûé ïàêåò ïîêà íè÷åãî íå äåëàåò, îí ãîòîâ ê èñïîëüçîâàíèþ.
    Symfony òàêæå ïðåäëàãàåò èíòåðôåéñ äëÿ êîìàíäíîé ñòðîêè äëÿ ñîçäàíèÿ áàçîâîãî
    80

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    êàðêàñà ïàêåòà:

    php app/console generate:bundle --namespace=Acme/TestBundle
    Êàðêàñ ïàêåòà ñîçäà¼ò áàçîâûé êîíòðîëëåð, øàáëîí è ìàðøðóò, êîòîðûå ìîæíî íàñòðîèòü. Ìû åùå âåðí¼ìñÿ ê èíñòðóìåíòàì êîìàíäíîé ñòðîêè ïîçæå.
    Ñîâåò: Êîãäà ñîçäà¼òå íîâûé ïàêåò, èëè èñïîëüçóåòå ñòîðîííèå ïàêåòû, óáåäèòåñü, ÷òî
    ïàêåò àêòèâèðîâàí â registerBundles(). Ïðè èñïîëüçîâàíèè êîìàíäû generate:bundle âñå
    óæå ñäåëàíî çà âàñ.

    Ñòðóêòóðà äèðåêòîðèè ïàêåòà
    Ñòðóêòóðà äèðåêòîðèè ïàêåòà ïðîñòà è ãèáêà. Ïî óìîë÷àíèþ, ñèñòåìà ïàêåòîâ ñëåäóåò
    íåêîòîðûì ñîãëàøåíèÿì, êîòîðûå ïîìîãàþò ïîääåðæèâàòü ñòèëåâîå åäèíîîáðàçèå âî
    âñåõ ïàêåòàõ Symfony2. Äàâàéòå âçãëÿíåì íà ïàêåò AcmeHelloBundle, òàê êàê îí ñîäåðæèò íàèáîëåå îñíîâíûå ýëåìåíòû ïàêåòà:

    • Controller/ ñîäåðæèò êîíòðîëëåðû (íàïðèìåð HelloController.php);
    • Resources/cong/ ìåñòî äëÿ êîíôèãóðàöèîííûõ ôàéëîâ, âêëþ÷àÿ êîíôèãóðàöèþ
    ìàðøðóòèçàòîðà (íàïðèìåð routing.yml);
    • Resources/views/ øàáëîíû, ñãðóïïèðîâàííûå ïî èìåíè êîíòðîëëåðà (íàïðèìåð
    Hello/index.html.twig);
    • Resources/public/ ïóáëè÷íî äîñòóïíûå ðåñóðñû (êàðòèíêè, ñòèëè. . . ), êîòîðûå áóäóò ñêîïèðîâàíû èëè ñâÿçàíû ñèìâîëè÷åñêîé ññûëêîé ñ äèðåêòîðèåé web/ ÷åðåç
    êîìàíäó assets:install
    • Tests/ ñîäåðæèò âñå òåñòû.
    Ïàêåò ìîæåò áûòü êàê ìàëåíüêèì, òàê è áîëüøèì  â çàâèñèìîñòè îò çàäà÷è, êîòîðóþ
    îí ðåàëèçóåò. Îí ñîäåðæèò ëèøü òå ôàéëû, êîòîðûå íóæíû  è íè÷åãî áîëåå.
    Â äðóãèõ ãëàâàõ êíèãè âû òàêæå óçíàåòå êàê ðàáîòàòü ñ áàçîé äàííûõ, êàê ñîçäàâàòü è
    âàëèäèðîâàòü ôîðìû, ñîçäàâàòü ôàéëû ïåðåâîäîâ, ïèñàòü òåñòû è ìíîãî ÷åãî åù¼. Âñå
    ýòè îáúåêòû â ïàêåòå èìåþò îïðåäåëåííóþ ðîëü è ìåñòî.
    5.4.4 Íàñòðîéêà ïðèëîæåíèÿ

    Ïðèëîæåíèå ñîñòîèò èç íàáîðà ïàêåòîâ, ðåàëèçóþùèõ âñå íåîáõîäèìûå ôóíêöèè âàøåãî ïðèëîæåíèÿ. Êàæäûé ïàêåò ìîæåò áûòü íàñòðîåí ïðè ïîìîùè êîíôèãóðàöèîííûõ
    ôàéëîâ, íàïèñàííûõ íà YAML, XML èëè PHP. Ïî óìîë÷àíèþ, îñíîâíîé êîíôèãóðàöèîííûé ôàéë ðàñïîëîæåí â äèðåêòîðèè app/cong/ è íàçûâàåòñÿ cong.yml, cong.xml
    èëè cong.php, â çàâèñèìîñòè îò ïðåäïî÷èòàåìîãî âàìè ôîðìàòà:
    5.4. Ñîçäàíèå ñòðàíèö â Symfony2

    81

    Symfony Documentation, Âûïóñê 2.0

    • YAML

    # app/cong/cong.yml
    imports:
    - { resource: parameters.ini }
    - { resource: security.yml }
    framework:
    secret:
    %secret%
    charset:
    UTF-8
    router:
    { resource: "%kernel.root_dir%/cong/routing.yml" }
    form:
    true
    csrf_protection: true
    validation:
    { enable_annotations: true }
    templating:
    { engines: ['twig'] } #assets_version: SomeVersionScheme
    session:
    default_locale: %locale%
    auto_start: true
    # Twig Conguration
    twig:
    debug:
    %kernel.debug%
    strict_variables: %kernel.debug%
    # ...
    • XML

    <!-- app/cong/cong.xml -->
    <imports>
    <import resource="parameters.ini" />
    <import resource="security.yml" />
    </imports>
    <framework:cong charset="UTF-8" secret="%secret%">
    <framework:router resource="%kernel.root_dir%/cong/routing.xml" />
    <framework:form />
    <framework:csrf-protection />
    <framework:validation annotations="true" />
    <framework:templating assets-version="SomeVersionScheme">
    <framework:engine id="twig" />
    </framework:templating>
    <framework:session default-locale="%locale%" auto-start="true" />
    </framework:cong>
    <!-- Twig Conguration -->
    <twig:cong debug="%kernel.debug%" strict-variables="%kernel.debug%" />

    82

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    <!-- ... -->
    • PHP

    $this->import('parameters.ini');
    $this->import('security.yml');
    $container->loadFromExtension('framework', array(
    'secret'
    => '%secret%',
    'charset'
    => 'UTF-8',
    'router'
    => array('resource' => '%kernel.root_dir%/cong/routing.php'),
    'form'
    => array(),
    'csrf-protection' => array(),
    'validation'
    => array('annotations' => true),
    'templating'
    => array(
    'engines' => array('twig'),
    #'assets_version' => "SomeVersionScheme",
    ),
    'session' => array(
    'default_locale' => "%locale%",
    'auto_start' => true,
    ),
    ));
    // Twig Conguration
    $container->loadFromExtension('twig', array(
    'debug'
    => '%kernel.debug%',
    'strict_variables' => '%kernel.debug%',
    ));
    // ...
    Ïðèìå÷àíèå: Î òîì êàê âûáðàòü êàêîé ôàéë/ôîðìàò çàãðóæàòü  ìû ðàññìîòðèì â
    ñëåäóþùåé ñåêöèè `Environments`_.
    Êàæäûé ïàðàìåòð âåðõíåãî óðîâíÿ, íàïðèìåð framework èëè twig, îïðåäåëÿåò íàñòðîéêè êîíêðåòíîãî ïàêåòà. Íàïðèìåð, êëþ÷ framework îïðåäåëÿåò íàñòðîéêè ÿäðà Symfony
    FrameworkBundle è âêëþ÷àåò íàñòðîéêè ìàðøðóòèçàöèè, øàáëîíèçàòîðà è ïðî÷èõ êëþ÷åâûõ ñèñòåì.
    Ïîêà æå íàì íå ñòîèò áåñïîêîèòüñÿ î êîíêðåòíûõ íàñòðîéêàõ â êàæäîé ñåêöèè. Ôàéë
    íàñòðîåê ïî óìîë÷àíèþ ñîäåðæèò âñå íåîáõîäèìûå ïàðàìåòðû. Ïî õîäó ÷òåíèÿ ïðî÷åé
    äîêóìåíòàöèè âû îçíàêîìèòåñü ñî âñåìè ñïåöèôè÷åñêèìè íàñòðîéêàìè.

    5.4. Ñîçäàíèå ñòðàíèö â Symfony2

    83

    Symfony Documentation, Âûïóñê 2.0
    Ôîðìàòû êîíôèãóðàöèé
    Âî âñåõ ãëàâàõ êíèãè âñå ïðèìåðû êîíôèãóðàöèé áóäóò ïîêàçàíû âî âñåõ òðåõ
    ôîðìàòàõ (YAML, XML and PHP). Êàæäûé èç íèõ èìååò ñâîè äîñòîèíñòâà è íåäîñòàòêè. Âûáîð æå ôîðìàòà öåëèêîì çàâèñèò î âàøèõ ïðåäïî÷òåíèé:
    • YAML: Ïðîñòîé, ïîíÿòíûé è ÷èòàáåëüíûé;
    • XML: Â ðàçû áîëåå ìîùíûé, íåæåëè YAML. Ïîääåðæèâàåòñÿ ìíîãèìè IDE
    (autocompletion);
    • PHP: Î÷åíü ìîùíûé, íî ìåíåå ÷èòàáåëüíûé, ÷åì ñòàíäàðòíûå ôîðìàòû êîíôèãóðàöèîííûõ ôàéëîâ.

    5.4.5 Îêðóæåíèÿ

    Ïðèëîæåíèå ìîæíî çàïóñêàòü â ðàçëè÷íûõ îêðóæåíèÿõ. Ðàçëè÷íûå îêðóæåíèÿ èñïîëüçóþò îäèí è òîò æå PHP êîä (çà èñêëþ÷åíèåì ôðîíò-êîíòðîëëåðà), íî ìîãóò èìåòü
    ñîâåðøåííî ðàçëè÷íûå íàñòðîéêè. Íàïðèìåð, dev îêðóæåíèå âåäåò ëîã îøèáîê è çàìå÷àíèé, â òî âðåìÿ êàê prod îêðóæåíèå ëîããèðóåò òîëüêî îøèáêè.  dev íåêîòîðûå
    ôàéëû ïåðåñîçäàþòñÿ ïðè êàæäîì çàïðîñå, íî êåøèðóþòñÿ â prod îêðóæåíèè. Â òî æå
    âðåìÿ, âñå îêðóæåíèÿ îäíîâðåìåííî äîñòóïíû íà îäíîé è òîé æå ìàøèíå.
    Ïðîåêò Symfony2 ïî óìîë÷àíèþ èìååò òðè îêðóæåíèÿ (dev, test è prod), õîòÿ ñîçäàòü
    íîâîå îêðóæåíèå íå ñëîæíî. Âû ìîæåòå ñìîòðåòü âàøå ïðèëîæåíèå â ðàçëè÷íûõ îêðóæåíèÿõ ïðîñòî ìåíÿÿ ôðîíò-êîíòðîëëåðû â áðàóçåðå. Äëÿ òîãî ÷òîáû îòîáðàçèòü ïðèëîæåíèå â dev îêðóæåíèè, îòêðîéòå åãî ïðè ïîìîùè ôðîíò êîíòðîëëåðà app_dev.php:

    http://localhost/app_dev.php/hello/Ryan
    Åñëè æå âû õîòèòå ïîñìîòðåòü, êàê ïîâåä¼ò ñåáÿ ïðèëîæåíèå â ïðîäóêòîâîé ñðåäå, âû
    ìîæåòå âûçâàòü ôðîíò-êîíòðîëëåð prod:

    http://localhost/app.php/hello/Ryan
    Òàê êàê prod îêðóæåíèå îïòèìèçèðîâàíî äëÿ ñêîðîñòè, íàñòðîéêè, ìàðøðóòû è øàáëîíû Twig êîìïèëèðóþòñÿ â ïëîñêèå PHP êëàññû è êåøèðóþòñÿ. Êîãäà âû õîòèòå
    ïîñìîòðåòü èçìåíåíèÿ â ïðîäóêòîâîì îêðóæåíèè, âàì ïîòðåáóåòñÿ óäàëèòü ýòè ôàéëû
    ÷òîáû îíè ïåðåñîçäàëèñü àâòîìàòè÷åñêè:

    php app/console cache:clear --env=prod --no-debug
    Ïðèìå÷àíèå: Åñëè âû îòêðîåòå ôàéë web/app.php, âû îáíàðóæèòå, ÷òî îí îäíîçíà÷íî
    íàñòðîåí íà èñïîëüçîâàíèå prod îêðóæåíèÿ:

    $kernel = new AppKernel('prod', false);

    84

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    Âû ìîæåòå ñîçäàòü íîâûé ôðîíò-êîíòðîëëåð äëÿ íîâîãî îêðóæåíèÿ ïðîñòî ñêîïèðîâàâ
    ýòîò ôàéë è èçìåíèâ prod íà äðóãîå çíà÷åíèå.

    Ïðèìå÷àíèå: Òåñòîâîå îêðóæåíèå test èñïîëüçóåòñÿ ïðè çàïóñêå àâòîòåñòîâ è åãî íåëüçÿ íàïðÿìóþ îòêðûòü ÷åðåç áðàóçåð. Ïîäðîáíåå îá ýòî ìîæíî ïî÷èòàòü â ãëàâå ïðî
    òåñòèðîâàíèå.

    Íàñòðîéêà îêðóæåíèé
    Êëàññ AppKernel îòâå÷àåò çà çàãðóçêó êîíôèãóðàöèîííûõ ôàéëîâ:

    // app/AppKernel.php
    public function registerContainerConguration(LoaderInterface $loader)
    {
    $loader->load(__DIR__.'/cong/cong_'.$this->getEnvironment().'.yml');
    }
    Ìû óæå çíàåì, ÷òî ðàñøèðåíèå .yml ìîæåò áûòü èçìåíåíî íà .xml èëè .php åñëè âû
    ïðåäïî÷èòàåòå èñïîëüçîâàòü XML èëè PHP. Èìåéòå òàêæå â âèäó, ÷òî êàæäîå îêðóæåíèå çàãðóæàåò ñâîè ñîáñòâåííûå íàñòðîéêè. Ðàññìîòðèì êîíôèãóðàöèîííûé ôàéë äëÿ
    dev îêðóæåíèÿ.

    • YAML

    # app/cong/cong_dev.yml
    imports:
    - { resource: cong.yml }
    framework:
    router: { resource: "%kernel.root_dir%/cong/routing_dev.yml" }
    proler: { only_exceptions: false }
    # ...
    • XML

    <!-- app/cong/cong_dev.xml -->
    <imports>
    <import resource="cong.xml" />
    </imports>
    <framework:cong>
    <framework:router resource="%kernel.root_dir%/cong/routing_dev.xml" />
    <framework:proler only-exceptions="false" />
    </framework:cong>
    5.4. Ñîçäàíèå ñòðàíèö â Symfony2

    85

    Symfony Documentation, Âûïóñê 2.0

    <!-- ... -->
    • PHP

    // app/cong/cong_dev.php
    $loader->import('cong.php');
    $container->loadFromExtension('framework', array(
    'router' => array('resource' => '%kernel.root_dir%/cong/routing_dev.php'),
    'proler' => array('only-exceptions' => false),
    ));
    // ...
    Êëþ÷ imports ïîõîæ ïî äåéñòâèþ íà âûðàæåíèå include â PHP è ãàðàíòèðóåò ÷òî ãëàâíûé êîíôèãóðàöèîííûé ôàéë (cong.yml) áóäåò çàãðóæåí â ïåðâóþ î÷åðåäü. Îñòàëüíîé
    êîä êîððåêòèðóåò êîíôèãóðàöèþ ïî-óìîë÷àíèþ äëÿ óâåëè÷åíèÿ ïîðîãà ëîããèðîâàíèÿ
    è ïðî÷èõ íàñòðîåê, ñïåöèôè÷íûõ äëÿ ðàçðàáîòêè.
    Îáà îêðóæåíèÿ  prod è test ñëåäóþò òîé æå ìîäåëè: êàæäîå îêðóæåíèå èìïîðòèðóåò
    áàçîâûå íàñòðîéêè è ìîäèôèöèðóåò èõ çíà÷åíèÿ äëÿ ñâîèõ íóæä. Ýòî ïðîñòî ñîãëàøåíèå, êîòîðîå ïîçâîëÿåò ïåðå-èñïîëüçîâàòü íàñòðîéêè è ìåíÿòü òîëüêî ÷àñòè â çàâèñèìîñòè îò îêðóæåíèÿ.
    5.4.6 Çàêëþ÷åíèå

    Ïîçäðàâëÿåì! Âû óñâîèëè âñå ôóíäàìåíòàëüíûå àñïåêòû Symfony2 è îáíàðóæèëè, êàêèìè ë¼ãêèìè è â òî æå âðåìÿ ãèáêèìè îíè ìîãóò áûòü. È, ïîñêîëüêó íà ïîäõîäå åù¼
    ìíîãî èíòåðåñíîãî, îáÿçàòåëüíî çàïîìíèòå ñëåäóþùèå ïîëîæåíèÿ:

    • Ñîçäàíèå ñòðàíèö  ýòî òðè ïðîñòûõ øàãà, âêëþ÷àþùèõ ìàðøðóò, êîíòðîëëåð è
    (îïöèîíàëüíî) øàáëîí.
    • Êàæäîå ïðèëîæåíèå äîëæíî ñîñòîÿòü òîëüêî èç 4õ äèðåêòîðèé: web/ (web assets
    è front controllers), app/ (íàñòðîéêè), src/ (âàøè ïàêåòû), è vendor/ (ñòîðîííèå
    áèáëèîòåêè);
    • Êàæäàÿ ôóíêöèÿ â Symfony2 (âêëþ÷àÿ ÿäðî ôðåéìâîðêà) äîëæíà ðàñïîëàãàòüñÿ
    âíóòðè ïàêåòà, êîòîðûé ïðåäñòàâëÿåò ñîáîé ñòðóêòóðèðîâàííûé íàáîð ôàéëîâ,
    ðåàëèçóþùèõ ýòó ôóíêöèþ;
    • íàñòðîéêè êàæäîãî ïàêåòà ðàñïîëàãàþòñÿ â äèðåêòîðèè app/cong è ìîãóò áûòü
    çàïèñàíû â ôîðìàòå YAML, XML èëè PHP;
    • êàæäîå îêðóæåíèå äîñòóïíî ÷åðåç ñâîé îòäåëüíûé ôðîíò-êîíòðîëëåð (íàïðèìåð
    app.php è app_dev.php) è çàãðóæàåò îòäåëüíûé ôàéë íàñòðîåê.

    86

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    Äàëåå, êàæäàÿ ãëàâà êíèãè ïîçíàêîìèò âàñ ñ âñå áîëåå è áîëåå ìîùíûìè èíñòðóìåíòàìè
    è áîëåå ãëóáîêèìè êîíöåïöèÿìè. ×åì áîëüøå âû çíàåòå î Symfony2, òåì áîëüøå âû
    áóäåòå öåíèòü ãèáêîñòü åãî àðõèòåêòóðû è åãî îáøèðíûå âîçìîæíîñòè äëÿ áûñòðîé
    ðàçðàáîòêè ïðèëîæåíèé.

    5.5 Controller

    A controller is a PHP function you create that takes information from the HTTP request and
    constructs and returns an HTTP response (as a Symfony2 Response object). The response
    could be an HTML page, an XML document, a serialized JSON array, an image, a redirect,
    a 404 error or anything else you can dream up. The controller contains whatever arbitrary
    logic your application needs to render the content of a page.
    To see how simple this is, let's look at a Symfony2 controller in action. The following
    controller would render a page that simply prints Hello world!:

    use Symfony\Component\HttpFoundation\Response;
    public function helloAction()
    {
    return new Response('Hello world!');
    }
    The goal of a controller is always the same: create and return a Response object. Along the
    way, it might read information from the request, load a database resource, send an email, or
    set information on the user's session. But in all cases, the controller will eventually return
    the Response object that will be delivered back to the client.
    There's no magic and no other requirements to worry about! Here are a few common
    examples:

    • Controller A prepares a Response object representing the content for the homepage of
    the site.
    • Controller B reads the slug parameter from the request to load a blog entry from the
    database and create a Response object displaying that blog. If the slug can't be found
    in the database, it creates and returns a Response object with a 404 status code.
    • Controller C handles the form submission of a contact form. It reads the form
    information from the request, saves the contact information to the database and emails
    the contact information to the webmaster. Finally, it creates a Response object that
    redirects the client's browser to the contact form thank you page.

    5.5. Controller

    87

    Symfony Documentation, Âûïóñê 2.0
    5.5.1 Requests, Controller, Response Lifecycle

    Every request handled by a Symfony2 project goes through the same simple lifecycle. The
    framework takes care of the repetitive tasks and ultimately executes a controller, which
    houses your custom application code:
    1. Each request is handled by a single front controller le (e.g. app.php or app_dev.php)
    that bootstraps the application;
    2. The Router reads information from the request (e.g. the URI), nds a route that
    matches that information, and reads the _controller parameter from the route;
    3. The controller from the matched route is executed and the code inside the controller
    creates and returns a Response object;
    4. The HTTP headers and content of the Response object are sent back to the client.
    Creating a page is as easy as creating a controller (#3) and making a route that maps a
    URL to that controller (#2).
    Ïðèìå÷àíèå: Though similarly named, a front controller is dierent from the controllers
    we'll talk about in this chapter. A front controller is a short PHP le that lives in your
    web directory and through which all requests are directed. A typical application will
    have a production front controller (e.g. app.php) and a development front controller (e.g.
    app_dev.php). You'll likely never need to edit, view or worry about the front controllers in
    your application.

    5.5.2 A Simple Controller

    While a controller can be any PHP callable (a function, method on an object, or a Closure),
    in Symfony2, a controller is usually a single method inside a controller object. Controllers
    are also called actions.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    // src/Acme/HelloBundle/Controller/HelloController.php
    namespace Acme\HelloBundle\Controller;
    use Symfony\Component\HttpFoundation\Response;
    class HelloController
    {
    public function indexAction($name)
    {
    return new Response('<html><body>Hello '.$name.'!</body></html>');
    }
    }

    88

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    Ñîâåò: Note that the controller is the indexAction method, which lives inside a controller
    class (HelloController). Don't be confused by the naming: a controller class is simply a
    convenient way to group several controllers/actions together. Typically, the controller class
    will house several controllers/actions (e.g. updateAction, deleteAction, etc).
    This controller is pretty straightforward, but let's walk through it:

    • line 3: Symfony2 takes advantage of PHP 5.3 namespace functionality to namespace
    the entire controller class. The use keyword imports the Response class, which our
    controller must return.
    • line 6: The class name is the concatenation of a name for the controller class (i.e. Hello)
    and the word Controller. This is a convention that provides consistency to controllers
    and allows them to be referenced only by the rst part of the name (i.e. Hello) in the
    routing conguration.
    • line 8: Each action in a controller class is suxed with Action and is referenced in the
    routing conguration by the action's name (index). In the next section, you'll create
    a route that maps a URI to this action. You'll learn how the route's placeholders
    ({name}) become arguments to the action method ($name).
    • line 10: The controller creates and returns a Response object.
    5.5.3 Mapping a URL to a Controller

    The new controller returns a simple HTML page. To actually view this page in your browser,
    you need to create a route, which maps a specic URL pattern to the controller:

    • YAML

    # app/cong/routing.yml
    hello:
    pattern:
    /hello/{name}
    defaults: { _controller: AcmeHelloBundle:Hello:index }
    • XML

    <!-- app/cong/routing.xml -->
    <route id="hello" pattern="/hello/{name}">
    <default key="_controller">AcmeHelloBundle:Hello:index</default>
    </route>
    • PHP

    // app/cong/routing.php
    $collection->add('hello', new Route('/hello/{name}', array(
    5.5. Controller

    89

    Symfony Documentation, Âûïóñê 2.0

    '_controller' => 'AcmeHelloBundle:Hello:index',
    )));
    Going to /hello/ryan now executes the HelloController::indexAction() controller and passes
    in ryan for the $name variable. Creating a page means simply creating a controller method
    and associated route.
    Notice the syntax used to refer to the controller: AcmeHelloBundle:Hello:index. Symfony2
    uses a exible string notation to refer to dierent controllers. This is the most common syntax
    and tells Symfony2 to look for a controller class called HelloController inside a bundle named
    AcmeHelloBundle. The method indexAction() is then executed.
    For more details on the string format used to reference dierent controllers, see Controller
    Naming Pattern.
    Ïðèìå÷àíèå: This example places the routing conguration directly in the app/cong/
    directory. A better way to organize your routes is to place each route in the bundle it
    belongs to. For more information on this, see Including External Routing Resources.

    Ñîâåò: You can learn much more about the routing system in the Routing chapter.

    Route Parameters as Controller Arguments
    You already know that the _controller parameter AcmeHelloBundle:Hello:index refers to
    a HelloController::indexAction() method that lives inside the AcmeHelloBundle bundle.
    What's more interesting is the arguments that are passed to that method:

    <?php
    // src/Acme/HelloBundle/Controller/HelloController.php
    namespace Acme\HelloBundle\Controller;
    use Symfony\Bundle\FrameworkBundle\Controller\Controller;
    class HelloController extends Controller
    {
    public function indexAction($name)
    {
    // ...
    }
    }
    The controller has a single argument, $name, which corresponds to the {name} parameter
    from the matched route (ryan in our example). In fact, when executing your controller,

    90

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    Symfony2 matches each argument of the controller with a parameter from the matched
    route. Take the following example:

    • YAML

    # app/cong/routing.yml
    hello:
    pattern:
    /hello/{rst_name}/{last_name}
    defaults: { _controller: AcmeHelloBundle:Hello:index, color: green }
    • XML

    <!-- app/cong/routing.xml -->
    <route id="hello" pattern="/hello/{rst_name}/{last_name}">
    <default key="_controller">AcmeHelloBundle:Hello:index</default>
    <default key="color">green</default>
    </route>
    • PHP

    // app/cong/routing.php
    $collection->add('hello', new Route('/hello/{rst_name}/{last_name}', array(
    '_controller' => 'AcmeHelloBundle:Hello:index',
    'color'
    => 'green',
    )));
    The controller for this can take several arguments:

    public function indexAction($rst_name, $last_name, $color)
    {
    // ...
    }
    Notice that both placeholder variables ({rst_name}, {last_name}) as well as the default
    color variable are available as arguments in the controller. When a route is matched, the
    placeholder variables are merged with the defaults to make one array that's available to your
    controller.
    Mapping route parameters to controller arguments is easy and exible. Keep the following
    guidelines in mind while you develop.

    • The order of the controller arguments does not matter
    Symfony is able to match the parameter names from the route to the variable
    names in the controller method's signature. In other words, it realizes that
    the {last_name} parameter matches up with the $last_name argument. The
    arguments of the controller could be totally reordered and still work perfectly:

    public function indexAction($last_name, $color, $rst_name)
    {
    5.5. Controller

    91

    Symfony Documentation, Âûïóñê 2.0

    }

    // ..

    • Each required controller argument must match up with a routing parameter
    The following would throw a RuntimeException because there is no foo
    parameter dened in the route:

    public function indexAction($rst_name, $last_name, $color, $foo)
    {
    // ..
    }
    Making the argument optional, however, is perfectly ok. The following
    example would not throw an exception:

    public function indexAction($rst_name, $last_name, $color, $foo = 'bar')
    {
    // ..
    }
    • Not all routing parameters need to be arguments on your controller
    If, for example, the last_name weren't important for your controller, you
    could omit it entirely:

    public function indexAction($rst_name, $color)
    {
    // ..
    }
    Ñîâåò: Every route also has a special _route parameter, which is equal to the name of the
    route that was matched (e.g. hello). Though not usually useful, this is equally available as a
    controller argument.

    The Request as a Controller Argument
    For convenience, you can also have Symfony pass you the Request object as an argument to
    your controller. This is especially convenient when you're working with forms, for example:

    use Symfony\Component\HttpFoundation\Request;
    public function updateAction(Request $request)
    {
    $form = $this->createForm(...);
    92

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    }

    $form->bindRequest($request);
    // ...

    5.5.4 The Base Controller Class

    For convenience, Symfony2 comes with a base Controller class that assists with some of the
    most common controller tasks and gives your controller class access to any resource it might
    need. By extending this Controller class, you can take advantage of several helper methods.
    Add the use statement atop the Controller class and then modify the HelloController to
    extend it:

    // src/Acme/HelloBundle/Controller/HelloController.php
    namespace Acme\HelloBundle\Controller;
    use Symfony\Bundle\FrameworkBundle\Controller\Controller;
    use Symfony\Component\HttpFoundation\Response;
    class HelloController extends Controller
    {
    public function indexAction($name)
    {
    return new Response('<html><body>Hello '.$name.'!</body></html>');
    }
    }
    This doesn't actually change anything about how your controller works. In the next section,
    you'll learn about the helper methods that the base controller class makes available. These
    methods are just shortcuts to using core Symfony2 functionality that's available to you with
    or without the use of the base Controller class. A great way to see the core functionality
    in action is to look in the Symfony\Bundle\FrameworkBundle\Controller\Controller class
    itself.
    Ñîâåò:
    Extending
    the
    base
    class
    is
    optional
    in
    Symfony;
    it
    contains useful shortcuts but nothing mandatory. You can also extend
    Symfony\Component\DependencyInjection\ContainerAware. The service container object
    will then be accessible via the container property.

    Ïðèìå÷àíèå: You can also dene your Controllers as Services.

    5.5. Controller

    93

    Symfony Documentation, Âûïóñê 2.0
    5.5.5 Common Controller Tasks

    Though a controller can do virtually anything, most controllers will perform the same basic
    tasks over and over again. These tasks, such as redirecting, forwarding, rendering templates
    and accessing core services, are very easy to manage in Symfony2.
    Redirecting
    If you want to redirect the user to another page, use the redirect() method:

    public function indexAction()
    {
    return $this->redirect($this->generateUrl('homepage'));
    }
    The generateUrl() method is just a helper function that generates the URL for a given route.
    For more information, see the Routing chapter.
    By default, the redirect() method performs a 302 (temporary) redirect. To perform a 301
    (permanent) redirect, modify the second argument:

    public function indexAction()
    {
    return $this->redirect($this->generateUrl('homepage'), 301);
    }
    Ñîâåò: The redirect() method is simply a shortcut that creates a Response object that
    specializes in redirecting the user. It's equivalent to:

    use Symfony\Component\HttpFoundation\RedirectResponse;
    return new RedirectResponse($this->generateUrl('homepage'));

    Forwarding
    You can also easily forward to another controller internally with the forward() method.
    Instead of redirecting the user's browser, it makes an internal sub-request, and calls the
    specied controller. The forward() method returns the Response object that's returned from
    that controller:

    public function indexAction($name)
    {
    $response = $this->forward('AcmeHelloBundle:Hello:fancy', array(
    'name' => $name,
    94

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    ));

    'color' => 'green'

    // further modify the response or return it directly
    }

    return $response;

    Notice that the forward() method uses the same string representation of the controller used
    in the routing conguration. In this case, the target controller class will be HelloController
    inside some AcmeHelloBundle. The array passed to the method becomes the arguments
    on the resulting controller. This same interface is used when embedding controllers into
    templates (see Embedding Controllers). The target controller method should look something
    like the following:

    public function fancyAction($name, $color)
    {
    // ... create and return a Response object
    }
    And just like when creating a controller for a route, the order of the arguments to fancyAction
    doesn't matter. Symfony2 matches the index key names (e.g. name) with the method
    argument names (e.g. $name). If you change the order of the arguments, Symfony2 will
    still pass the correct value to each variable.
    Ñîâåò: Like other base Controller methods, the forward method is just a shortcut for core
    Symfony2 functionality. A forward can be accomplished directly via the http_kernel service.
    A forward returns a Response object:

    $httpKernel = $this->container->get('http_kernel');
    $response = $httpKernel->forward('AcmeHelloBundle:Hello:fancy', array(
    'name' => $name,
    'color' => 'green',
    ));

    Rendering Templates
    Though not a requirement, most controllers will ultimately render a template that's
    responsible for generating the HTML (or other format) for the controller. The renderView()
    method renders a template and returns its content. The content from the template can be
    used to create a Response object:

    $content = $this->renderView('AcmeHelloBundle:Hello:index.html.twig', array('name' => $name));

    5.5. Controller

    95

    Symfony Documentation, Âûïóñê 2.0

    return new Response($content);
    This can even be done in just one step with the render() method, which returns a Response
    object containing the content from the template:

    return $this->render('AcmeHelloBundle:Hello:index.html.twig', array('name' => $name));
    In both cases, the Resources/views/Hello/index.html.twig
    AcmeHelloBundle will be rendered.

    template

    inside

    the

    The Symfony templating engine is explained in great detail in the Templating chapter.
    Ñîâåò: The renderView method is a shortcut to direct use of the templating service. The
    templating service can also be used directly:

    $templating = $this->get('templating');
    $content = $templating->render('AcmeHelloBundle:Hello:index.html.twig', array('name' => $name));

    Accessing other Services
    When extending the base controller class, you can access any Symfony2 service via the get()
    method. Here are several common services you might need:

    $request = $this->getRequest();
    $templating = $this->get('templating');
    $router = $this->get('router');
    $mailer = $this->get('mailer');
    There are countless other services available and you are encouraged to dene your own. To
    list all available services, use the container:debug console command:

    php app/console container:debug
    For more information, see the Service Container chapter.
    5.5.6 Managing Errors and 404 Pages

    When things are not found, you should play well with the HTTP protocol and return a 404
    response. To do this, you'll throw a special type of exception. If you're extending the base
    controller class, do the following:

    96

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    public function indexAction()
    {
    $product = // retrieve the object from database
    if (!$product) {
    throw $this->createNotFoundException('The product does not exist');
    }
    }

    return $this->render(...);

    The createNotFoundException() method creates a special NotFoundHttpException object,
    which ultimately triggers a 404 HTTP response inside Symfony.
    Of course, you're free to throw any Exception class in your controller - Symfony2 will
    automatically return a 500 HTTP response code.

    throw new \Exception('Something went wrong!');
    In every case, a styled error page is shown to the end user and a full debug error page is
    shown to the developer (when viewing the page in debug mode). Both of these error pages
    can be customized. For details, read the Êàê ñîçäàòü ñîáñòâåííûå ñòðàíèöû îøèáîê
    cookbook recipe.
    5.5.7 Managing the Session

    Symfony2 provides a nice session object that you can use to store information about the user
    (be it a real person using a browser, a bot, or a web service) between requests. By default,
    Symfony2 stores the attributes in a cookie by using the native PHP sessions.
    Storing and retrieving information from the session can be easily achieved from any controller:

    $session = $this->getRequest()->getSession();
    // store an attribute for reuse during a later user request
    $session->set('foo', 'bar');
    // in another controller for another request
    $foo = $session->get('foo');
    // set the user locale
    $session->setLocale('fr');
    These attributes will remain on the user for the remainder of that user's session.

    5.5. Controller

    97

    Symfony Documentation, Âûïóñê 2.0
    Flash Messages
    You can also store small messages that will be stored on the user's session for exactly one
    additional request. This is useful when processing a form: you want to redirect and have
    a special message shown on the next request. These types of messages are called ash
    messages.
    For example, imagine you're processing a form submit:

    public function updateAction()
    {
    $form = $this->createForm(...);
    $form->bindRequest($this->getRequest());
    if ($form->isValid()) {
    // do some sort of processing
    $this->get('session')->setFlash('notice', 'Your changes were saved!');
    }
    }

    return $this->redirect($this->generateUrl(...));

    return $this->render(...);

    After processing the request, the controller sets a notice ash message and then redirects.
    The name (notice) isn't signicant - it's just what you're using to identify the type of the
    message.
    In the template of the next action, the following code could be used to render the notice
    message:

    • Twig

    {% if app.session.hasFlash('notice') %}
    <div class="ash-notice">
    {{ app.session.ash('notice') }}
    </div>
    {% endif %}
    • PHP

    <?php if ($view['session']->hasFlash('notice')): ?>
    <div class="ash-notice">
    <?php echo $view['session']->getFlash('notice') ?>
    </div>
    <?php endif; ?>
    By design, ash messages are meant to live for exactly one request (they're gone in a ash).
    98

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    They're designed to be used across redirects exactly as you've done in this example.
    5.5.8 The Response Object

    The only requirement for a controller is to return a Response object. The
    Symfony\Component\HttpFoundation\Response class is a PHP abstraction around the
    HTTP response - the text-based message lled with HTTP headers and content that's sent
    back to the client:

    // create a simple Response with a 200 status code (the default)
    $response = new Response('Hello '.$name, 200);
    // create a JSON-response with a 200 status code
    $response = new Response(json_encode(array('name' => $name)));
    $response->headers->set('Content-Type', 'application/json');
    Ñîâåò: The headers property is a Symfony\Component\HttpFoundation\HeaderBag object
    with several useful methods for reading and mutating the Response headers. The header
    names are normalized so that using Content-Type is equivalent to content-type or even
    content_type.

    5.5.9 The Request Object

    Besides the values of the routing placeholders, the controller also has access to the Request
    object when extending the base Controller class:

    $request = $this->getRequest();
    $request->isXmlHttpRequest(); // is it an Ajax request?
    $request->getPreferredLanguage(array('en', 'fr'));
    $request->query->get('page'); // get a $_GET parameter
    $request->request->get('page'); // get a $_POST parameter
    Like the Response object, the request headers are stored in a HeaderBag object and are
    easily accessible.

    5.5. Controller

    99

    Symfony Documentation, Âûïóñê 2.0
    5.5.10 Final Thoughts

    Whenever you create a page, you'll ultimately need to write some code that contains the
    logic for that page. In Symfony, this is called a controller, and it's a PHP function that can
    do anything it needs in order to return the nal Response object that will be returned to
    the user.
    To make life easier, you can choose to extend a base Controller class, which contains shortcut
    methods for many common controller tasks. For example, since you don't want to put HTML
    code in your controller, you can use the render() method to render and return the content
    from a template.
    In other chapters, you'll see how the controller can be used to persist and fetch objects from
    a database, process form submissions, handle caching and more.
    5.5.11 Learn more from the Cookbook

    • Êàê ñîçäàòü ñîáñòâåííûå ñòðàíèöû îøèáîê
    • How to dene Controllers as Services

    5.6 Routing

    Beautiful URLs are an absolute must for any serious web application. This means leaving
    behind ugly URLs like index.php?article_id=57 in favor of something like /read/intro-tosymfony.
    Having exibility is even more important. What if you need to change the URL of a page
    from /blog to /news? How many links should you need to hunt down and update to make
    the change? If you're using Symfony's router, the change is simple.
    The Symfony2 router lets you dene creative URLs that you map to dierent areas of your
    application. By the end of this chapter, you'll be able to:

    • Create complex routes that map to controllers
    • Generate URLs inside templates and controllers
    • Load routing resources from bundles (or anywhere else)
    • Debug your routes
    5.6.1 Routing in Action

    A route is a map from a URL pattern to a controller. For example, suppose you want to
    match any URL like /blog/my-post or /blog/all-about-symfony and send it to a controller
    100

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    that can look up and render that blog entry. The route is simple:

    • YAML

    # app/cong/routing.yml
    blog_show:
    pattern: /blog/{slug}
    defaults: { _controller: AcmeBlogBundle:Blog:show }
    • XML

    <!-- app/cong/routing.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <routes xmlns="http://symfony.com/schema/routing"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing
    <route id="blog_show" pattern="/blog/{slug}">
    <default key="_controller">AcmeBlogBundle:Blog:show</default>
    </route>
    </routes>
    • PHP

    // app/cong/routing.php
    use Symfony\Component\Routing\RouteCollection;
    use Symfony\Component\Routing\Route;
    $collection = new RouteCollection();
    $collection->add('blog_show', new Route('/blog/{slug}', array(
    '_controller' => 'AcmeBlogBundle:Blog:show',
    )));
    return $collection;
    The pattern dened by the blog_show route acts like /blog/* where the wildcard is given the
    name slug. For the URL /blog/my-blog-post, the slug variable gets a value of my-blog-post,
    which is available for you to use in your controller (keep reading).
    The _controller parameter is a special key that tells Symfony which controller should be
    executed when a URL matches this route. The _controller string is called the logical name.
    It follows a pattern that points to a specic PHP class and method:

    // src/Acme/BlogBundle/Controller/BlogController.php
    namespace Acme\BlogBundle\Controller;
    use Symfony\Bundle\FrameworkBundle\Controller\Controller;
    class BlogController extends Controller
    5.6. Routing

    101

    Symfony Documentation, Âûïóñê 2.0

    {

    }

    public function showAction($slug)
    {
    $blog = // use the $slug varible to query the database

    }

    return $this->render('AcmeBlogBundle:Blog:show.html.twig', array(
    'blog' => $blog,
    ));

    Congratulations! You've just created your rst route and connected it to a controller. Now,
    when you visit /blog/my-post, the showAction controller will be executed and the $slug
    variable will be equal to my-post.
    This is the goal of the Symfony2 router: to map the URL of a request to a controller. Along
    the way, you'll learn all sorts of tricks that make mapping even the most complex URLs easy.
    5.6.2 Routing: Under the Hood

    When a request is made to your application, it contains an address to the exact resource
    that the client is requesting. This address is called the URL, (or URI), and could be /contact,
    /blog/read-me, or anything else. Take the following HTTP request for example:

    GET /blog/my-blog-post
    The goal of the Symfony2 routing system is to parse this URL and determine which controller
    should be executed. The whole process looks like this:
    1. The request is handled by the Symfony2 front controller (e.g. app.php);
    2. The Symfony2 core (i.e. Kernel) asks the router to inspect the request;
    3. The router matches the incoming URL to a specic route and returns information
    about the route, including the controller that should be executed;
    4. The Symfony2 Kernel executes the controller, which ultimately returns a Response
    object.
    5.6.3 Creating Routes

    Symfony loads all the routes for your application from a single routing conguration le.
    The le is usually app/cong/routing.yml, but can be congured to be anything (including
    an XML or PHP le) via the application conguration le:

    • YAML
    102

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    Ðèñ. 5.2: The routing layer is a tool that translates the incoming URL into a specic controller
    to execute.

    # app/cong/cong.yml
    framework:
    # ...
    router:
    { resource: "%kernel.root_dir%/cong/routing.yml" }
    • XML

    <!-- app/cong/cong.xml -->
    <framework:cong ...>
    <!-- ... -->
    <framework:router resource="%kernel.root_dir%/cong/routing.xml" />
    </framework:cong>
    • PHP

    // app/cong/cong.php
    $container->loadFromExtension('framework', array(
    // ...
    'router'
    => array('resource' => '%kernel.root_dir%/cong/routing.php'),
    ));
    Ñîâåò: Even though all routes are loaded from a single le, it's common practice to
    include additional routing resources from inside the le. See the Including External Routing
    Resources section for more information.

    5.6. Routing

    103

    Symfony Documentation, Âûïóñê 2.0
    Basic Route Conguration
    Dening a route is easy, and a typical application will have lots of routes. A basic route
    consists of just two parts: the pattern to match and a defaults array:

    • YAML

    _welcome:
    pattern: /
    defaults: { _controller: AcmeDemoBundle:Main:homepage }
    • XML

    <?xml version="1.0" encoding="UTF-8" ?>

    <routes xmlns="http://symfony.com/schema/routing"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing
    <route id="_welcome" pattern="/">
    <default key="_controller">AcmeDemoBundle:Main:homepage</default>
    </route>
    </routes>
    • PHP

    use Symfony\Component\Routing\RouteCollection;
    use Symfony\Component\Routing\Route;
    $collection = new RouteCollection();
    $collection->add('_welcome', new Route('/', array(
    '_controller' => 'AcmeDemoBundle:Main:homepage',
    )));
    return $collection;
    This route matches the homepage (/) and maps it to the AcmeDemoBundle:Main:homepage
    controller. The _controller string is translated by Symfony2 into an actual PHP function and
    executed. That process will be explained shortly in the Controller Naming Pattern section.
    Routing with Placeholders
    Of course the routing system supports much more interesting routes. Many routes will contain
    one or more named wildcard placeholders:

    • YAML

    104

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    blog_show:
    pattern: /blog/{slug}
    defaults: { _controller: AcmeBlogBundle:Blog:show }
    • XML

    <?xml version="1.0" encoding="UTF-8" ?>

    <routes xmlns="http://symfony.com/schema/routing"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing
    <route id="blog_show" pattern="/blog/{slug}">
    <default key="_controller">AcmeBlogBundle:Blog:show</default>
    </route>
    </routes>
    • PHP

    use Symfony\Component\Routing\RouteCollection;
    use Symfony\Component\Routing\Route;
    $collection = new RouteCollection();
    $collection->add('blog_show', new Route('/blog/{slug}', array(
    '_controller' => 'AcmeBlogBundle:Blog:show',
    )));
    return $collection;
    The pattern will match anything that looks like /blog/*. Even better, the value matching
    the {slug} placeholder will be available inside your controller. In other words, if the URL
    is /blog/hello-world, a $slug variable, with a value of hello-world, will be available in the
    controller. This can be used, for example, to load the blog post matching that string.
    The pattern will not, however, match simply /blog. That's because, by default, all
    placeholders are required. This can be changed by adding a placeholder value to the defaults
    array.
    Required and Optional Placeholders
    To make things more exciting, add a new route that displays a list of all the available blog
    posts for this imaginary blog application:

    • YAML

    blog:
    pattern: /blog
    5.6. Routing

    105

    Symfony Documentation, Âûïóñê 2.0

    defaults: { _controller: AcmeBlogBundle:Blog:index }
    • XML

    <?xml version="1.0" encoding="UTF-8" ?>

    <routes xmlns="http://symfony.com/schema/routing"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing
    <route id="blog" pattern="/blog">
    <default key="_controller">AcmeBlogBundle:Blog:index</default>
    </route>
    </routes>
    • PHP

    use Symfony\Component\Routing\RouteCollection;
    use Symfony\Component\Routing\Route;
    $collection = new RouteCollection();
    $collection->add('blog', new Route('/blog', array(
    '_controller' => 'AcmeBlogBundle:Blog:index',
    )));
    return $collection;
    So far, this route is as simple as possible - it contains no placeholders and will only match
    the exact URL /blog. But what if you need this route to support pagination, where /blog/2
    displays the second page of blog entries? Update the route to have a new {page} placeholder:

    • YAML

    blog:
    pattern: /blog/{page}
    defaults: { _controller: AcmeBlogBundle:Blog:index }
    • XML

    <?xml version="1.0" encoding="UTF-8" ?>

    <routes xmlns="http://symfony.com/schema/routing"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing
    <route id="blog" pattern="/blog/{page}">
    <default key="_controller">AcmeBlogBundle:Blog:index</default>
    </route>
    106

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    </routes>
    • PHP

    use Symfony\Component\Routing\RouteCollection;
    use Symfony\Component\Routing\Route;
    $collection = new RouteCollection();
    $collection->add('blog', new Route('/blog/{page}', array(
    '_controller' => 'AcmeBlogBundle:Blog:index',
    )));
    return $collection;
    Like the {slug} placeholder before, the value matching {page} will be available inside your
    controller. Its value can be used to determine which set of blog posts to display for the given
    page.
    But hold on! Since placeholders are required by default, this route will no longer match on
    simply /blog. Instead, to see page 1 of the blog, you'd need to use the URL /blog/1! Since
    that's no way for a rich web app to behave, modify the route to make the {page} parameter
    optional. This is done by including it in the defaults collection:

    • YAML

    blog:
    pattern: /blog/{page}
    defaults: { _controller: AcmeBlogBundle:Blog:index, page: 1 }
    • XML

    <?xml version="1.0" encoding="UTF-8" ?>

    <routes xmlns="http://symfony.com/schema/routing"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing
    <route id="blog" pattern="/blog/{page}">
    <default key="_controller">AcmeBlogBundle:Blog:index</default>
    <default key="page">1</default>
    </route>
    </routes>
    • PHP

    use Symfony\Component\Routing\RouteCollection;
    use Symfony\Component\Routing\Route;
    $collection = new RouteCollection();
    5.6. Routing

    107

    Symfony Documentation, Âûïóñê 2.0

    $collection->add('blog', new Route('/blog/{page}', array(
    '_controller' => 'AcmeBlogBundle:Blog:index',
    'page' => 1,
    )));
    return $collection;
    By adding page to the defaults key, the {page} placeholder is no longer required. The URL
    /blog will match this route and the value of the page parameter will be set to 1. The URL
    /blog/2 will also match, giving the page parameter a value of 2. Perfect.
    /blog
    /blog/1
    /blog/2

    {page} = 1
    {page} = 1
    {page} = 2

    Adding Requirements
    Take a quick look at the routes that have been created so far:

    • YAML

    blog:
    pattern: /blog/{page}
    defaults: { _controller: AcmeBlogBundle:Blog:index, page: 1 }
    blog_show:
    pattern: /blog/{slug}
    defaults: { _controller: AcmeBlogBundle:Blog:show }
    • XML

    <?xml version="1.0" encoding="UTF-8" ?>

    <routes xmlns="http://symfony.com/schema/routing"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing
    <route id="blog" pattern="/blog/{page}">
    <default key="_controller">AcmeBlogBundle:Blog:index</default>
    <default key="page">1</default>
    </route>
    <route id="blog_show" pattern="/blog/{slug}">
    <default key="_controller">AcmeBlogBundle:Blog:show</default>
    </route>
    </routes>

    108

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    • PHP

    use Symfony\Component\Routing\RouteCollection;
    use Symfony\Component\Routing\Route;
    $collection = new RouteCollection();
    $collection->add('blog', new Route('/blog/{page}', array(
    '_controller' => 'AcmeBlogBundle:Blog:index',
    'page' => 1,
    )));
    $collection->add('blog_show', new Route('/blog/{show}', array(
    '_controller' => 'AcmeBlogBundle:Blog:show',
    )));
    return $collection;
    Can you spot the problem? Notice that both routes have patterns that match URL's that
    look like /blog/*. The Symfony router will always choose the rst matching route it nds.
    In other words, the blog_show route will never be matched. Instead, a URL like /blog/myblog-post will match the rst route (blog) and return a nonsense value of my-blog-post to
    the {page} parameter.
    URL
    /blog/2
    /blog/my-blog-post

    route
    blog
    blog

    parameters
    {page} = 2
    {page} = my-blog-post

    The answer to the problem is to add route requirements. The routes in this example would
    work perfectly if the /blog/{page} pattern only matched URLs where the {page} portion
    is an integer. Fortunately, regular expression requirements can easily be added for each
    parameter. For example:

    • YAML

    blog:
    pattern: /blog/{page}
    defaults: { _controller: AcmeBlogBundle:Blog:index, page: 1 }
    requirements:
    page: \d+
    • XML

    <?xml version="1.0" encoding="UTF-8" ?>

    <routes xmlns="http://symfony.com/schema/routing"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing
    <route id="blog" pattern="/blog/{page}">
    5.6. Routing

    109

    Symfony Documentation, Âûïóñê 2.0

    <default key="_controller">AcmeBlogBundle:Blog:index</default>
    <default key="page">1</default>
    <requirement key="page">\d+</requirement>
    </route>
    </routes>
    • PHP

    use Symfony\Component\Routing\RouteCollection;
    use Symfony\Component\Routing\Route;
    $collection = new RouteCollection();
    $collection->add('blog', new Route('/blog/{page}', array(
    '_controller' => 'AcmeBlogBundle:Blog:index',
    'page' => 1,
    ), array(
    'page' => '\d+',
    )));
    return $collection;
    The \d+ requirement is a regular expression that says that the value of the {page} parameter
    must be a digit (i.e. a number). The blog route will still match on a URL like /blog/2 (because
    2 is a number), but it will no longer match a URL like /blog/my-blog-post (because myblog-post is not a number).
    As a result, a URL like /blog/my-blog-post will now properly match the blog_show route.
    URL
    /blog/2
    /blog/my-blog-post

    route
    blog
    blog_show

    parameters
    {page} = 2
    {slug} = my-blog-post

    Earlier Routes always Win
    What this all means is that the order of the routes is very important. If the blog_show
    route were placed above the blog route, the URL /blog/2 would match blog_show
    instead of blog since the {slug} parameter of blog_show has no requirements. By using
    proper ordering and clever requirements, you can accomplish just about anything.
    Since the parameter requirements are regular expressions, the complexity and exibility of
    each requirement is entirely up to you. Suppose the homepage of your application is available
    in two dierent languages, based on the URL:

    • YAML

    homepage:
    pattern: /{culture}
    defaults: { _controller: AcmeDemoBundle:Main:homepage, culture: en }
    110

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    requirements:
    culture: en|fr
    • XML

    <?xml version="1.0" encoding="UTF-8" ?>

    <routes xmlns="http://symfony.com/schema/routing"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing
    <route id="homepage" pattern="/{culture}">
    <default key="_controller">AcmeDemoBundle:Main:homepage</default>
    <default key="culture">en</default>
    <requirement key="culture">en|fr</requirement>
    </route>
    </routes>
    • PHP

    use Symfony\Component\Routing\RouteCollection;
    use Symfony\Component\Routing\Route;
    $collection = new RouteCollection();
    $collection->add('homepage', new Route('/{culture}', array(
    '_controller' => 'AcmeDemoBundle:Main:homepage',
    'culture' => 'en',
    ), array(
    'culture' => 'en|fr',
    )));
    return $collection;
    For incoming requests, the {culture} portion of the URL is matched against the regular
    expression (en|fr).
    /
    /en
    /fr
    /es

    {culture} = en
    {culture} = en
    {culture} = fr
    won't match this route

    Adding HTTP Method Requirements
    In addition to the URL, you can also match on the method of the incoming request (i.e. GET,
    HEAD, POST, PUT, DELETE). Suppose you have a contact form with two controllers one for displaying the form (on a GET request) and one for processing the form when
    5.6. Routing

    111

    Symfony Documentation, Âûïóñê 2.0
    it's submitted (on a POST request). This can be accomplished with the following route
    conguration:

    • YAML

    contact:
    pattern: /contact
    defaults: { _controller: AcmeDemoBundle:Main:contact }
    requirements:
    _method: GET
    contact_process:
    pattern: /contact
    defaults: { _controller: AcmeDemoBundle:Main:contactProcess }
    requirements:
    _method: POST
    • XML

    <?xml version="1.0" encoding="UTF-8" ?>

    <routes xmlns="http://symfony.com/schema/routing"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing
    <route id="contact" pattern="/contact">
    <default key="_controller">AcmeDemoBundle:Main:contact</default>
    <requirement key="_method">GET</requirement>
    </route>
    <route id="contact_process" pattern="/contact">
    <default key="_controller">AcmeDemoBundle:Main:contactProcess</default>
    <requirement key="_method">POST</requirement>
    </route>
    </routes>
    • PHP

    use Symfony\Component\Routing\RouteCollection;
    use Symfony\Component\Routing\Route;
    $collection = new RouteCollection();
    $collection->add('contact', new Route('/contact', array(
    '_controller' => 'AcmeDemoBundle:Main:contact',
    ), array(
    '_method' => 'GET',
    )));
    $collection->add('contact_process', new Route('/contact', array(
    112

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    '_controller' => 'AcmeDemoBundle:Main:contactProcess',
    ), array(
    '_method' => 'POST',
    )));
    return $collection;
    Despite the fact that these two routes have identical patterns (/contact), the rst route will
    match only GET requests and the second route will match only POST requests. This means
    that you can display the form and submit the form via the same URL, while using distinct
    controllers for the two actions.
    Ïðèìå÷àíèå: If no _method requirement is specied, the route will match on all methods.
    Like the other requirements, the _method requirement is parsed as a regular expression. To
    match GET or POST requests, you can use GET|POST.
    Advanced Routing Example
    At this point, you have everything you need to create a powerful routing structure in Symfony.
    The following is an example of just how exible the routing system can be:

    • YAML

    article_show:
    pattern: /articles/{culture}/{year}/{title}.{_format}
    defaults: { _controller: AcmeDemoBundle:Article:show, _format: html }
    requirements:
    culture: en|fr
    _format: html|rss
    year: \d+
    • XML

    <?xml version="1.0" encoding="UTF-8" ?>

    <routes xmlns="http://symfony.com/schema/routing"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing
    <route id="article_show" pattern="/articles/{culture}/{year}/{title}.{_format}">
    <default key="_controller">AcmeDemoBundle:Article:show</default>
    <default key="_format">html</default>
    <requirement key="culture">en|fr</requirement>
    <requirement key="_format">html|rss</requirement>
    <requirement key="year">\d+</requirement>
    5.6. Routing

    113

    Symfony Documentation, Âûïóñê 2.0

    </route>
    </routes>
    • PHP

    use Symfony\Component\Routing\RouteCollection;
    use Symfony\Component\Routing\Route;
    $collection = new RouteCollection();
    $collection->add('homepage', new Route('/articles/{culture}/{year}/{title}.{_format}', array(
    '_controller' => 'AcmeDemoBundle:Article:show',
    '_format' => 'html',
    ), array(
    'culture' => 'en|fr',
    '_format' => 'html|rss',
    'year' => '\d+',
    )));
    return $collection;
    As you've seen, this route will only match if the {culture} portion of the URL is either en
    or fr and if the {year} is a number. This route also shows how you can use a period between
    placeholders instead of a slash. URLs matching this route might look like:

    • /articles/en/2010/my-post
    • /articles/fr/2010/my-post.rss
    The Special _format Routing Parameter
    This example also highlights the special _format routing parameter. When using this
    parameter, the matched value becomes the request format of the Request object.
    Ultimately, the request format is used for such things such as setting the ContentType of the response (e.g. a json request format translates into a Content-Type of
    application/json). It can also be used in the controller to render a dierent template for
    each value of _format. The _format parameter is a very powerful way to render the
    same content in dierent formats.

    Special Routing Parameters
    As you've seen, each routing parameter or default value is eventually available as an argument
    in the controller method. Additionally, there are three parameters that are special: each adds
    a unique piece of functionality inside your application:

    • _controller: As you've seen, this parameter is used to determine which controller is
    executed when the route is matched;
    114

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    • _format: Used to set the request format (read more);
    • _locale: Used to set the locale on the session (read more);
    5.6.4 Controller Naming Pattern

    Every route must have a _controller parameter, which dictates which controller should be
    executed when that route is matched. This parameter uses a simple string pattern called
    the logical controller name, which Symfony maps to a specic PHP method and class. The
    pattern has three parts, each separated by a colon:
    bundle:controller:action
    For example, a _controller value of AcmeBlogBundle:Blog:show means:
    Bundle
    AcmeBlogBundle

    Controller Class
    BlogController

    Method Name
    showAction

    The controller might look like this:

    // src/Acme/BlogBundle/Controller/BlogController.php
    namespace Acme\BlogBundle\Controller;
    use Symfony\Bundle\FrameworkBundle\Controller\Controller;
    class BlogController extends Controller
    {
    public function showAction($slug)
    {
    // ...
    }
    }
    Notice that Symfony adds the string Controller to the class name (Blog => BlogController)
    and Action to the method name (show => showAction).
    You could also refer to this controller using its fully-qualied class name and method:
    Acme\BlogBundle\Controller\BlogController::showAction. But if you follow some simple
    conventions, the logical name is more concise and allows more exibility.
    Ïðèìå÷àíèå: In addition to using the logical name or the fully-qualied class name, Symfony
    supports a third way of referring to a controller. This method uses just one colon separator
    (e.g. service_name:indexAction) and refers to the controller as a service (see How to dene
    Controllers as Services).

    5.6. Routing

    115

    Symfony Documentation, Âûïóñê 2.0
    5.6.5 Route Parameters and Controller Arguments

    The route parameters (e.g. {slug}) are especially important because each is made available
    as an argument to the controller method:

    public function showAction($slug)
    {
    // ...
    }
    In reality, the entire defaults collection is merged with the parameter values to form a single
    array. Each key of that array is available as an argument on the controller.
    In other words, for each argument of your controller method, Symfony looks for a route
    parameter of that name and assigns its value to that argument. In the advanced example
    above, any combination (in any order) of the following variables could be used as arguments
    to the showAction() method:

    • $culture
    • $year
    • $title
    • $_format
    • $_controller
    Since the placeholders and defaults collection are merged together, even the $_controller
    variable is available. For a more detailed discussion, see Route Parameters as Controller
    Arguments.
    Ñîâåò: You can also use a special $_route variable, which is set to the name of the route
    that was matched.

    5.6.6 Including External Routing Resources

    All routes are loaded via a single conguration le - usually app/cong/routing.yml (see
    Creating Routes above). Commonly, however, you'll want to load routes from other places,
    like a routing le that lives inside a bundle. This can be done by importing that le:

    • YAML

    # app/cong/routing.yml
    acme_hello:
    resource: "@AcmeHelloBundle/Resources/cong/routing.yml"
    • XML
    116

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    <!-- app/cong/routing.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>

    <routes xmlns="http://symfony.com/schema/routing"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing
    <import resource="@AcmeHelloBundle/Resources/cong/routing.xml" />
    </routes>
    • PHP

    // app/cong/routing.php
    use Symfony\Component\Routing\RouteCollection;
    $collection = new RouteCollection();
    $collection->addCollection($loader->import("@AcmeHelloBundle/Resources/cong/routing.php"));
    return $collection;
    Ïðèìå÷àíèå: When importing resources from YAML, the key (e.g. acme_hello) is
    meaningless. Just be sure that it's unique so no other lines override it.
    The resource key loads the given routing resource. In this example the resource is the full
    path to a le, where the @AcmeHelloBundle shortcut syntax resolves to the path of that
    bundle. The imported le might look like this:

    • YAML

    # src/Acme/HelloBundle/Resources/cong/routing.yml
    acme_hello:
    pattern: /hello/{name}
    defaults: { _controller: AcmeHelloBundle:Hello:index }
    • XML

    <!-- src/Acme/HelloBundle/Resources/cong/routing.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>

    <routes xmlns="http://symfony.com/schema/routing"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing
    <route id="acme_hello" pattern="/hello/{name}">
    <default key="_controller">AcmeHelloBundle:Hello:index</default>
    </route>
    </routes>
    5.6. Routing

    117

    Symfony Documentation, Âûïóñê 2.0

    • PHP

    // src/Acme/HelloBundle/Resources/cong/routing.php
    use Symfony\Component\Routing\RouteCollection;
    use Symfony\Component\Routing\Route;
    $collection = new RouteCollection();
    $collection->add('acme_hello', new Route('/hello/{name}', array(
    '_controller' => 'AcmeHelloBundle:Hello:index',
    )));
    return $collection;
    The routes from this le are parsed and loaded in the same way as the main routing le.
    Prexing Imported Routes
    You can also choose to provide a prex for the imported routes. For example, suppose you
    want the acme_hello route to have a nal pattern of /admin/hello/{name} instead of simply
    /hello/{name}:

    • YAML

    # app/cong/routing.yml
    acme_hello:
    resource: "@AcmeHelloBundle/Resources/cong/routing.yml"
    prex: /admin
    • XML

    <!-- app/cong/routing.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>

    <routes xmlns="http://symfony.com/schema/routing"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing
    <import resource="@AcmeHelloBundle/Resources/cong/routing.xml" prex="/admin" />
    </routes>
    • PHP

    // app/cong/routing.php
    use Symfony\Component\Routing\RouteCollection;
    $collection = new RouteCollection();
    118

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    $collection->addCollection($loader->import("@AcmeHelloBundle/Resources/cong/routing.php"), '/admin
    return $collection;
    The string /admin will now be prepended to the pattern of each route loaded from the new
    routing resource.
    5.6.7 Visualizing & Debugging Routes

    While adding and customizing routes, it's helpful to be able to visualize and get detailed
    information about your routes. A great way to see every route in your application is via the
    router:debug console command. Execute the command by running the following from the
    root of your project.

    php app/console router:debug
    The command will print a helpful list of all the congured routes in your application:

    homepage
    ANY
    /
    contact
    GET
    /contact
    contact_process
    POST
    /contact
    article_show
    ANY
    /articles/{culture}/{year}/{title}.{_format}
    blog
    ANY
    /blog/{page}
    blog_show
    ANY
    /blog/{slug}
    You can also get very specic information on a single route by including the route name
    after the command:

    php app/console router:debug article_show

    5.6.8 Generating URLs

    The routing system should also be used to generate URLs. In reality, routing is a bidirectional system: mapping the URL to a controller+parameters and a route+parameters
    back to a URL. The :method:`Symfony\\Component\\Routing\\Router::match` and
    :method:`Symfony\\Component\\Routing\\Router::generate` methods form this bidirectional system. Take the blog_show example route from earlier:

    $params = $router->match('/blog/my-blog-post');
    // array('slug' => 'my-blog-post', '_controller' => 'AcmeBlogBundle:Blog:show')
    $uri = $router->generate('blog_show', array('slug' => 'my-blog-post'));
    // /blog/my-blog-post

    5.6. Routing

    119

    Symfony Documentation, Âûïóñê 2.0
    To generate a URL, you need to specify the name of the route (e.g. blog_show) and any
    wildcards (e.g. slug = my-blog-post) used in the pattern for that route. With this information,
    any URL can easily be generated:

    class MainController extends Controller
    {
    public function showAction($slug)
    {
    // ...

    }

    }

    $url = $this->get('router')->generate('blog_show', array('slug' => 'my-blog-post'));

    In an upcoming section, you'll learn how to generate URLs from inside templates.
    Generating Absolute URLs
    By default, the router will generate relative URLs (e.g. /blog). To generate an absolute URL,
    simply pass true to the third argument of the generate() method:

    $router->generate('blog_show', array('slug' => 'my-blog-post'), true);
    // http://www.example.com/blog/my-blog-post
    Ïðèìå÷àíèå: The host that's used when generating an absolute URL is the host of the
    current Request object. This is detected automatically based on server information supplied
    by PHP. When generating absolute URLs for scripts run from the command line, you'll need
    to manually set the desired host on the Request object:

    $request->headers->set('HOST', 'www.example.com');

    Generating URLs with Query Strings
    The generate method takes an array of wildcard values to generate the URI. But if you pass
    extra ones, they will be added to the URI as a query string:

    $router->generate('blog', array('page' => 2, 'category' => 'Symfony'));
    // /blog/2?category=Symfony
    Generating URLs from a template
    The most common place to generate a URL is from within a template when linking between
    pages in your application. This is done just as before, but using a template helper function:
    120

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    • Twig

    <a href="{{ path('blog_show', { 'slug': 'my-blog-post' }) }}">
    Read this blog post.
    </a>
    • PHP

    <a href="<?php echo $view['router']->generate('blog_show', array('slug' => 'my-blog-post')) ?>">
    Read this blog post.
    </a>
    Absolute URLs can also be generated.

    • Twig

    <a href="{{ url('blog_show', { 'slug': 'my-blog-post' }) }}">
    Read this blog post.
    </a>
    • PHP

    <a href="<?php echo $view['router']->generate('blog_show', array('slug' => 'my-blog-post'), true) ?>">
    Read this blog post.
    </a>

    5.6.9 Summary

    Routing is a system for mapping the URL of incoming requests to the controller function
    that should be called to process the request. It both allows you to specify beautiful URLs
    and keeps the functionality of your application decoupled from those URLs. Routing is a
    two-way mechanism, meaning that it should also be used to generate URLs.
    5.6.10 Learn more from the Cookbook

    • How to force routes to always use HTTPS

    5.7 Creating and using Templates

    As you know, the controller is responsible for handling each request that comes into a
    Symfony2 application. In reality, the controller delegates the most of the heavy work to
    other places so that code can be tested and reused. When a controller needs to generate
    HTML, CSS or any other content, it hands the work o to the templating engine. In this
    chapter, you'll learn how to write powerful templates that can be used to return content
    5.7. Creating and using Templates

    121

    Symfony Documentation, Âûïóñê 2.0
    to the user, populate email bodies, and more. You'll learn shortcuts, clever ways to extend
    templates and how to reuse template code.
    5.7.1 Templates

    A template is simply a text le that can generate any text-based format (HTML, XML,
    CSV, LaTeX ...). The most familiar type of template is a PHP template - a text le parsed
    by PHP that contains a mix of text and PHP code:

    <!DOCTYPE html>
    <html>
    <head>
    <title>Welcome to Symfony!</title>
    </head>
    <body>
    <h1><?php echo $page_title ?></h1>
    <ul id="navigation">
    <?php foreach ($navigation as $item): ?>
    <li>
    <a href="<?php echo $item->getHref() ?>">
    <?php echo $item->getCaption() ?>
    </a>
    </li>
    <?php endforeach; ?>
    </ul>
    </body>
    </html>
    But Symfony2 packages an even more powerful templating language called Twig. Twig allows
    you to write concise, readable templates that are more friendly to web designers and, in
    several ways, more powerful than PHP templates:

    <!DOCTYPE html>
    <html>
    <head>
    <title>Welcome to Symfony!</title>
    </head>
    <body>
    <h1>{{ page_title }}</h1>
    <ul id="navigation">
    {% for item in navigation %}
    <li><a href="{{ item.href }}">{{ item.caption }}</a></li>
    {% endfor %}
    </ul>
    122

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    </body>
    </html>
    Twig denes two types of special syntax:

    • {{ ... }}: Says something: prints a variable or the result of an expression to the
    template;
    • {% ... %}: Does something: a tag that controls the logic of the template; it is used to
    execute statements such as for-loops for example.
    Ïðèìå÷àíèå: There is a third syntax used for creating comments: {# this is a comment
    #}. This syntax can be used across multiple lines like the PHP-equivalent /* comment */
    syntax.
    Twig also contains lters, which modify content before being rendered. The following makes
    the title variable all uppercase before rendering it:

    {{ title | upper }}
    Twig comes with a long list of tags and lters that are available by default. You can even
    add your own extensions to Twig as needed.
    Ñîâåò: Registering a Twig extension is as easy as creating a new service and tagging it with
    twig.extension tag.
    As you'll see throughout the documentation, Twig also supports functions and new functions
    can be easily added. For example, the following uses a standard for tag and the cycle function
    to print ten div tags, with alternating odd, even classes:

    {% for i in 0..10 %}
    <div class="{{ cycle(['odd', 'even'], i) }}">
    <!-- some HTML here -->
    </div>
    {% endfor %}
    Throughout this chapter, template examples will be shown in both Twig and PHP.

    5.7. Creating and using Templates

    123

    Symfony Documentation, Âûïóñê 2.0
    Why Twig?
    Twig templates are meant to be simple and won't process PHP tags. This is by design:
    the Twig template system is meant to express presentation, not program logic. The
    more you use Twig, the more you'll appreciate and benet from this distinction. And of
    course, you'll be loved by web designers everywhere.
    Twig can also do things that PHP can't, such as true template inheritance (Twig
    templates compile down to PHP classes that inherit from each other), whitespace
    control, sandboxing, and the inclusion of custom functions and lters that only aect
    templates. Twig contains little features that make writing templates easier and more
    concise. Take the following example, which combines a loop with a logical if statement:

    <ul>
    {% for user in users %}
    <li>{{ user.username }}</li>
    {% else %}
    <li>No users found</li>
    {% endfor %}
    </ul>

    Twig Template Caching
    Twig is fast. Each Twig template is compiled down to a native PHP class that is rendered at
    runtime. The compiled classes are located in the app/cache/{environment}/twig directory
    (where {environment} is the environment, such as dev or prod) and in some cases can be
    useful while debugging. See Îêðóæåíèÿ for more information on environments.
    When debug mode is enabled (common in the dev environment), a Twig template will be
    automatically recompiled when changes are made to it. This means that during development
    you can happily make changes to a Twig template and instantly see the changes without
    needing to worry about clearing any cache.
    When debug mode is disabled (common in the prod environment), however, you must clear
    the Twig cache directory so that the Twig templates will regenerate. Remember to do this
    when deploying your application.
    5.7.2 Template Inheritance and Layouts

    More often than not, templates in a project share common elements, like the header, footer,
    sidebar or more. In Symfony2, we like to think about this problem dierently: a template
    can be decorated by another one. This works exactly the same as PHP classes: template
    inheritance allows you to build a base layout template that contains all the common
    elements of your site dened as blocks (think PHP class with base methods). A child

    124

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    template can extend the base layout and override any of its blocks (think PHP subclass
    that overrides certain methods of its parent class).
    First, build a base layout le:

    • Twig

    {# app/Resources/views/base.html.twig #}
    <!DOCTYPE html>
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>{% block title %}Test Application{% endblock %}</title>
    </head>
    <body>
    <div id="sidebar">
    {% block sidebar %}
    <ul>
    <li><a href="/">Home</a></li>
    <li><a href="/blog">Blog</a></li>
    </ul>
    {% endblock %}
    </div>
    <div id="content">
    {% block body %}{% endblock %}
    </div>
    </body>
    </html>
    • PHP

    <!-- app/Resources/views/base.html.php -->
    <!DOCTYPE html>
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title><?php $view['slots']->output('title', 'Test Application') ?></title>
    </head>
    <body>
    <div id="sidebar">
    <?php if ($view['slots']->has('sidebar'): ?>
    <?php $view['slots']->output('sidebar') ?>
    <?php else: ?>
    <ul>
    <li><a href="/">Home</a></li>
    <li><a href="/blog">Blog</a></li>
    </ul>
    <?php endif; ?>
    5.7. Creating and using Templates

    125

    Symfony Documentation, Âûïóñê 2.0

    </div>
    <div id="content">
    <?php $view['slots']->output('body') ?>
    </div>
    </body>
    </html>
    Ïðèìå÷àíèå: Though the discussion about template inheritance will be in terms of Twig,
    the philosophy is the same between Twig and PHP templates.
    This template denes the base HTML skeleton document of a simple two-column page. In
    this example, three {% block %} areas are dened (title, sidebar and body). Each block may
    be overridden by a child template or left with its default implementation. This template
    could also be rendered directly. In that case the title, sidebar and body blocks would simply
    retain the default values used in this template.
    A child template might look like this:

    • Twig

    {# src/Acme/BlogBundle/Resources/views/Blog/index.html.twig #}
    {% extends '::base.html.twig' %}
    {% block title %}My cool blog posts{% endblock %}
    {% block body %}
    {% for entry in blog_entries %}
    <h2>{{ entry.title }}</h2>
    <p>{{ entry.body }}</p>
    {% endfor %}
    {% endblock %}
    • PHP

    <!-- src/Acme/BlogBundle/Resources/views/Blog/index.html.php -->
    <?php $view->extend('::base.html.php') ?>
    <?php $view['slots']->set('title', 'My cool blog posts') ?>
    <?php $view['slots']->start('body') ?>
    <?php foreach ($blog_entries as $entry): ?>
    <h2><?php echo $entry->getTitle() ?></h2>
    <p><?php echo $entry->getBody() ?></p>
    <?php endforeach; ?>
    <?php $view['slots']->stop() ?>
    126

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    Ïðèìå÷àíèå: The parent template is identied by a special string syntax (::base.html.twig)
    that indicates that the template lives in the app/Resources/views directory of the project.
    This naming convention is explained fully in Template Naming and Locations.
    The key to template inheritance is the {% extends %} tag. This tells the templating engine
    to rst evaluate the base template, which sets up the layout and denes several blocks. The
    child template is then rendered, at which point the title and body blocks of the parent are
    replaced by those from the child. Depending on the value of blog_entries, the output might
    look like this:

    <!DOCTYPE html>
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>My cool blog posts</title>
    </head>
    <body>
    <div id="sidebar">
    <ul>
    <li><a href="/">Home</a></li>
    <li><a href="/blog">Blog</a></li>
    </ul>
    </div>
    <div id="content">
    <h2>My rst post</h2>
    <p>The body of the rst post.</p>
    <h2>Another post</h2>
    <p>The body of the second post.</p>
    </div>
    </body>
    </html>
    Notice that since the child template didn't dene a sidebar block, the value from the parent
    template is used instead. Content within a {% block %} tag in a parent template is always
    used by default.
    You can use as many levels of inheritance as you want. In the next section, a common threelevel inheritance model will be explained along with how templates are organized inside a
    Symfony2 project.
    When working with template inheritance, here are some tips to keep in mind:

    • If you use {% extends %} in a template, it must be the rst tag in that template.
    • The more {% block %} tags you have in your base templates, the better. Remember,
    5.7. Creating and using Templates

    127

    Symfony Documentation, Âûïóñê 2.0
    child templates don't have to dene all parent blocks, so create as many blocks in your
    base templates as you want and give each a sensible default. The more blocks your
    base templates have, the more exible your layout will be.

    • If you nd yourself duplicating content in a number of templates, it probably means
    you should move that content to a {% block %} in a parent template. In some cases,
    a better solution may be to move the content to a new template and include it (see
    Including other Templates).
    • If you need to get the content of a block from the parent template, you can use the
    {{ parent() }} function. This is useful if you want to add to the contents of a parent
    block instead of completely overriding it:

    {% block sidebar %}
    <h3>Table of Contents</h3>
    ...
    {{ parent() }}
    {% endblock %}

    5.7.3 Template Naming and Locations

    By default, templates can live in two dierent locations:

    • app/Resources/views/: The applications views directory can contain application-wide
    base templates (i.e. your application's layouts) as well as templates that override bundle
    templates (see Overriding Bundle Templates);
    • path/to/bundle/Resources/views/: Each bundle houses its templates in its
    Resources/views directory (and subdirectories). The majority of templates will live
    inside a bundle.
    Symfony2 uses a bundle:controller:template string syntax for templates. This allows for
    several dierent types of templates, each which lives in a specic location:

    • AcmeBlogBundle:Blog:index.html.twig: This syntax is used to specify a template for
    a specic page. The three parts of the string, each separated by a colon (:), mean the
    following:
     AcmeBlogBundle: (bundle) the template lives inside the AcmeBlogBundle (e.g.
    src/Acme/BlogBundle);
     Blog: (controller) indicates that the template lives inside the Blog subdirectory
    of Resources/views;
     index.html.twig: (template) the actual name of the le is index.html.twig.
    Assuming that the AcmeBlogBundle lives at src/Acme/BlogBundle, the nal path to
    the layout would be src/Acme/BlogBundle/Resources/views/Blog/index.html.twig.

    128

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    • AcmeBlogBundle::layout.html.twig: This syntax refers to a base template that's specic
    to the AcmeBlogBundle. Since the middle, controller, portion is missing (e.g. Blog),
    the template lives at Resources/views/layout.html.twig inside AcmeBlogBundle.
    • ::base.html.twig: This syntax refers to an application-wide base template or layout.
    Notice that the string begins with two colons (::), meaning that both the bundle and
    controller portions are missing. This means that the template is not located in any
    bundle, but instead in the root app/Resources/views/ directory.
    In the Overriding Bundle Templates section, you'll nd out how each template living inside
    the AcmeBlogBundle, for example, can be overridden by placing a template of the same name
    in the app/Resources/AcmeBlogBundle/views/ directory. This gives the power to override
    templates from any vendor bundle.
    Ñîâåò: Hopefully the template naming syntax looks familiar - it's the same naming
    convention used to refer to Controller Naming Pattern.

    Template Sux
    The bundle:controller:template format of each template species where the template le is
    located. Every template name also has two extensions that specify the format and engine
    for that template.

    • AcmeBlogBundle:Blog:index.html.twig - HTML format, Twig engine
    • AcmeBlogBundle:Blog:index.html.php - HTML format, PHP engine
    • AcmeBlogBundle:Blog:index.css.twig - CSS format, Twig engine
    By default, any Symfony2 template can be written in either Twig or PHP, and the last part
    of the extension (e.g. .twig or .php) species which of these two engines should be used.
    The rst part of the extension, (e.g. .html, .css, etc) is the nal format that the template
    will generate. Unlike the engine, which determines how Symfony2 parses the template, this is
    simply an organizational tactic used in case the same resource needs to be rendered as HTML
    (index.html.twig), XML (index.xml.twig), or any other format. For more information, read
    the Template Formats section.
    Ïðèìå÷àíèå: The available engines can be congured and even new engines added. See
    Templating Conguration for more details.

    5.7.4 Tags and Helpers

    You already understand the basics of templates, how they're named and how to use template
    inheritance. The hardest parts are already behind you. In this section, you'll learn about a
    5.7. Creating and using Templates

    129

    Symfony Documentation, Âûïóñê 2.0
    large group of tools available to help perform the most common template tasks such as
    including other templates, linking to pages and including images.
    Symfony2 comes bundled with several specialized Twig tags and functions that ease the
    work of the template designer. In PHP, the templating system provides an extensible helper
    system that provides useful features in a template context.
    We've already seen a few built-in Twig tags ({% block %} & {% extends %}) as well as an
    example of a PHP helper ($view['slots']). Let's learn a few more.
    Including other Templates
    You'll often want to include the same template or code fragment on several dierent pages.
    For example, in an application with news articles, the template code displaying an article
    might be used on the article detail page, on a page displaying the most popular articles, or
    in a list of the latest articles.
    When you need to reuse a chunk of PHP code, you typically move the code to a new PHP
    class or function. The same is true for templates. By moving the reused template code into
    its own template, it can be included from any other template. First, create the template that
    you'll need to reuse.

    • Twig

    {# src/Acme/ArticleBundle/Resources/views/Article/articleDetails.html.twig #}
    <h1>{{ article.title }}</h1>
    <h3 class="byline">by {{ article.authorName }}</h3>
    <p>
    {{ article.body }}
    </p>
    • PHP

    <!-- src/Acme/ArticleBundle/Resources/views/Article/articleDetails.html.php -->
    <h2><?php echo $article->getTitle() ?></h2>
    <h3 class="byline">by <?php echo $article->getAuthorName() ?></h3>
    <p>
    <?php echo $article->getBody() ?>
    </p>
    Including this template from any other template is simple:

    • Twig

    {# src/Acme/ArticleBundle/Resources/Article/list.html.twig #}
    {% extends 'AcmeArticleBundle::layout.html.twig' %}
    130

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    {% block body %}
    <h1>Recent Articles<h1>
    {% for article in articles %}
    {% include 'AcmeArticleBundle:Article:articleDetails.html.twig' with {'article': article} %}
    {% endfor %}
    {% endblock %}
    • PHP

    <!-- src/Acme/ArticleBundle/Resources/Article/list.html.php -->
    <?php $view->extend('AcmeArticleBundle::layout.html.php') ?>
    <?php $view['slots']->start('body') ?>
    <h1>Recent Articles</h1>

    <?php foreach ($articles as $article): ?>
    <?php echo $view->render('AcmeArticleBundle:Article:articleDetails.html.php', array('article' => $a
    <?php endforeach; ?>
    <?php $view['slots']->stop() ?>
    The template is included using the {% include %} tag. Notice that the template name follows
    the same typical convention. The articleDetails.html.twig template uses an article variable.
    This is passed in by the list.html.twig template using the with command.
    Ñîâåò: The {'article': article} syntax is the standard Twig syntax for hash maps (i.e. an
    array with named keys). If we needed to pass in multiple elements, it would look like this:
    {'foo': foo, 'bar': bar}.

    Embedding Controllers
    In some cases, you need to do more than include a simple template. Suppose you have a
    sidebar in your layout that contains the three most recent articles. Retrieving the three
    articles may include querying the database or performing other heavy logic that can't be
    done from within a template.
    The solution is to simply embed the result of an entire controller from your template. First,
    create a controller that renders a certain number of recent articles:

    // src/Acme/ArticleBundle/Controller/ArticleController.php
    class ArticleController extends Controller
    {
    public function recentArticlesAction($max = 3)
    {
    5.7. Creating and using Templates

    131

    Symfony Documentation, Âûïóñê 2.0

    // make a database call or other logic to get the "$max" most recent articles
    $articles = ...;

    }

    }

    return $this->render('AcmeArticleBundle:Article:recentList.html.twig', array('articles' => $articles));

    The recentList template is perfectly straightforward:

    • Twig

    {# src/Acme/ArticleBundle/Resources/views/Article/recentList.html.twig #}
    {% for article in articles %}
    <a href="/article/{{ article.slug }}">
    {{ article.title }}
    </a>
    {% endfor %}
    • PHP

    <!-- src/Acme/ArticleBundle/Resources/views/Article/recentList.html.php -->
    <?php foreach ($articles in $article): ?>
    <a href="/article/<?php echo $article->getSlug() ?>">
    <?php echo $article->getTitle() ?>
    </a>
    <?php endforeach; ?>
    Ïðèìå÷àíèå: Notice that we've cheated and hardcoded the article URL in this example
    (e.g. /article/*slug*). This is a bad practice. In the next section, you'll learn how to do this
    correctly.
    To include the controller, you'll need to refer to it using the standard string syntax for
    controllers (i.e. bundle:controller:action):

    • Twig

    {# app/Resources/views/base.html.twig #}
    ...
    <div id="sidebar">
    {% render "AcmeArticleBundle:Article:recentArticles" with {'max': 3} %}
    </div>
    • PHP

    <!-- app/Resources/views/base.html.php -->
    ...
    132

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    <div id="sidebar">
    <?php echo $view['actions']->render('AcmeArticleBundle:Article:recentArticles', array('max' => 3)) ?>
    </div>
    Whenever you nd that you need a variable or a piece of information that you don't have
    access to in a template, consider rendering a controller. Controllers are fast to execute and
    promote good code organization and reuse.
    Linking to Pages
    Creating links to other pages in your application is one of the most common jobs for a
    template. Instead of hardcoding URLs in templates, use the path Twig function (or the
    router helper in PHP) to generate URLs based on the routing conguration. Later, if you
    want to modify the URL of a particular page, all you'll need to do is change the routing
    conguration; the templates will automatically generate the new URL.
    First, link to the _welcome page, which is accessible via the following routing conguration:

    • YAML

    _welcome:
    pattern: /
    defaults: { _controller: AcmeDemoBundle:Welcome:index }
    • XML

    <route id="_welcome" pattern="/">
    <default key="_controller">AcmeDemoBundle:Welcome:index</default>
    </route>
    • PHP

    $collection = new RouteCollection();
    $collection->add('_welcome', new Route('/', array(
    '_controller' => 'AcmeDemoBundle:Welcome:index',
    )));
    return $collection;
    To link to the page, just use the path Twig function and refer to the route:

    • Twig

    <a href="{{ path('_welcome') }}">Home</a>
    • PHP

    5.7. Creating and using Templates

    133

    Symfony Documentation, Âûïóñê 2.0

    <a href="<?php echo $view['router']->generate('_welcome') ?>">Home</a>
    As expected, this will generate the URL /. Let's see how this works with a more complicated
    route:

    • YAML

    article_show:
    pattern: /article/{slug}
    defaults: { _controller: AcmeArticleBundle:Article:show }
    • XML

    <route id="article_show" pattern="/article/{slug}">
    <default key="_controller">AcmeArticleBundle:Article:show</default>
    </route>
    • PHP

    $collection = new RouteCollection();
    $collection->add('article_show', new Route('/article/{slug}', array(
    '_controller' => 'AcmeArticleBundle:Article:show',
    )));
    return $collection;
    In this case, you need to specify both the route name (article_show) and a value for the
    {slug} parameter. Using this route, let's revisit the recentList template from the previous
    section and link to the articles correctly:

    • Twig

    {# src/Acme/ArticleBundle/Resources/views/Article/recentList.html.twig #}
    {% for article in articles %}
    <a href="{{ path('article_show', { 'slug': article.slug }) }}">
    {{ article.title }}
    </a>
    {% endfor %}
    • PHP

    <!-- src/Acme/ArticleBundle/Resources/views/Article/recentList.html.php -->
    <?php foreach ($articles in $article): ?>
    <a href="<?php echo $view['router']->generate('article_show', array('slug' => $article->getSlug()) ?>
    <?php echo $article->getTitle() ?>
    </a>
    <?php endforeach; ?>

    134

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    Ñîâåò: You can also generate an absolute URL by using the url Twig function:

    <a href="{{ url('_welcome') }}">Home</a>
    The same can be done in PHP templates by passing a third argument to the generate()
    method:

    <a href="<?php echo $view['router']->generate('_welcome', array(), true) ?>">Home</a>

    Linking to Assets
    Templates also commonly refer to images, Javascript, stylesheets and other assets. Of course
    you could hard-code the path to these assets (e.g. /images/logo.png), but Symfony2 provides
    a more dynamic option via the assets Twig function:

    • Twig

    <img src="{{ asset('images/logo.png') }}" alt="Symfony!" />
    <link href="{{ asset('css/blog.css') }}" rel="stylesheet" type="text/css" />
    • PHP

    <img src="<?php echo $view['assets']->getUrl('images/logo.png') ?>" alt="Symfony!" />
    <link href="<?php echo $view['assets']->getUrl('css/blog.css') ?>" rel="stylesheet" type="text/css" />
    The asset function's main purpose is to make your application more portable. If your
    application lives at the root of your host (e.g. http://example.com), then the rendered
    paths should be /images/logo.png. But if your application lives in a subdirectory (e.g.
    http://example.com/my_app), each asset path should render with the subdirectory (e.g.
    /my_app/images/logo.png). The asset function takes care of this by determining how your
    application is being used and generating the correct paths accordingly.
    Additionally, if you use the asset function, Symfony can automatically append a query string
    to your asset, in order to guarantee that updated static assets won't be cached when deployed.
    For example, /images/logo.png might look like /images/logo.png?v2. For more information,
    see the assets_version conguration option.
    5.7.5 Including Stylesheets and Javascripts in Twig

    No site would be complete without including Javascript les and stylesheets. In Symfony,
    the inclusion of these assets is handled elegantly by taking advantage of Symfony's template
    inheritance.

    5.7. Creating and using Templates

    135

    Symfony Documentation, Âûïóñê 2.0
    Ñîâåò: This section will teach you the philosophy behind including stylesheet and Javascript
    assets in Symfony. Symfony also packages another library, called Assetic, which follows this
    philosophy but allows you to do much more interesting things with those assets. For more
    information on using Assetic see How to Use Assetic for Asset Management.
    Start by adding two blocks to your base template that will hold your assets: one called
    stylesheets inside the head tag and another called javascripts just above the closing body tag.
    These blocks will contain all of the stylesheets and Javascripts that you'll need throughout
    your site:

    {# 'app/Resources/views/base.html.twig' #}
    <html>
    <head>
    {# ... #}
    {% block stylesheets %}
    <link href="{{ asset('/css/main.css') }}" type="text/css" rel="stylesheet" />
    {% endblock %}
    </head>
    <body>
    {# ... #}
    {% block javascripts %}
    <script src="{{ asset('/js/main.js') }}" type="text/javascript"></script>
    {% endblock %}
    </body>
    </html>
    That's easy enough! But what if you need to include an extra stylesheet or Javascript from
    a child template? For example, suppose you have a contact page and you need to include a
    contact.css stylesheet just on that page. From inside that contact page's template, do the
    following:

    {# src/Acme/DemoBundle/Resources/views/Contact/contact.html.twig #}
    {# extends '::base.html.twig' #}
    {% block stylesheets %}
    {{ parent() }}
    <link href="{{ asset('/css/contact.css') }}" type="text/css" rel="stylesheet" />
    {% endblock %}
    {# ... #}
    In the child template, you simply override the stylesheets block and put your new stylesheet
    tag inside of that block. Of course, since you want to add to the parent block's content (and
    not actually replace it), you should use the parent() Twig function to include everything
    136

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    from the stylesheets block of the base template.
    The end result is a page that includes both the main.css and contact.css stylesheets.
    5.7.6 Conguring and using the templating Service

    The heart of the template system in Symfony2 is the templating Engine. This special object is
    responsible for rendering templates and returning their content. When you render a template
    in a controller, for example, you're actually using the templating engine service. For example:

    return $this->render('AcmeArticleBundle:Article:index.html.twig');
    is equivalent to

    $engine = $this->container->get('templating');
    $content = $engine->render('AcmeArticleBundle:Article:index.html.twig');
    return $response = new Response($content);
    The templating engine (or service) is precongured to work automatically inside Symfony2.
    It can, of course, be congured further in the application conguration le:

    • YAML

    # app/cong/cong.yml
    framework:
    # ...
    templating: { engines: ['twig'] }
    • XML

    <!-- app/cong/cong.xml -->
    <framework:templating>
    <framework:engine id="twig" />
    </framework:templating>
    • PHP

    // app/cong/cong.php
    $container->loadFromExtension('framework', array(
    // ...
    'templating'
    => array(
    'engines' => array('twig'),
    ),
    ));
    Several conguration options are available and are covered in the Conguration Appendix.

    5.7. Creating and using Templates

    137

    Symfony Documentation, Âûïóñê 2.0
    Ïðèìå÷àíèå: The twig engine is mandatory to use the webproler (as well as many thirdparty bundles).

    5.7.7 Overriding Bundle Templates

    The Symfony2 community prides itself on creating and maintaining high quality bundles (see
    Symfony2Bundles.org) for a large number of dierent features. Once you use a third-party
    bundle, you'll likely need to override and customize one or more of its templates.
    Suppose you've included the imaginary open-source AcmeBlogBundle in your project (e.g.
    in the src/Acme/BlogBundle directory). And while you're really happy with everything, you
    want to override the blog list page to customize the markup specically for your application.
    By digging into the Blog controller of the AcmeBlogBundle, you nd the following:

    public function indexAction()
    {
    $blogs = // some logic to retrieve the blogs
    }

    $this->render('AcmeBlogBundle:Blog:index.html.twig', array('blogs' => $blogs));

    When the AcmeBlogBundle:Blog:index.html.twig is rendered, Symfony2 actually looks in
    two dierent locations for the template:
    1. app/Resources/AcmeBlogBundle/views/Blog/index.html.twig
    2. src/Acme/BlogBundle/Resources/views/Blog/index.html.twig
    To override the bundle template, just copy the index.html.twig template from
    the bundle to app/Resources/AcmeBlogBundle/views/Blog/index.html.twig (the
    app/Resources/AcmeBlogBundle directory won't exist, so you'll need to create it).
    You're now free to customize the template.
    This logic also applies to base bundle templates. Suppose also that each template in
    AcmeBlogBundle inherits from a base template called AcmeBlogBundle::layout.html.twig.
    Just as before, Symfony2 will look in the following two places for the template:
    1. app/Resources/AcmeBlogBundle/views/layout.html.twig
    2. src/Acme/BlogBundle/Resources/views/layout.html.twig
    Once again, to override the template, just copy it from the bundle to
    app/Resources/AcmeBlogBundle/views/layout.html.twig. You're now free to customize this
    copy as you see t.
    If you take a step back, you'll see that Symfony2 always starts by looking in the
    app/Resources/{BUNDLE_NAME}/views/ directory for a template. If the template doesn't
    exist there, it continues by checking inside the Resources/views directory of the bundle itself.
    138

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    This means that all bundle templates can be overridden by placing them in the correct
    app/Resources subdirectory.
    Overriding Core Templates
    Since the Symfony2 framework itself is just a bundle, core templates can be overridden
    in the same way. For example, the core TwigBundle contains a number of dierent
    exception and error templates that can be overridden by copying each from
    the Resources/views/Exception directory of the TwigBundle to, you guessed it, the
    app/Resources/TwigBundle/views/Exception directory.
    5.7.8 Three-level Inheritance

    One common way to use inheritance is to use a three-level approach. This method works
    perfectly with the three dierent types of templates we've just covered:

    • Create a app/Resources/views/base.html.twig le that contains the main layout for
    your application (like in the previous example). Internally, this template is called
    ::base.html.twig;
    • Create a template for each section of your site. For example, an AcmeBlogBundle,
    would have a template called AcmeBlogBundle::layout.html.twig that contains only
    blog section-specic elements;

    {# src/Acme/BlogBundle/Resources/views/layout.html.twig #}
    {% extends '::base.html.twig' %}
    {% block body %}
    <h1>Blog Application</h1>
    {% block content %}{% endblock %}
    {% endblock %}
    • Create individual templates for each page and make each extend the appropriate
    section template. For example, the index page would be called something close to
    AcmeBlogBundle:Blog:index.html.twig and list the actual blog posts.

    {# src/Acme/BlogBundle/Resources/views/Blog/index.html.twig #}
    {% extends 'AcmeBlogBundle::layout.html.twig' %}
    {% block content %}
    {% for entry in blog_entries %}
    <h2>{{ entry.title }}</h2>
    <p>{{ entry.body }}</p>
    {% endfor %}
    {% endblock %}
    5.7. Creating and using Templates

    139

    Symfony Documentation, Âûïóñê 2.0
    Notice that this template extends the section template -(AcmeBlogBundle::layout.html.twig)
    which in-turn extends the base application layout (::base.html.twig). This is the common
    three-level inheritance model.
    When building your application, you may choose to follow this method or simply make
    each page template extend the base application template directly (e.g. {% extends
    '::base.html.twig' %}). The three-template model is a best-practice method used by vendor
    bundles so that the base template for a bundle can be easily overridden to properly extend
    your application's base layout.
    5.7.9 Output Escaping

    When generating HTML from a template, there is always a risk that a template variable
    may output unintended HTML or dangerous client-side code. The result is that dynamic
    content could break the HTML of the resulting page or allow a malicious user to perform a
    Cross Site Scripting (XSS) attack. Consider this classic example:

    • Twig

    Hello {{ name }}
    • PHP

    Hello <?php echo $name ?>
    Imagine that the user enters the following code as his/her name:

    <script>alert('hello!')</script>
    Without any output escaping, the resulting template will cause a JavaScript alert box to
    pop up:

    Hello <script>alert('hello!')</script>
    And while this seems harmless, if a user can get this far, that same user should also be able
    to write JavaScript that performs malicious actions inside the secure area of an unknowing,
    legitimate user.
    The answer to the problem is output escaping. With output escaping on, the same template
    will render harmlessly, and literally print the script tag to the screen:

    Hello &lt;script&gt;alert(&#39;helloe&#39;)&lt;/script&gt;
    The Twig and PHP templating systems approach the problem in dierent ways. If you're
    using Twig, output escaping is on by default and you're protected. In PHP, output escaping
    is not automatic, meaning you'll need to manually escape where necessary.

    140

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    Output Escaping in Twig
    If you're using Twig templates, then output escaping is on by default. This means that
    you're protected out-of-the-box from the unintentional consequences of user-submitted code.
    By default, the output escaping assumes that content is being escaped for HTML output.
    In some cases, you'll need to disable output escaping when you're rendering a variable that is
    trusted and contains markup that should not be escaped. Suppose that administrative users
    are able to write articles that contain HTML code. By default, Twig will escape the article
    body. To render it normally, add the raw lter: {{ article.body | raw }}.
    You can also disable output escaping inside a {% block %} area or for an entire template.
    For more information, see Output Escaping in the Twig documentation.
    Output Escaping in PHP
    Output escaping is not automatic when using PHP templates. This means that unless you
    explicitly choose to escape a variable, you're not protected. To use output escaping, use the
    special escape() view method:

    Hello <?php echo $view->escape($name) ?>
    By default, the escape() method assumes that the variable is being rendered within an HTML
    context (and thus the variable is escaped to be safe for HTML). The second argument lets
    you change the context. For example, to output something in a JavaScript string, use the js
    context:

    var myMsg = 'Hello <?php echo $view->escape($name, 'js') ?>';

    5.7.10 Template Formats

    Templates are a generic way to render content in any format. And while in most cases you'll
    use templates to render HTML content, a template can just as easily generate JavaScript,
    CSS, XML or any other format you can dream of.
    For example, the same resource is often rendered in several dierent formats. To render an
    article index page in XML, simply include the format in the template name:

    • XML template name: AcmeArticleBundle:Article:index.xml.twig
    • XML template lename: index.xml.twig
    In reality, this is nothing more than a naming convention and the template isn't actually
    rendered dierently based on its format.
    In many cases, you may want to allow a single controller to render multiple dierent formats
    based on the request format. For that reason, a common pattern is to do the following:
    5.7. Creating and using Templates

    141

    Symfony Documentation, Âûïóñê 2.0

    public function indexAction()
    {
    $format = $this->getRequest()->getRequestFormat();
    }

    return $this->render('AcmeBlogBundle:Blog:index.'.$format.'.twig');

    The getRequestFormat on the Request object defaults to html, but can return any other
    format based on the format requested by the user. The request format is most often managed
    by the routing, where a route can be congured so that /contact sets the request format
    to html while /contact.xml sets the format to xml. For more information, see the Advanced
    Example in the Routing chapter.
    To create links that include the format parameter, include a _format key in the parameter
    hash:

    • Twig

    <a href="{{ path('article_show', {'id': 123, '_format': 'pdf'}) }}">
    PDF Version
    </a>
    • PHP

    <a href="<?php echo $view['router']->generate('article_show', array('id' => 123, '_format' => 'pdf')) ?>
    PDF Version
    </a>

    5.7.11 Final Thoughts

    The templating engine in Symfony is a powerful tool that can be used each time you need to
    generate presentational content in HTML, XML or any other format. And though templates
    are a common way to generate content in a controller, their use is not mandatory. The
    Response object returned by a controller can be created with our without the use of a
    template:

    // creates a Response object whose content is the rendered template
    $response = $this->render('AcmeArticleBundle:Article:index.html.twig');
    // creates a Response object whose content is simple text
    $response = new Response('response content');
    Symfony's templating engine is very exible and two dierent template renderers are available
    by default: the traditional PHP templates and the sleek and powerful Twig templates. Both
    support a template hierarchy and come packaged with a rich set of helper functions capable
    of performing the most common tasks.
    142

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    Overall, the topic of templating should be thought of as a powerful tool that's at your
    disposal. In some cases, you may not need to render a template, and in Symfony2, that's
    absolutely ne.
    5.7.12 Learn more from the Cookbook

    • How to use PHP instead of Twig for Templates
    • Êàê ñîçäàòü ñîáñòâåííûå ñòðàíèöû îøèáîê

    5.8 Áàçû äàííûõ è Doctrine (Ìîäåëè)

    Äàâàéòå ïîñìîòðèì ïðàâäå â ãëàçà, îäíà èç ñàìûõ ðàñïðîñòðàíåííûõ è ñëîæíûõ çàäà÷
    äëÿ ëþáîãî ïðèëîæåíèÿ - ýòî ñîõðàíåíèå è ÷òåíèå èíôîðìàöèè èç áàçû äàííûõ. Ê
    ñ÷àñòüþ, Symfony ïîñòàâëÿåòñÿ ñ èíòåãðèðîâàííîé Doctrine, áèáëèîòåêîé, ãëàâíàÿ öåëü
    êîòîðîé - äàòü âàì ìîùíûé èíñòðóìåíò, äåëàþùåé ýòó çàäà÷ó ïðîñòîé. Â ýòîé ãëàâå âû
    óçíàåòå áàçîâóþ ôèëîñîôèþ Doctrine è óâèäèòå íà ñêîëüêî ïðîñòîé ìîæåò áûòü ðàáîòà
    ñ áàçîé äàííûõ.
    Ïðèìå÷àíèå: Doctrine ÿâëÿåòñÿ ïîëíîñòüþ îòäåëüíûì èíñòðóìåíòîâ è åå èñïîëüçîâàíèå
    íå îáÿçàòåëüíî. Â ýòîé ãëàâå âñå î Doctrine ORM, êîòîðàÿ îïåðèðóåò îáúåêòàìè äëÿ
    ðàáîòû ñ ðåëÿöèîííûìè áàçàìè äàííûõ (òàêèõ êàê MySQL, PostgreSQL èëè Microsoft
    SQL). Åñëè âû ïðåäïî÷èòàåòå îáû÷íûå çàïðîñû, òî ýòî ïðîñòî è ðàñêðûòî â ñòàòüå
    How to use Doctrine's DBAL Layer êíèãè ðåöåïòîâ.
    Âû
    òàê
    æå
    ìîæåòå
    ñîõðàíÿòü
    èíôîðìàöèþ
    â
    MongoDB
    èñïîëüçóÿ
    áèáëèîòåêó
    Doctrine
    ODM.
    Äëÿ
    áîëüøåé
    èíôîðìàöèè
    ÷èòàéòå
    ãëàâó
    /bundles/DoctrineMongoDBBundle/index.

    5.8.1 Ïðîñòîé ïðèìåð: Ïðîäóêò

    Ïðîñòåéøèé ïóòü äëÿ ïîíèìàíèÿ òîãî, êàê ðàáîòàåò Doctrine - ýòî óâèäåòü âñå íà ïðèìåðå.  ýòîé ÷àñòè âû íàñòðîèòå áàçó äàííûõ, ñîçäàäèòå îáúåêò Product, ñîõðàíèòå åãî
    â áàçå äàííûõ è çàãðóçèòå åãî îáðàòíî.
    Ïðîãðàììèðîâàíèå âìåñòå ñ ïðèìåðîì
    Åñëè âû õîòèòå ñëåäîâàòü çà ïðèìåðîì â ãëàâå ñîçäàéòå AcmeStoreBundle:

    php app/console generate:bundle --namespace=Acme/StoreBundle

    5.8. Áàçû äàííûõ è Doctrine (Ìîäåëè)

    143

    Symfony Documentation, Âûïóñê 2.0
    Íàñòðîéêà áàçû
    Ïðåæäå ÷åì íà÷àòü âàì íóæíî íàñòðîèòü ñîåäèíåíèå ñ áàçîé äàííûõ. Ïî ñîãëàøåíèþ
    ýòà èíôîðìàöèÿ îáû÷íî íàñòðàèâàåòñÿ â ôàéëå app/cong/parameters.ini:

    ;app/cong/parameters.ini
    [parameters]
    database_driver = pdo_mysql
    database_host = localhost
    database_name = test_project
    database_user = root
    database_password = password
    Ïðèìå÷àíèå: Çàäàíèå íàñòðîåê ÷åðåç ôàéë parameters.ini âñåãî ëèøü ñîãëàøåíèå. Ïàðàìåòðû, çàäàííûå â ýòîì ôàéëå, ïåðåäàþòñÿ â ãëàâíóþ êîíôèãóðàöèþ ïðè íàñòðîéêå
    Doctrine:

    doctrine:
    dbal:
    driver: %database_driver%
    host: %database_host%
    dbname: %database_name%
    user: %database_user%
    password: %database_password%
    Ïðè ïîìîùè ðàçäåëåíèÿ íàñòðîåê íà ðàçíûå ôàéëû, âû ìîæåòå ïðîñòî äåðæàòü ðàçíûå âåðñèè ôàéëîâ íà êàæäîì ñåðâåðå. Âû òàê æå ìîæåòå äåðæàòü êîíôèãóðàöèþ
    áàçû äàííûõ (èëè ëþáóþ âàæíóþ èíôîðìàöèþ) çà ïðåäåëàìè âàøåãî ïðîåêòà, íàïðèìåð, âíóòðè íàñòðîåê Apache. Äëÿ áîëüøåé èíôîðìàöèè ñìîòðèòå How to Set External
    Parameters in the Service Container.
    Òåïåðü Doctrine çíàåò î âàøåé áàçå äàííûõ, âû ìîæåòå ñîçäàòü áàçó êîìàíäîé:

    php app/console doctrine:database:create
    Ñîçäàíèå êëàññà ñóùíîñòè (entity)
    Ïðåäñòàâèì, ÷òî âû ñîçäàåòå ïðèëîæåíèå, ãäå íóæíî îòîáðàçèòü ïðîäóêòû. Äàæå íå
    äóìàÿ î Doctrine èëè áàçàõ äàííûõ âû óæå çíàåòå, ÷òî âàì íóæåí îáúåêò Product äëÿ
    ïðåäñòàâëåíèÿ äàííûõ. Ñîçäàéòå ýòîò êëàññ âíóòðè äèðåêòîðèè Entity âíóòðè âàøåãî
    AcmeStoreBundle:

    // src/Acme/StoreBundle/Entity/Product.php
    namespace Acme\StoreBundle\Entity;

    144

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    class Product
    {
    protected $name;
    protected $price;
    }

    protected $description;

    Ýòîò êëàññ - ÷àñòî íàçûâàåìûé ñóùíîñòüþ, èìåÿ ââèäó áàçîâûé êëàññ, êîòîðûé ñîäåðæèò äàííûå - ïðîñòîé è ïîìîãàåò âûïîëíÿòü áèçíåñ-ïðàâèëà âàøåãî ïðèëîæåíèÿ.
    Ýòîò êëàññ íå ìîæåò ñîõðàíÿòü èíôîðìàöèþ â áàçó - ýòî ïðîñòîé PHP-êëàññ.
    Ñîâåò: Êîãäà âû óçíàëè êàê ðàáîòàåò Doctrine, âû ìîæåòå çàñòàâèòü Doctrine ñîçäàòü
    êëàññ ñóùíîñòè çà âàñ:

    php app/console doctrine:generate:entity --entity="AcmeStoreBundle:Product" --elds="name:string(255) price:

    Ñâÿçîâàíèå èíôîðìàöèè
    Doctrine ïîçâîëÿåò ðàáîòàòü ñ áàçàìè áîëåå èíòåðåñíûì ñïîñîáîì, ÷åì çàïðîñ ñòðîê
    òàáëèöû â ìàññèâ. Âìåñòî ýòîãî, Doctrine ïîçâîëÿåò ñîõðàíÿòü ñàìè îáúåêòû â áàçó
    è çàïðàøèâàòü îáúåêòû îáðàòíî. Ýòî ðàáîòàåò ïðè ïîìîùè ñâÿçûâàíèÿ PHP-êëàññà ñ
    òàáëèåö ÁÄ è ñâîéñòâ ýòîãî êëàññà ñî ñòîëáöàìè òàáëèöû:

    ×òîáû ñäåëàòü ýòî ñ Doctrine, âû äîëæíû ñîçäàòü ìåòàäàííûå èëè êîíôèãóðàöèþ,
    êîòîðàÿ ñêàæåò Doctrine êàê êëàññ Product è åãî ñâîéñòâà ñâÿçàíû ñ áàçîé äàííûõ. Ýòè
    ìåòîäàííûå ìîãóò áûòü îïèñàíû â íåñêîëüêèõ ôîðìàòàõ, âêëþ÷àÿ YAML, XML èëè
    ïðÿìî âíóòðè êëàññà Product ÷åðåç àííîòàöèè:
    Ïðèìå÷àíèå: A bundle can accept only one metadata denition format. For example, it's
    not possible to mix YAML metadata denitions with annotated PHP entity class denitions.
    5.8. Áàçû äàííûõ è Doctrine (Ìîäåëè)

    145

    Symfony Documentation, Âûïóñê 2.0

    • Annotations

    // src/Acme/StoreBundle/Entity/Product.php
    namespace Acme\StoreBundle\Entity;
    use Doctrine\ORM\Mapping as ORM;
    /**
    * @ORM\Entity
    * @ORM\Table(name="product")
    */
    class Product
    {
    /**
    * @ORM\Id
    * @ORM\Column(type="integer")
    * @ORM\GeneratedValue(strategy="AUTO")
    */
    protected $id;
    /**
    * @ORM\Column(type="string", length=100)
    */
    protected $name;
    /**
    * @ORM\Column(type="decimal", scale=2)
    */
    protected $price;

    }

    /**
    * @ORM\Column(type="text")
    */
    protected $description;

    • YAML

    # src/Acme/StoreBundle/Resources/cong/doctrine/Product.orm.yml
    Acme\StoreBundle\Entity\Product:
    type: entity
    table: product
    id:
    id:
    type: integer
    generator: { strategy: AUTO }
    146

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    elds:
    name:
    type: string
    length: 100
    price:
    type: decimal
    scale: 2
    description:
    type: text
    • XML

    <!-- src/Acme/StoreBundle/Resources/cong/doctrine/Product.orm.xml -->
    <doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
    http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
    <entity name="Acme\StoreBundle\Entity\Product" table="product">
    <id name="id" type="integer" column="id">
    <generator strategy="AUTO" />
    </id>
    <eld name="name" column="name" type="string" length="100" />
    <eld name="price" column="price" type="decimal" scale="2" />
    <eld name="description" column="description" type="text" />
    </entity>
    </doctrine-mapping>
    Ñîâåò: Íàñòðîêà table íå îáÿçàòåëüíà è ìîæåò áûòü îïóùåíà, òîãäà íàçâàíèå òàáëèöû
    áóäåò îïðåäåëåííî íà îñíîâå íàçâàíèÿ êëàññà..
    Doctrine allows you to choose from a wide variety of dierent eld types, each with their own
    options. For information on the available eld types, see the Doctrine Field Types Reference
    section.
    Ñì.òàêæå:
    You can also check out Doctrine's Basic Mapping Documentation for all details about
    mapping information. If you use annotations, you'll need to prepend all annotations with
    ORM\ (e.g. ORM\Column(..)), which is not shown in Doctrine's documentation. You'll also
    need to include the use Doctrine\ORM\Mapping as ORM; statement, which imports the
    ORM annotations prex.

    5.8. Áàçû äàííûõ è Doctrine (Ìîäåëè)

    147

    Symfony Documentation, Âûïóñê 2.0
    Îñòîðîæíî: Be careful that your class name and properties aren't mapped to a protected
    SQL keyword (such as group or user). For example, if your entity class name is Group,
    then, by default, your table name will be group, which will cause an SQL error in some
    engines. See Doctrine's Reserved SQL keywords documentation on how to properly escape
    these names.
    Ïðèìå÷àíèå: When using another library or program (ie. Doxygen) that uses annotations,
    you should place the @IgnoreAnnotation annotation on the class to indicate which
    annotations Symfony should ignore.
    For example, to prevent the @fn annotation from throwing an exception, add the following:

    /**
    * @IgnoreAnnotation("fn")
    */
    class Product

    Generating Getters and Setters
    Even though Doctrine now knows how to persist a Product object to the database, the class
    itself isn't really useful yet. Since Product is just a regular PHP class, you need to create
    getter and setter methods (e.g. getName(), setName()) in order to access its properties (since
    the properties are protected). Fortunately, Doctrine can do this for you by running:

    php app/console doctrine:generate:entities Acme/StoreBundle/Entity/Product
    This command makes sure that all of the getters and setters are generated for the Product
    class. This is a safe command - you can run it over and over again: it only generates getters
    and setters that don't exist (i.e. it doesn't replace your existing methods).
    Îñòîðîæíî: The doctrine:generate:entities command saves a backup of the original
    Product.php named Product.php~. In some cases, the presence of this le can cause
    a Cannot redeclare class error. It can be safely removed.
    You can also generate all known entities (i.e. any PHP class with Doctrine mapping
    information) of a bundle or an entire namespace:

    php app/console doctrine:generate:entities AcmeStoreBundle
    php app/console doctrine:generate:entities Acme
    Ïðèìå÷àíèå: Doctrine doesn't care whether your properties are protected or private, or
    whether or not you have a getter or setter function for a property. The getters and setters
    are generated here only because you'll need them to interact with your PHP object.
    148

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    Creating the Database Tables/Schema
    You now have a usable Product class with mapping information so that Doctrine knows
    exactly how to persist it. Of course, you don't yet have the corresponding product table in
    your database. Fortunately, Doctrine can automatically create all the database tables needed
    for every known entity in your application. To do this, run:

    php app/console doctrine:schema:update --force
    Ñîâåò: Actually, this command is incredibly powerful. It compares what your database
    should look like (based on the mapping information of your entities) with how it actually
    looks, and generates the SQL statements needed to update the database to where it should
    be. In other words, if you add a new property with mapping metadata to Product and run
    this task again, it will generate the alter table statement needed to add that new column
    to the existing products table.
    An even better way to take advantage of this functionality is via migrations, which allow
    you to generate these SQL statements and store them in migration classes that can be run
    systematically on your production server in order to track and migrate your database schema
    safely and reliably.
    Your database now has a fully-functional product table with columns that match the
    metadata you've specied.
    Persisting Objects to the Database
    Now that you have a mapped Product entity and corresponding product table, you're ready
    to persist data to the database. From inside a controller, this is pretty easy. Add the following
    method to the DefaultController of the bundle:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    // src/Acme/StoreBundle/Controller/DefaultController.php
    use Acme\StoreBundle\Entity\Product;
    use Symfony\Component\HttpFoundation\Response;
    // ...
    public function createAction()
    {
    $product = new Product();
    $product->setName('A Foo Bar');
    $product->setPrice('19.99');
    $product->setDescription('Lorem ipsum dolor');

    12

    5.8. Áàçû äàííûõ è Doctrine (Ìîäåëè)

    149

    Symfony Documentation, Âûïóñê 2.0

    $em = $this->getDoctrine()->getEntityManager();
    $em->persist($product);
    $em->ush();

    13
    14
    15
    16
    17
    18

    }

    return new Response('Created product id '.$product->getId());

    Ïðèìå÷àíèå: If you're following along with this example, you'll need to create a route that
    points to this action to see it in work.
    Let's walk through this example:

    • lines 8-11 In this section, you instantiate and work with the $product object like any
    other, normal PHP object;
    • line 13 This line fetches Doctrine's entity manager object, which is responsible for
    handling the process of persisting and fetching objects to and from the database;
    • line 14 The persist() method tells Doctrine to manage the $product object. This does
    not actually cause a query to be made to the database (yet).
    • line 15 When the ush() method is called, Doctrine looks through all of the objects
    that it's managing to see if they need to be persisted to the database. In this example,
    the $product object has not been persisted yet, so the entity manager executes an
    INSERT query and a row is created in the product table.
    Ïðèìå÷àíèå: In fact, since Doctrine is aware of all your managed entities, when you
    call the ush() method, it calculates an overall changeset and executes the most ecient
    query/queries possible. For example, if you persist a total of 100 Product objects and then
    subsequently call ush(), Doctrine will create a single prepared statement and re-use it for
    each insert. This pattern is called Unit of Work, and it's used because it's fast and ecient.
    When creating or updating objects, the workow is always the same. In the next section,
    you'll see how Doctrine is smart enough to automatically issue an UPDATE query if the
    record already exists in the database.
    Ñîâåò:
    Doctrine provides a library that allows you to programmatically
    load testing data into your project (i.e. xture data). For information, see
    /bundles/DoctrineFixturesBundle/index.

    150

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    Èçâëå÷åíèå îáúåêòîâ èç áàçû
    Èçâëå÷ü îáúåêò èç áàçû î÷åíü ïðîñòî. Äëÿ ïðèìåðà, âû íàñòðîèëè ìàðøðóò äëÿ îòîáðàæåíèÿ îòäåëüíîãî Product â çàâèñèìîñòè îò åãî çíà÷åíèÿ id:

    public function showAction($id)
    {
    $product = $this->getDoctrine()
    ->getRepository('AcmeStoreBundle:Product')
    ->nd($id);
    if (!$product) {
    throw $this->createNotFoundException('No product found for id '.$id);
    }
    }

    // do something, like pass the $product object into a template

    When you query for a particular type of object, you always use what's known as its
    repository. You can think of a repository as a PHP class whose only job is to help you
    fetch entities of a certain class. You can access the repository object for an entity class via:

    $repository = $this->getDoctrine()
    ->getRepository('AcmeStoreBundle:Product');
    Ïðèìå÷àíèå: The AcmeStoreBundle:Product string is a shortcut you can use anywhere in
    Doctrine instead of the full class name of the entity (i.e. Acme\StoreBundle\Entity\Product).
    As long as your entity lives under the Entity namespace of your bundle, this will work.
    Once you have your repository, you have access to all sorts of helpful methods:

    // query by the primary key (usually "id")
    $product = $repository->nd($id);
    // dynamic method names to nd based on a column value
    $product = $repository->ndOneById($id);
    $product = $repository->ndOneByName('foo');
    // nd *all* products
    $products = $repository->ndAll();
    // nd a group of products based on an arbitrary column value
    $products = $repository->ndByPrice(19.99);
    Ïðèìå÷àíèå: Of course, you can also issue complex queries, which you'll learn more about
    in the Querying for Objects section.
    5.8. Áàçû äàííûõ è Doctrine (Ìîäåëè)

    151

    Symfony Documentation, Âûïóñê 2.0

    You can also take advantage of the useful ndBy and ndOneBy methods to easily fetch
    objects based on multiple conditions:

    // query for one product matching be name and price
    $product = $repository->ndOneBy(array('name' => 'foo', 'price' => 19.99));
    // query for all products matching the name, ordered by price
    $product = $repository->ndBy(
    array('name' => 'foo'),
    array('price' => 'ASC')
    );
    Ñîâåò: When you render any page, you can see how many queries were made in the bottom
    right corner of the web debug toolbar.

    If you click the icon, the proler will open, showing you the exact queries that were made.

    Updating an Object
    Once you've fetched an object from Doctrine, updating it is easy. Suppose you have a route
    that maps a product id to an update action in a controller:

    public function updateAction($id)
    {
    $em = $this->getDoctrine()->getEntityManager();
    $product = $em->getRepository('AcmeStoreBundle:Product')->nd($id);
    if (!$product) {
    throw $this->createNotFoundException('No product found for id '.$id);
    }
    $product->setName('New product name!');
    $em->ush();
    152

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    }

    return $this->redirect($this->generateUrl('homepage'));

    Updating an object involves just three steps:
    1. fetching the object from Doctrine;
    2. modifying the object;
    3. calling ush() on the entity manager
    Notice that calling $em->persist($product) isn't necessary. Recall that this method simply
    tells Doctrine to manage or watch the $product object. In this case, since you fetched the
    $product object from Doctrine, it's already managed.
    Deleting an Object
    Deleting an object is very similar, but requires a call to the remove() method of the entity
    manager:

    $em->remove($product);
    $em->ush();
    As you might expect, the remove() method noties Doctrine that you'd like to remove the
    given entity from the database. The actual DELETE query, however, isn't actually executed
    until the ush() method is called.
    5.8.2 Querying for Objects

    You've already seen how the repository object allows you to run basic queries without any
    work:

    $repository->nd($id);
    $repository->ndOneByName('Foo');
    Of course, Doctrine also allows you to write more complex queries using the Doctrine Query
    Language (DQL). DQL is similar to SQL except that you should imagine that you're querying
    for one or more objects of an entity class (e.g. Product) instead of querying for rows on a
    table (e.g. product).
    When querying in Doctrine, you have two options: writing pure Doctrine queries or using
    Doctrine's Query Builder.

    5.8. Áàçû äàííûõ è Doctrine (Ìîäåëè)

    153

    Symfony Documentation, Âûïóñê 2.0
    Querying for Objects with DQL
    Imaging that you want to query for products, but only return products that cost more than
    19.99, ordered from cheapest to most expensive. From inside a controller, do the following:

    $em = $this->getDoctrine()->getEntityManager();
    $query = $em->createQuery(
    'SELECT p FROM AcmeStoreBundle:Product p WHERE p.price > :price ORDER BY p.price ASC'
    )->setParameter('price', '19.99');
    $products = $query->getResult();
    If you're comfortable with SQL, then DQL should feel very natural. The biggest dierence
    is that you need to think in terms of objects instead of rows in a database. For this reason,
    you select from AcmeStoreBundle:Product and then alias it as p.
    The getResult() method returns an array of results. If you're querying for just one object,
    you can use the getSingleResult() method instead:

    $product = $query->getSingleResult();
    Îñòîðîæíî: The getSingleResult() method throws a Doctrine\ORM\NoResultException
    exception if no results are returned and a Doctrine\ORM\NonUniqueResultException if
    more than one result is returned. If you use this method, you may need to wrap it in a trycatch block and ensure that only one result is returned (if you're querying on something
    that could feasibly return more than one result):

    $query = $em->createQuery('SELECT ....')
    ->setMaxResults(1);
    try {
    $product = $query->getSingleResult();
    } catch (\Doctrine\Orm\NoResultException $e) {
    $product = null;
    }
    // ...
    The DQL syntax is incredibly powerful, allowing you to easily join between entities (the
    topic of relations will be covered later), group, etc. For more information, see the ocial
    Doctrine Doctrine Query Language documentation.

    154

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    Setting Parameters
    Take note of the setParameter() method. When working with Doctrine, it's always a
    good idea to set any external values as placeholders, which was done in the above
    query:

    ... WHERE p.price > :price ...
    You can then set the value of the price placeholder by calling the setParameter() method:

    ->setParameter('price', '19.99')
    Using parameters instead of placing values directly in the query string is done to prevent
    SQL injection attacks and should always be done. If you're using multiple parameters,
    you can set their values at once using the setParameters() method:

    ->setParameters(array(
    'price' => '19.99',
    'name' => 'Foo',
    ))

    Using Doctrine's Query Builder
    Instead of writing the queries directly, you can alternatively use Doctrine's QueryBuilder to
    do the same job using a nice, object-oriented interface. If you use an IDE, you can also take
    advantage of auto-completion as you type the method names. From inside a controller:

    $repository = $this->getDoctrine()
    ->getRepository('AcmeStoreBundle:Product');
    $query = $repository->createQueryBuilder('p')
    ->where('p.price > :price')
    ->setParameter('price', '19.99')
    ->orderBy('p.price', 'ASC')
    ->getQuery();
    $products = $query->getResult();
    The QueryBuilder object contains every method necessary to build your query. By calling
    the getQuery() method, the query builder returns a normal Query object, which is the same
    object you built directly in the previous section.
    For more information on Doctrine's Query Builder, consult Doctrine's Query Builder
    documentation.

    5.8. Áàçû äàííûõ è Doctrine (Ìîäåëè)

    155

    Symfony Documentation, Âûïóñê 2.0
    Custom Repository Classes
    In the previous sections, you began constructing and using more complex queries from inside
    a controller. In order to isolate, test and reuse these queries, it's a good idea to create a
    custom repository class for your entity and add methods with your query logic there.
    To do this, add the name of the repository class to your mapping denition.

    • Annotations

    // src/Acme/StoreBundle/Entity/Product.php
    namespace Acme\StoreBundle\Entity;
    use Doctrine\ORM\Mapping as ORM;
    /**
    * @ORM\Entity(repositoryClass="Acme\StoreBundle\Repository\ProductRepository")
    */
    class Product
    {
    //...
    }
    • YAML

    # src/Acme/StoreBundle/Resources/cong/doctrine/Product.orm.yml
    Acme\StoreBundle\Entity\Product:
    type: entity
    repositoryClass: Acme\StoreBundle\Repository\ProductRepository
    # ...
    • XML

    <!-- src/Acme/StoreBundle/Resources/cong/doctrine/Product.orm.xml -->
    <!-- ... -->
    <doctrine-mapping>
    <entity name="Acme\StoreBundle\Entity\Product"
    repository-class="Acme\StoreBundle\Repository\ProductRepository">
    <!-- ... -->
    </entity>
    </doctrine-mapping>
    Doctrine can generate the repository class for you by running the same command used earlier
    to generate the missing getter and setter methods:

    php app/console doctrine:generate:entities Acme
    Next, add a new method - ndAllOrderedByName() - to the newly generated repository
    156

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    class. This method will query for all of the Product entities, ordered alphabetically.

    // src/Acme/StoreBundle/Repository/ProductRepository.php
    namespace Acme\StoreBundle\Repository;
    use Doctrine\ORM\EntityRepository;
    class ProductRepository extends EntityRepository
    {
    public function ndAllOrderedByName()
    {
    return $this->getEntityManager()
    ->createQuery('SELECT p FROM AcmeStoreBundle:Product p ORDER BY p.name ASC')
    ->getResult();
    }
    }
    Ñîâåò: The entity manager can be accessed via $this->getEntityManager() from inside the
    repository.
    You can use this new method just like the default nder methods of the repository:

    $em = $this->getDoctrine()->getEntityManager();
    $products = $em->getRepository('AcmeStoreBundle:Product')
    ->ndAllOrderedByName();
    Ïðèìå÷àíèå: When using a custom repository class, you still have access to the default
    nder methods such as nd() and ndAll().

    5.8.3 Entity Relationships/Associations

    Suppose that the products in your application all belong to exactly one category. In this
    case, you'll need a Category object and a way to relate a Product object to a Category
    object. Start by creating the Category entity. Since you know that you'll eventually need to
    persist the class through Doctrine, you can let Doctrine create the class for you.

    php app/console doctrine:generate:entity --entity="AcmeStoreBundle:Category" --elds="name:string(255)"
    This task generates the Category entity for you, with an id eld, a name eld and the
    associated getter and setter functions.

    5.8. Áàçû äàííûõ è Doctrine (Ìîäåëè)

    157

    Symfony Documentation, Âûïóñê 2.0
    Relationship Mapping Metadata
    To relate the Category and Product entities, start by creating a products property on the
    Category class:

    // src/Acme/StoreBundle/Entity/Category.php
    // ...
    use Doctrine\Common\Collections\ArrayCollection;
    class Category
    {
    // ...
    /**
    * @ORM\OneToMany(targetEntity="Product", mappedBy="category")
    */
    protected $products;

    }

    public function __construct()
    {
    $this->products = new ArrayCollection();
    }

    First, since a Category object will relate to many Product objects, a products array property
    is added to hold those Product objects. Again, this isn't done because Doctrine needs it,
    but instead because it makes sense in the application for each Category to hold an array of
    Product objects.
    Ïðèìå÷àíèå: The code in the __construct() method is important because Doctrine requires
    the $products property to be an ArrayCollection object. This object looks and acts almost
    exactly like an array, but has some added exibility. If this makes you uncomfortable, don't
    worry. Just imagine that it's an array and you'll be in good shape.
    Next, since each Product class can relate to exactly one Category object, you'll want to add
    a $category property to the Product class:

    // src/Acme/StoreBundle/Entity/Product.php
    // ...
    class Product
    {
    // ...
    /**
    * @ORM\ManyToOne(targetEntity="Category", inversedBy="products")
    158

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    }

    * @ORM\JoinColumn(name="category_id", referencedColumnName="id")
    */
    protected $category;

    Finally, now that you've added a new property to both the Category and Product classes,
    tell Doctrine to generate the missing getter and setter methods for you:

    php app/console doctrine:generate:entities Acme
    Ignore the Doctrine metadata for a moment. You now have two classes - Category and
    Product with a natural one-to-many relationship. The Category class holds an array of
    Product objects and the Product object can hold one Category object. In other words you've built your classes in a way that makes sense for your needs. The fact that the data
    needs to be persisted to a database is always secondary.
    Now, look at the metadata above the $category property on the Product class. The
    information here tells doctrine that the related class is Category and that it should store
    the id of the category record on a category_id eld that lives on the product table. In other
    words, the related Category object will be stored on the $category property, but behind
    the scenes, Doctrine will persist this relationship by storing the category's id value on a
    category_id column of the product table.

    5.8. Áàçû äàííûõ è Doctrine (Ìîäåëè)

    159

    Symfony Documentation, Âûïóñê 2.0

    The metadata above the $products property of the Category object is less important,
    and simply tells Doctrine to look at the Product.category property to gure out how the
    relationship is mapped.
    Before you continue, be sure to tell Doctrine to add the new category table, and
    product.category_id column, and new foreign key:

    php app/console doctrine:schema:update --force
    Ïðèìå÷àíèå: This task should only be really used during development. For a more
    robust method of systematically updating your production database, read about Doctrine
    migrations.

    160

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    Saving Related Entities
    Now, let's see the code in action. Imagine you're inside a controller:

    // ...
    use Acme\StoreBundle\Entity\Category;
    use Acme\StoreBundle\Entity\Product;
    use Symfony\Component\HttpFoundation\Response;
    // ...
    class DefaultController extends Controller
    {
    public function createProductAction()
    {
    $category = new Category();
    $category->setName('Main Products');
    $product = new Product();
    $product->setName('Foo');
    $product->setPrice(19.99);
    // relate this product to the category
    $product->setCategory($category);
    $em = $this->getDoctrine()->getEntityManager();
    $em->persist($category);
    $em->persist($product);
    $em->ush();

    }

    }

    return new Response(
    'Created product id: '.$product->getId().' and category id: '.$category->getId()
    );

    Now, a single row is added to both the category and product tables. The product.category_id
    column for the new product is set to whatever the id is of the new category. Doctrine manages
    the persistence of this relationship for you.
    Fetching Related Objects
    When you need to fetch associated objects, your workow looks just like it did before. First,
    fetch a $product object and then access its related Category:

    public function showAction($id)
    {
    $product = $this->getDoctrine()
    5.8. Áàçû äàííûõ è Doctrine (Ìîäåëè)

    161

    Symfony Documentation, Âûïóñê 2.0

    ->getRepository('AcmeStoreBundle:Product')
    ->nd($id);
    $categoryName = $product->getCategory()->getName();
    }

    // ...

    In this example, you rst query for a Product object based on the product's id. This issues a
    query for just the product data and hydrates the $product object with that data. Later, when
    you call $product->getCategory()->getName(), Doctrine silently makes a second query to
    nd the Category that's related to this Product. It prepares the $category object and returns
    it to you.

    What's important is the fact that you have easy access to the product's related category,
    but the category data isn't actually retrieved until you ask for the category (i.e. it's lazily
    loaded).
    You can also query in the other direction:

    public function showProductAction($id)
    {
    $category = $this->getDoctrine()
    ->getRepository('AcmeStoreBundle:Category')
    ->nd($id);

    162

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    $products = $category->getProducts();
    }

    // ...

    In this case, the same things occurs: you rst query out for a single Category object, and
    then Doctrine makes a second query to retrieve the related Product objects, but only once/if
    you ask for them (i.e. when you call ->getProducts()). The $products variable is an array
    of all Product objects that relate to the given Category object via their category_id value.
    Relationships and Proxy Classes
    This lazy loading is possible because, when necessary, Doctrine returns a proxy object
    in place of the true object. Look again at the above example:

    $product = $this->getDoctrine()
    ->getRepository('AcmeStoreBundle:Product')
    ->nd($id);
    $category = $product->getCategory();
    // prints "Proxies\AcmeStoreBundleEntityCategoryProxy"
    echo get_class($category);
    This proxy object extends the true Category object, and looks and acts exactly like
    it. The dierence is that, by using a proxy object, Doctrine can delay querying for
    the real Category data until you actually need that data (e.g. until you call $category>getName()).
    The proxy classes are generated by Doctrine and stored in the cache directory. And
    though you'll probably never even notice that your $category object is actually a proxy
    object, it's important to keep in mind.
    In the next section, when you retrieve the product and category data all at once (via
    a join), Doctrine will return the true Category object, since nothing needs to be lazily
    loaded.

    Joining to Related Records
    In the above examples, two queries were made - one for the original object (e.g. a Category)
    and one for the related object(s) (e.g. the Product objects).
    Ñîâåò: Remember that you can see all of the queries made during a request via the web
    debug toolbar.
    Of course, if you know up front that you'll need to access both objects, you can avoid
    5.8. Áàçû äàííûõ è Doctrine (Ìîäåëè)

    163

    Symfony Documentation, Âûïóñê 2.0
    the second query by issuing a join in the original query. Add the following method to the
    ProductRepository class:

    // src/Acme/StoreBundle/Repository/ProductRepository.php
    public function ndOneByIdJoinedToCategory($id)
    {
    $query = $this->getEntityManager()
    ->createQuery('
    SELECT p, c FROM AcmeStoreBundle:Product p
    JOIN p.category c
    WHERE p.id = :id'
    )->setParameter('id', $id);

    }

    try {
    return $query->getSingleResult();
    } catch (\Doctrine\ORM\NoResultException $e) {
    return null;
    }

    Now, you can use this method in your controller to query for a Product object and its related
    Category with just one query:

    public function showAction($id)
    {
    $product = $this->getDoctrine()
    ->getRepository('AcmeStoreBundle:Product')
    ->ndOneByIdJoinedToCategory($id);
    $category = $product->getCategory();
    }

    // ...

    More Information on Associations
    This section has been an introduction to one common type of entity relationship, the
    one-to-many relationship. For more advanced details and examples of how to use other
    types of relations (e.g. one-to-one, many-to-many), see Doctrine's Association Mapping
    Documentation.
    Ïðèìå÷àíèå: If you're using annotations, you'll need to prepend all annotations with ORM\
    (e.g. ORM\OneToMany), which is not reected in Doctrine's documentation. You'll also need
    to include the use Doctrine\ORM\Mapping as ORM; statement, which imports the ORM
    annotations prex.
    164

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    5.8.4 Conguration

    Doctrine is highly congurable, though you probably won't ever need to worry about most
    of its options. To nd out more about conguring Doctrine, see the Doctrine section of the
    reference manual.
    5.8.5 Lifecycle Callbacks

    Sometimes, you need to perform an action right before or after an entity is inserted, updated,
    or deleted. These types of actions are known as lifecycle callbacks, as they're callback
    methods that you need to execute during dierent stages of the lifecycle of an entity (e.g.
    the entity is inserted, updated, deleted, etc).
    If you're using annotations for your metadata, start by enabling the lifecycle callbacks. This
    is not necessary if you're using YAML or XML for your mapping:

    /**
    * @ORM\Entity()
    * @ORM\HasLifecycleCallbacks()
    */
    class Product
    {
    // ...
    }
    Now, you can tell Doctrine to execute a method on any of the available lifecycle events. For
    example, suppose you want to set a created date column to the current date, only when the
    entity is rst persisted (i.e. inserted):

    • Annotations

    /**
    * @ORM\prePersist
    */
    public function setCreatedValue()
    {
    $this->created = new \DateTime();
    }
    • YAML

    # src/Acme/StoreBundle/Resources/cong/doctrine/Product.orm.yml
    Acme\StoreBundle\Entity\Product:
    type: entity
    5.8. Áàçû äàííûõ è Doctrine (Ìîäåëè)

    165

    Symfony Documentation, Âûïóñê 2.0

    # ...
    lifecycleCallbacks:
    prePersist: [ setCreatedValue ]
    • XML

    <!-- src/Acme/StoreBundle/Resources/cong/doctrine/Product.orm.xml -->
    <!-- ... -->
    <doctrine-mapping>
    <entity name="Acme\StoreBundle\Entity\Product">
    <!-- ... -->
    <lifecycle-callbacks>
    <lifecycle-callback type="prePersist" method="setCreatedValue" />
    </lifecycle-callbacks>
    </entity>
    </doctrine-mapping>
    Ïðèìå÷àíèå: The above example assumes that you've created and mapped a created
    property (not shown here).
    Now, right before the entity is rst persisted, Doctrine will automatically call this method
    and the created eld will be set to the current date.
    This can be repeated for any of the other lifecycle events, which include:

    • preRemove
    • postRemove
    • prePersist
    • postPersist
    • preUpdate
    • postUpdate
    • postLoad
    • loadClassMetadata
    For more information on what these lifecycle events mean and lifecycle callbacks in general,
    see Doctrine's Lifecycle Events documentation

    166

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    Lifecycle Callbacks and Event Listeners
    Notice that the setCreatedValue() method receives no arguments. This is always the case
    for lifecylce callbacks and is intentional: lifecycle callbacks should be simple methods
    that are concerned with internally transforming data in the entity (e.g. setting a
    created/updated eld, generating a slug value).
    If you need to do some heavier lifting - like perform logging or send an email - you
    should register an external class as an event listener or subscriber and give it access to
    whatever resources you need. For more information, see Registering Event Listeners and
    Subscribers.

    5.8.6 Doctrine Extensions: Timestampable, Sluggable, etc.

    Doctrine is quite exible, and a number of third-party extensions are available that allow
    you to easily perform repeated and common tasks on your entities. These include thing such
    as Sluggable, Timestampable, Loggable, Translatable, and Tree.
    For more information on how to nd and use these extensions, see the cookbook article about
    using common Doctrine extensions.
    5.8.7 Doctrine Field Types Reference

    Doctrine comes with a large number of eld types available. Each of these maps a PHP data
    type to a specic column type in whatever database you're using. The following types are
    supported in Doctrine:

    • Strings
     string (used for shorter strings)
     text (used for larger strings)

    • Numbers
     integer
     smallint
     bigint
     decimal
     oat

    • Dates and Times (use a DateTime object for these elds in PHP)
     date
     time
    5.8. Áàçû äàííûõ è Doctrine (Ìîäåëè)

    167

    Symfony Documentation, Âûïóñê 2.0
     datetime

    • Other Types
     boolean
     object (serialized and stored in a CLOB eld)
     array (serialized and stored in a CLOB eld)
    For more information, see Doctrine's Mapping Types documentation.
    Field Options
    Each eld can have a set of options applied to it. The available options include type (defaults
    to string), name, length, unique and nullable. Take a few annotations examples:

    /**
    * A string eld with length 255 that cannot be null
    * (reecting the default values for the "type", "length" and *nullable* options)
    *
    * @ORM\Column()
    */
    protected $name;
    /**
    * A string eld of length 150 that persists to an "email_address" column
    * and has a unique index.
    *
    * @ORM\Column(name="email_address", unique="true", length="150")
    */
    protected $email;
    Ïðèìå÷àíèå: There are a few more options not listed here. For more details, see Doctrine's
    Property Mapping documentation

    5.8.8 Console Commands

    The Doctrine2 ORM integration oers several console commands under the doctrine
    namespace. To view the command list you can run the console without any arguments:

    php app/console
    A list of available command will print out, many of which start with the doctrine: prex. You
    can nd out more information about any of these commands (or any Symfony command) by

    168

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    running the help command. For example, to get details about the doctrine:database:create
    task, run:

    php app/console help doctrine:database:create
    Some notable or interesting tasks include:

    • doctrine:ensure-production-settings - checks to see if the current environment is
    congured eciently for production. This should always be run in the prod
    environment:

    php app/console doctrine:ensure-production-settings --env=prod
    • doctrine:mapping:import - allows Doctrine to introspect an existing database and
    create mapping information. For more information, see How to generate Entities from
    an Existing Database.
    • doctrine:mapping:info - tells you all of the entities that Doctrine is aware of and whether
    or not there are any basic errors with the mapping.
    • doctrine:query:dql and doctrine:query:sql - allow you to execute DQL or SQL queries
    directly from the command line.
    Ïðèìå÷àíèå: To be able to load data xtures to your database, you will need to
    have the DoctrineFixturesBundle bundle installed. To learn how to do it, read the
    /bundles/DoctrineFixturesBundle/index entry of the documentation.

    5.8.9 Summary

    With Doctrine, you can focus on your objects and how they're useful in your application
    and worry about database persistence second. This is because Doctrine allows you to use
    any PHP object to hold your data and relies on mapping metadata information to map an
    object's data to a particular database table.
    And even though Doctrine revolves around a simple concept, it's incredibly powerful, allowing
    you to create complex queries and subscribe to events that allow you to take dierent actions
    as objects go through their persistence lifecycle.
    For more information about Doctrine, see the Doctrine section of the cookbook, which
    includes the following articles:

    • /bundles/DoctrineFixturesBundle/index
    • Doctrine Extensions: Timestampable: Sluggable, Translatable, etc.

    5.8. Áàçû äàííûõ è Doctrine (Ìîäåëè)

    169

    Symfony Documentation, Âûïóñê 2.0

    5.9 Òåñòèðîâàíèå

    Êàê òîëüêî âû ïèøåòå íîâóþ ñòðîêó êîäà, âû òàêæå ïîòåíöèàëüíî äîáàâëÿåòå íîâûå
    îøèáêè. Àâòîìàòè÷åñêèå òåñòû äîëæíû çàùèòèòü âàñ è ýòî ðóêîâîäñòâî ïîêàæåò êàê
    ïèñàòü ìîäóëüíûå è ôóíêöèîíàëüíûå òåñòû äëÿ ïðèëîæåíèÿ Symfony2.
    5.9.1 Ôðåéìâîðê äëÿ òåñòèðîâàíèÿ

    Òåñòû Symfony2 ïîëàãàþòñÿ íà PHPUnit, íà åãî ëó÷øèå ìåòîäèêè è íåêîòîðûå ñîãëàøåíèÿ. Çäåñü íå îïèñûâàåòñÿ ñàì PHPUnit, íî åñëè îí âàì íå çíàêîì, ìîæåòå ïðî÷åñòü
    îòëè÷íóþ äîêóìåíòàöèþ.
    Ïðèìå÷àíèå: Symfony2 ðàáîòàåò ñ PHPUnit 3.5.11 èëè ñòàðøå.
    Èçíà÷àëüíî PHPUnit íàñòðîåí ÷òîáû èñêàòü òåñòû â ïîäïàïêàõ Tests/ âíóòðè áàíäëîâ:

    <!-- app/phpunit.xml.dist -->
    <phpunit bootstrap="../src/autoload.php">
    <testsuites>
    <testsuite name="Project Test Suite">
    <directory>../src/*/*Bundle/Tests</directory>
    </testsuite>
    </testsuites>
    ...
    </phpunit>
    Âûïîëíèòü êîìïëåêò òåñòîâ äëÿ äàííîãî ïðèëîæåíèÿ ïðîñòî:

    # óêàæèòå ïàïêó ñ êîíôèãàìè â êîìàíäíîé ñòðîêå
    $ phpunit -c app/
    # èëè çàïóñòèòå phpunit èç ïàïêè ïðèëîæåíèÿ
    $ cd app/
    $ phpunit
    Ñîâåò: Ïîêðûòèå êîäà ìîæåò áûòü ïîëó÷åíî ñ ïîìîùüþ îïöèè --coverage-html.

    170

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    5.9.2 Ìîäóëüíûå òåñòû

    Íàïèñàíèå ìîäóëüíûõ òåñòîâ äëÿ Symfony2 íå îòëè÷àåòñÿ îò íàïèñàíèÿ
    ñòàíäàðòíûõ ìîäóëüíûõ òåñòîâ äëÿ PHPUnit. Ïî ñîãëàøåíèþ ðåêîìåíäóåòñÿ ïîâòîðÿòü ñòðóêòóðó ïàïêè áàíäëà â åãî ïîäïàïêå Tests/. Òàêèì îáðàçîì ïèøèòå òåñòû äëÿ êëàññà Acme\HelloBundle\Model\Article â ôàéëå
    Acme/HelloBundle/Tests/Model/ArticleTest.php.
     ìîäóëüíîì òåñòå àâòîçàãðóçêà óæå âêëþ÷åíà ÷åðåç ôàéë src/autoload.php (ýòî íàñòðîåíî ïî óìîë÷àíèþ â ôàéëå phpunit.xml.dist).
    Âûïîëíèòü òåñòû äëÿ çàäàííîãî ôàéëà èëè ïàïêè òàêæå ïðîñòî:

    # çàïóñòèòü âñå òåñòû äëÿ Controller
    $ phpunit -c app src/Acme/HelloBundle/Tests/Controller/
    # çàïóñòèòü âñå òåñòû äëÿ Model
    $ phpunit -c app src/Acme/HelloBundle/Tests/Model/
    # çàïóñòèòü òåñòû äëÿ êëàññà Article
    $ phpunit -c app src/Acme/HelloBundle/Tests/Model/ArticleTest.php
    # çàïóñòèòü âñå òåñòû äëÿ öåëîãî Bundle
    $ phpunit -c app src/Acme/HelloBundle/

    5.9.3 Ôóíêöèîíàëüíûå òåñòû

    Ôóíêöèîíàëüíûå òåñòû ïðîâåðÿþò îáúåäèíåíèÿ ðàçëè÷íûõ ñëî¼â ïðèëîæåíèÿ (îò
    ìàðøðóòèçàöèè äî âèäîâ). Îíè íå îòëè÷àþòñÿ îò ìîäóëüíûõ òåñòîâ íàñòîëüêî, íàñêîëüêî PHPUnit ïîçâîëÿåò ýòî, íî èìåþò êîíêðåòíûé ðàáî÷èé ïðîöåññ:

    • Ñäåëàòü çàïðîñ;
    • Ïðîòåñòèðîâàòü îòâåò;
    • Êëèêíóòü ïî ññûëêå èëè îòïðàâèòü ôîðìó;
    • Ïðîòåñòèðîâàòü îòâåò;
    • Ïðîôèëüòðîâàòü è ïîâòîðèòü.
    Çàïðîñû, êëèêè è îòïðàâêè âûïîëíÿþòñÿ êëèåíòîì, êîòîðûé çíàåò êàê îáùàòüñÿ ñ ïðèëîæåíèåì. ×òîáû âîñïîëüçîâàòüñÿ òàêèì êëèåíòîì, òåñòû äîëæíû íàñëåäîâàòü êëàññ
    Symfony2 WebTestCase. Ñòàíäàðòíîå èçäàíèå ïîñòàâëÿåòñÿ ñ ïðîñòûì ôóíêöèîíàëüíûì
    òåñòîì äëÿ DemoController, ïðåäñòàâëÿþùèì ñîáîé ñëåäóþùåå:

    // src/Acme/DemoBundle/Tests/Controller/DemoControllerTest.php
    namespace Acme\DemoBundle\Tests\Controller;
    5.9. Òåñòèðîâàíèå

    171

    Symfony Documentation, Âûïóñê 2.0

    use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
    class DemoControllerTest extends WebTestCase
    {
    public function testIndex()
    {
    $client = static::createClient();
    $crawler = $client->request('GET', '/demo/hello/Fabien');

    }

    }

    $this->assertTrue($crawler->lter('html:contains("Hello Fabien")')->count() > 0);

    Ìåòîä createClient() âîçâðàùàåò êëèåíòà, ïðèâÿçàííîãî ê òåêóùåìó ïðèëîæåíèþ:

    $crawler = $client->request('GET', '/demo/hello/Fabien');
    Ìåòîä request() âîçâðàùàåò îáúåêò Crawler, èñïîëüçóåìûé äëÿ âûáîðà ýëåìåíòîâ â
    Response, äëÿ êëèêîâ ïî ññûëêàì è îòïðàâêå ôîðì.
    Ñîâåò: Crawler ìîæåò èñïîëüçîâàòüñÿ òîëüêî â òîì ñëó÷àå, åñëè ñîäåðæèìîå Response
    ýòî XML èëè HTML äîêóìåíò. Äëÿ äðóãèõ òèïîâ íóæíî ïîëó÷àòü ñîäåðæèìîå Response
    ÷åðåç $client->getResponse()->getContent().
    You can set the content-type of the request to JSON by adding `HTTP_CONTENT_TYPE'
    => `application/json'.

    Ñîâåò: The full signature of the request() method is:

    request($method,
    $uri,
    array $parameters = array(),
    array $les = array(),
    array $server = array(),
    $content = null,
    $changeHistory = true
    )
    ×òîáû êëèêíóòü ïî ññûëêå, ñíà÷àëà âûáåðèòå å¼ ñ ïîìîùüþ Crawler, èñïîëüçóÿ âûðàæåíèå XPath èëè CSS ñåëåêòîð, çàòåì êëèêíèòå ïî íåé ñ ïîìîùüþ Client:

    $link = $crawler->lter('a:contains("Greet")')->eq(1)->link();
    $crawler = $client->click($link);
    172

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    Îòïðàâêà ôîðìû ïðîèñõîäèò ñõîæèì îáðàçîì: âûáåðèòå êíîïêó íà ôîðìå, ïî æåëàíèþ
    ïåðåîïðåäåëèòå êàêèå-íèáóäü çíà÷åíèÿ ôîðìû, è îòïðàâüòå å¼:

    $form = $crawler->selectButton('submit')->form();
    // óñòàíàâëèâàåò êàêèå-íèáóäü çíà÷åíèÿ
    $form['name'] = 'Lucas';
    // îòïðàâëÿåò ôîðìó
    $crawler = $client->submit($form);
    Êàæäîå ïîëå Form èìååò îïðåäåë¼ííûå ìåòîäû, çàâèñÿùèå îò åãî òèïà:

    // çàïîëíÿåò ïîëå input
    $form['name'] = 'Lucas';
    // âûáèðàåò option èëè radio
    $form['country']->select('France');
    // ñòàâèò ãàëî÷êó â checkbox
    $form['like_symfony']->tick();
    // çàãðóæàåò ôàéë
    $form['photo']->upload('/path/to/lucas.jpg');
    Âìåñòî èçìåíåíèÿ îäíîãî ïîëÿ çà ðàç, ìîæíî ïåðåäàòü ìàññèâ çíà÷åíèé ìåòîäó submit():

    $crawler = $client->submit($form, array(
    'name'
    => 'Lucas',
    'country'
    => 'France',
    'like_symfony' => true,
    'photo'
    => '/path/to/lucas.jpg',
    ));
    Òåïåðü, êîãäà âû ñ ë¼ãêîñòüþ ìîæåòå ïåðåìåùàòüñÿ ïî ïðèëîæåíèþ, âîñïîëüçóéòåñü
    óòâåðæäåíèÿìè ÷òîáû ïðîâåðèòü îæèäàåìûå äåéñòâèÿ. Âîñïîëüçóéòåñü Crawler ÷òîáû
    ñäåëàòü óòâåðæäåíèÿ äëÿ DOM:

    // Óòâåðæäàåò ÷òî îòâåò ñîîòâåñòâóåò çàäàííîìó CSS ñåëåêòîðó.
    $this->assertTrue($crawler->lter('h1')->count() > 0);
    Èëè ïðîâåðüòå ñîäåðæèìîå Response íàïðÿìóþ, åñëè õîòèòå óáåäèòüñÿ ÷òî åãî ñîäåðæèìîå âêëþ÷àåò êàêîé-òî òåêñò, èëè ÷òî Response íå ÿâëÿåòñÿ äîêóìåíòîì XML/HTML:

    $this->assertRegExp('/Hello Fabien/', $client->getResponse()->getContent());

    5.9. Òåñòèðîâàíèå

    173

    Symfony Documentation, Âûïóñê 2.0
    Ïîëåçíûå óòâåðæäåíèÿ
    Íåñêîëüêî ïîçæå âû çàìåòèòå ÷òî âñåãäà ïèøèòå òèïè÷íûå óòâåðæäåíèÿ. Âîò ñïèñîê
    íàèáîëåå îáùèõ è ïîëåçíûõ óòâåðæäåíèé, ÷òîáû âû ñìîãëè íà÷àòü áûñòðåå:

    // Óòâåðæäàåò ÷òî îòâåò ñîîòâåñòâóåò çàäàííîìó CSS ñåëåêòîðó.
    $this->assertTrue($crawler->lter($selector)->count() > 0);
    // Óòâåðæäàåò ÷òî îòâåò ñîîòâåñòâóåò çàäàííîìó CSS ñåëåêòîðó n ðàç.
    $this->assertEquals($count, $crawler->lter($selector)->count());
    // Óòâåðæäàåò ÷òî çàãîëîâîê îòâåòà èìååò óêàçàííîå çíà÷åíèå.
    $this->assertTrue($client->getResponse()->headers->contains($key, $value));
    // Óòâåðæäàåò ÷òî ñîäåðæèìîå îòâåòà ñîîòâåñòâóåò çàäàííîìó regexp.
    $this->assertRegExp($regexp, $client->getResponse()->getContent());
    // Ïðîâåðÿåò ñòàòóñ êîä ó îòâåòà.
    $this->assertTrue($client->getResponse()->isSuccessful());
    $this->assertTrue($client->getResponse()->isNotFound());
    $this->assertEquals(200, $client->getResponse()->getStatusCode());
    // Óòâåðæäàåò ÷òî ñòàòóñ êîä îòâåòà ÿâëÿåòñÿ ðåäèðåêòîì.
    $this->assertTrue($client->getResponse()->isRedirect('google.com'));

    5.9.4 Òåñòîâûé êëèåíò

    Òåñòîâûé êëèåíò ñèìóëèðóåò HTTP êëèåíòà, òàêîãî êàê áðàóçåð.
    Ïðèìå÷àíèå: Òåñòîâûé êëèåíò îñíîâàí íà êîìïîíåíòàõ BrowserKit è Crawler.

    Ñîçäàíèå çàïðîñîâ
    Êëèåíò çíàåò êàê äåëàòü çàïðîñû ê ïðèëîæåíèþ Symfony2:

    $crawler = $client->request('GET', '/hello/Fabien');
    Ìåòîä request() ïðèíèìàåò â êà÷åñòâå àðãóìåíòîâ HTTP ìåòîä è URL è âîçâðàùàåò
    ýêçåìïëÿð Crawler.
    Âîñïîëüçóéòåñü Crawler ÷òîáû íàéòè DOM ýëåìåíòû â Response. Çàòåì èõ ìîæíî èñïîëüçîâàòü äëÿ êëèêàíèÿ ïî ññûëêàì è îòïðàâêè ôîðì:

    174

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    $link = $crawler->selectLink('Go elsewhere...')->link();
    $crawler = $client->click($link);
    $form = $crawler->selectButton('validate')->form();
    $crawler = $client->submit($form, array('name' => 'Fabien'));
    Ìåòîäû click() è submit() âîçâðàùàþò îáúåêò Crawler. Îíè ÿâëÿþòñÿ ëó÷øèì ñïîñîáîì
    äëÿ îñìîòðà ïðèëîæåíèÿ òàê êàê ñêðûâàþò ìíîãèå äåòàëè. Íàïðèìåð, ìåòîä submit,
    êîãäà âû ïîñûëàåòå ôîðìó îí àâòîìàòè÷åñêè îïðåäåëÿåò HTTP ìåòîä è URL, äà¼ò
    êðàñèâûé API äëÿ çàãðóçêè ôàéëîâ è îáúåäèíÿåò ïðèñëàííûå çíà÷åíèÿ ñî çíà÷åíèÿìè
    ïî óìîë÷àíèþ è ò. ä.
    Ñîâåò: Áîëüøå óçíàòü îá îáúåêòàõ Link è Form ìîæíî â ðàçäåëå Crawler.
    Íî âû òàêæå ìîæåòå ñèìóëèðîâàòü îòïðàâêó ôîðì è ñëîæíûå çàïðîñû ñ ïîìîùüþ
    äîïîëíèòåëüíûõ àðãóìåíòîâ ìåòîäà request():

    // Îòïðàâëÿåò ôîðìó
    $client->request('POST', '/submit', array('name' => 'Fabien'));
    // Îòïðàâëÿåò ôîðìó ñ çàãðóçêîé ôàéëà
    use Symfony\Component\HttpFoundation\File\UploadedFile;
    $photo = new UploadedFile('/path/to/photo.jpg', 'photo.jpg', 'image/jpeg', 123);
    // èëè
    $photo = array('tmp_name' => '/path/to/photo.jpg', 'name' => 'photo.jpg', 'type' => 'image/jpeg', 'size' =>
    $client->request('POST', '/submit', array('name' => 'Fabien'), array('photo' => $photo));

    // Óêàçûâàåò çàãîëîâêè HTTP
    $client->request('DELETE', '/post/12', array(), array(), array('PHP_AUTH_USER' => 'username', 'PHP_AU
    Ñîâåò: Form submissions are greatly simplied by using a crawler object (see below).
    Êîãäà çàïðîñ âîçâðàùàåò îòâåò ñ ïåðåíàïðàâëåíèåì, êëèåíò àâòîìàòè÷åñêè ïðîñëåäóåò
    òóäà. Ýòî ïîâåäåíèå ìîæíî èçìåíèòü ñ ïîìîùüþ ìåòîäà followRedirects():

    $client->followRedirects(false);
    Åñëè æå êëèåíò íå ñëåäóåò ïî ïåðåíàïðàâëåíèÿì, ìîæíî çàñòàâèòü åãî ñ ïîìîùüþ ìåòîäà followRedirect():

    $crawler = $client->followRedirect();
    È ïîñëåäíåå, íî íå ìåíåå âàæíîå, ìîæíî çàñòàâèòü êàæäûé çàïðîñ âûïîëíÿòüñÿ â ñîá5.9. Òåñòèðîâàíèå

    175

    Symfony Documentation, Âûïóñê 2.0
    ñòâåííîì ïðîöåññå PHP ÷òîáû èçáåæàòü ëþáûõ ïîáî÷íûõ ýôôåêòîâ êîãäà íåñêîëüêî
    êëèåíòîâ ðàáîòàþò â îäíîì ñêðèïòå:

    $client->insulate();
    Áðàóçèíã
    Êëèåíò ïîääåðæèâàåò ìíîãèå îïåðàöèè, ñâîéñòâåííûå íàñòîÿùåìó áðàóçåðó:

    $client->back();
    $client->forward();
    $client->reload();
    // Î÷èùàåò âñå êóêè è èñòîðèþ.
    $client->restart();
    Ïîëó÷åíèå âíóòðåííèõ îáúåêòîâ
    Êîãäà êëèåíò èñïîëüçóåòñÿ äëÿ òåñòèðîâàíèÿ ïðèëîæåíèÿ, âîçíèêàåò íåîáõîäèìîñòü
    ïîëó÷èòü äîñòóï ê åãî âíóòðåííèì îáúåêòàì:

    $history = $client->getHistory();
    $cookieJar = $client->getCookieJar();
    Òàêæå ìîæíî ïîëó÷èòü îáúåêòû, îòíîñÿùèåñÿ ê ïîñëåäíåìó çàïðîñó:

    $request = $client->getRequest();
    $response = $client->getResponse();
    $crawler = $client->getCrawler();
    Åñëè çàïðîñû íå áûëè èçîëèðîâàíû, òî ìîæíî ïîëó÷èòü äîñòóï ê Container è Kernel:

    $container = $client->getContainer();
    $kernel = $client->getKernel();
    Ïîëó÷åíèå Container
    Íàñòîÿòåëüíî ðåêîìåíäóåòñÿ èñïîëüçîâàòü ôóíêöèîíàëüíûå òåñòû òîëüêî äëÿ ïðîâåðêè Response. Íî â íåêîòîðûõ ðåäêèõ ñëó÷àÿõ íåîáõîäèìî ïîëó÷èòü äîñòóï ê êàêèì-ëèáî
    âíóòðåííèì îáúåêòàì äëÿ íàïèñàíèÿ óòâåðæäåíèé. Äëÿ ýòîãî ìîæíî èñïîëüçîâàòü êîíòåéíåð âíåäðåíèÿ çàâèñèìîñòè:

    $container = $client->getContainer();

    176

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    Èìåéòå â âèäó ÷òî ýòî íå ñðàáîòàåò åñëè âû èçîëèðîâàëè êëèåíòà èëè èñïîëüçîâàëè
    HTTP ñëîé.
    Ñîâåò: Åñëè íåîáõîäèìàÿ äëÿ ïðîâåðêè èíôîðìàöèÿ äîñòóïíà èç ïðîôèëèðîâùèêà,
    òîãäà èñïîëüçóéòå åãî.

    Ïîëó÷åíèå äàííûõ ïðîôèëèðîâùèêà
    ×òîáû ïðîâåðèòü äàííûå, ñîáðàííûå ïðîôèëèðîâùèêîì, ìîæíî âçÿòü ïðîôèëü òåêóùåãî çàïðîñà:

    $prole = $client->getProle();
    Ïåðåíàïðàâëåíèå
    Ïî óìîë÷àíèþ êëèåíò íå ñëåäóåò ïî HTTP ïåðåíàïðàâëåíèÿì, ÷òîáû ìîæíî áûëî ïîëó÷èòü è ïðîâåðèòü Response äî ïåðåíàïðàâëåíèÿ. Êîãäà æå áóäåò íåîáõîäèìî ïåðåíàïðàâèòü êëèåíòà, âûçîâèòå ìåòîä followRedirect():

    // Äåëàåò ÷òî-íèáóäü, ÷òî âûçûâàåò ïåðåíàïðàâëåíèå (íàïðèìåð, çàïîëíÿåò ôîðìó)
    // ïðîõîäèò ïî ïåðåíàïðàâëåíèþ
    $crawler = $client->followRedirect();
    Åñëè íåîáõîäèìî âñåãäà ïåðåíàïðàâëÿòü êëèåíòà àâòîìàòè÷åñêè, ìîæíî âûçâàòü ìåòîä
    followRedirects():

    $client->followRedirects();
    $crawler = $client->request('GET', '/');
    // ïðîõîäèò ïî âñåì ïåðåíàïðàâëåíèÿì
    // âîçâðàùàåò ðó÷íîå ïåðåíàïðàâëåíèå êëèåíòà
    $client->followRedirects(false);

    5.9.5 Crawler

    Ýêçåìïëÿð Crawler âîçâðàùàåòñÿ êàæäûé ðàç êîãäà âûïîëíÿåòñÿ çàïðîñ ïîñðåäñòâîì
    êëèåíòà. Îí ïîçâîëÿåò ïåðåìåùàòüñÿ ïî HTML äîêóìåíòàì, âûáèðàòü óçëû, èñêàòü
    ññûëêè è ôîðìû.

    5.9. Òåñòèðîâàíèå

    177

    Symfony Documentation, Âûïóñê 2.0
    Ñîçäàíèå ýêçåìïëÿðà Crawler
    Ýêçåìïëÿð Crawler àâòîìàòè÷åñêè ñîçäà¼òñÿ êîãäà âûïîëíÿåòñÿ çàïðîñ ÷åðåç êëèåíòà.
    Òàêæå ëåãêî ìîæíî ñîçäàòü åãî ñâîèìè ðóêàìè:

    use Symfony\Component\DomCrawler\Crawler;
    $crawler = new Crawler($html, $url);
    Êîíñòðóêòîð ïðèíèìàåò äâà àðãóìåíòà: èç êîòîðûõ âòîðîé ýòî URL, èñïîëüçóåìûé
    äëÿ ñîçäàíèÿ àáñîëþòíûõ URL-îâ äëÿ ññûëîê è ôîðì, à ïåðâûé ìîæåò ïðèíèìàòü
    ñëåäóþùèå çíà÷åíèÿ:

    • HTML äîêóìåíò;
    • XML äîêóìåíò;
    • ýêçåìïëÿð DOMDocument;
    • ýêçåìïëÿð DOMNodeList;
    • ýêçåìïëÿð DOMNode;
    • ëèáî ìàññèâ èç ïåðå÷èñëåííûõ ýëåìåíòîâ.
    Ïîñëå ñîçäàíèÿ, ìîæíî äîáàâèòü åù¼ óçëîâ:
    Ìåòîä
    addHTMLDocument()
    addXMLDocument()
    addDOMDocument()
    addDOMNodeList()
    addDOMNode()
    addNodes()
    add()

    Îïèñàíèå
    HTML äîêóìåíò
    XML äîêóìåíò
    ýêçåìïëÿð DOMDocument
    ýêçåìïëÿð DOMNodeList
    ýêçåìïëÿð DOMNode
    ìàññèâ ïåðå÷èñëåííûõ ýëåìåíòîâ
    ïðèíèìàåò ëþáûå ïåðå÷èñëåííûå âûøå ýëåìåíòû

    Ïåðåìåùåíèÿ
    Êàê è jQuery, Crawler èìååò ìåòîäû äëÿ ïåðåìåùåíèÿ ïî DOM äîêóìåíòà HTML/XML:

    178

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    Ìåòîä
    lter('h1')
    lterXpath('h1')
    eq(1)
    rst()
    last()
    siblings()
    nextAll()
    previousAll()
    parents()
    children()
    reduce($lambda)

    Îïèñàíèå
    Óçëû, ñîîòâåñòâóþùèå CSS ñåëåêòîðó
    Óçëû, ñîîòâåñòâóþùèå âûðàæåíèþ XPath
    Óçåë ñ îïðåäåë¼ííûì èíäåêñîì
    Ïåðâûé óçåë
    Ïîñëåäíèé óçåë
    Äî÷åðíèå óçëû
    Âñå ïîñëåäóþùèå äî÷åðíèå óçëû
    Âñå ïðåäøåñòâóþùèå äî÷åðíèå óçëû
    Ðîäèòåëüñêèå óçëû
    Äåòè
    Óçëû, äëÿ êîòîðûõ âûçûâàåìàÿ ôóíêöèÿ íå âîçâðàùàåò false

    Ìîæíî ïîñòåïåííî ñóçèòü âûáîðêó èç óçëîâ, îáúåäèíÿÿ âûçîâû ìåòîäîâ â öåïî÷êè ò.
    ê. ìåòîäû âîçâðàùàþò ýêçåìïëÿð Crawler äëÿ ñîîòâåñòâóþùèõ óçëîâ:

    $crawler
    ->lter('h1')
    ->reduce(function ($node, $i)
    {
    if (!$node->getAttribute('class')) {
    return false;
    }
    })
    ->rst();
    Ñîâåò: Èñïîëüçóéòå ôóíêöèþ count() ÷òîáû ïîëó÷èòü êîëè÷åñòâî óçëîâ, õðàíÿùèõñÿ
    â Crawler: count($crawler)

    Èçâëå÷åíèå èíôîðìàöèè
    Crawler ìîæåò èçâëå÷ü èíôîðìàöèþ èç óçëîâ:

    // Âîçâðàùàåò çíà÷åíèå àòðèáóòà äëÿ ïåðâîãî óçëà
    $crawler->attr('class');
    // Âîçâðàùàåò çíà÷åíèå óçëà äëÿ ïåðâîãî óçëà
    $crawler->text();
    // Èçâëåêàåò ìàññèâ àòðèáóòîâ äëÿ âñåõ óçëîâ (_text âîçâðàùàåò çíà÷åíèå óçëà)
    $crawler->extract(array('_text', 'href'));
    // Âûïîëíÿåò lambda äëÿ êàæäîãî óçëà è âîçâðàùàåò ìàññèâ ðåçóëüòàòîâ
    $data = $crawler->each(function ($node, $i)
    {
    5.9. Òåñòèðîâàíèå

    179

    Symfony Documentation, Âûïóñê 2.0

    });

    return $node->getAttribute('href');

    Ññûëêè
    Ìîæíî âûáèðàòü ññûëêè ñ ïîìîùüþ ìåòîäîâ îáõîäà, íî ñîêðàùåíèå selectLink() ÷àñòî
    áîëåå óäîáíî:

    $crawler->selectLink('Click here');
    Îíî âûáèðàåò ññûëêè, ñîäåðæàùèå óêàçàííûé òåêñò, ëèáî èçîáðàæåíèÿ, ïî êîòîðûì
    ìîæíî êëèêàòü, ñîäåðæàùèå ýòîò òåêñò â àòðèáóòå alt.
    Êëèåíòñêèé ìåòîä click() ïðèíèìàåò ýêçåìïëÿð Link, âîçâðàùàåìûé ìåòîäîì link():

    $link = $crawler->link();
    $client->click($link);
    Ñîâåò: Ìåòîä links() âîçâðàùàåò ìàññèâ îáúåêòîâ Link äëÿ âñåõ óçëîâ.

    Ôîðìû
    Êàê è ññûëêè, ôîðìû âûáèðàéòå ìåòîäîì selectButton():

    $crawler->selectButton('submit');
    Çàìåòüòå ÷òî âûáèðàåòñÿ êíîïêà íà ôîðìå, à íå ñàìà ôîðìà, ò. ê. îíà ìîæåò èìåòü
    íåñêîëüêî êíîïîê; åñëè èñïîëüçóþòñÿ API ïåðåìåùåíèé, òî ïîìíèòå ÷òî íàäî èñêàòü
    êíîïêó.
    Ìåòîä selectButton() ìîæåò âûáðàòü òåãè button è input ñ òèïîì submit; â í¼ì çàëîæåíî
    íåñêîëüêî ýâðèñòèê äëÿ èõ íàõîæäåíèÿ ïî:

    • çíà÷åíèþ àòðèáóòà value;
    • çíà÷åíèþ àòðèáóòà id èëè alt äëÿ èçîáðàæåíèé;
    • çíà÷åíèþ àòðèáóòà id èëè name äëÿ òåãîâ button.
    Êîãäà èìååòñÿ óçåë, îïèñûâàþùèé êíîïêó, âûçîâèòå ìåòîä form() ÷òîáû ïîëó÷èòü ýêçåìïëÿð Form, ôîðìû îá¼ðòûâàþùåé åãî:

    $form = $crawler->form();

    180

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    Ïðè âûçîâå ìåòîäà form() ìîæíî ïåðåäàòü ìàññèâ çíà÷åíèé äëÿ ïîëåé, ïåðåçàïèñûâàþùèõ íà÷àëüíûå çíà÷åíèÿ:

    $form = $crawler->form(array(
    'name'
    => 'Fabien',
    'like_symfony' => true,
    ));
    À åñëè íàäî ñèìóëèðîâàòü îïðåäåë¼ííûé HTTP ìåòîä äëÿ ôîðìû, ïåðåäàéòå åãî âòîðûì àðãóìåíòîì:

    $form = $crawler->form(array(), 'DELETE');
    Êëèåíò ìîæåò îòïðàâëÿòü ýçêåìïëÿðû Form:

    $client->submit($form);
    Çíà÷åíèÿ ïîëåé ìîãóò áûòü ïåðåäàíû âòîðûì àðãóìåíòîì ìåòîäà submit():

    $client->submit($form, array(
    'name'
    => 'Fabien',
    'like_symfony' => true,
    ));
     áîëåå ñëîæíûõ ñëó÷àÿõ, èñïîëüçóéòå ýêçåìïëÿð Form êàê ìàññèâ ÷òîáû çàäàòü çíà÷åíèÿ êàæäîãî ïîëÿ èíäèâèäóàëüíî:

    // Èçìåíÿåò çíà÷åíèå ïîëÿ
    $form['name'] = 'Fabien';
    Çäåñü òîæå åñòü êðàñèâûé API äëÿ óïðàâëåíèÿ çíà÷åíèÿìè ïîëåé â çàâèñèìîñòè îò èõ
    òèïîâ:

    // Âûáèðàåò option èëè radio
    $form['country']->select('France');
    // Ñòàâèò ãàëî÷êó â checkbox
    $form['like_symfony']->tick();
    // Çàãðóæàåò ôàéë
    $form['photo']->upload('/path/to/lucas.jpg');
    Ñîâåò:
    Ìîæíî ïîëó÷èòü çíà÷åíèÿ, êîòîðûå áóäóò îòïðàâëåíû, âûçâàâ ìåòîä
    getValues(). Çàãðóæàåìûå ôàéëû äîñòóïíû â îòäåëüíîì ìàññèâå, âîçâðàùàåìîì ÷åðåç
    getFiles(). getPhpValues() è getPhpFiles() òîæå âîçâðàùàþò çíà÷åíèÿ äëÿ îòïðàâêè, íî
    â ôîðìàòå PHP (îí ïðåîáðàçóåò êëþ÷è ñ êâàäðàòíûìè ñêîáêàìè â PHP ìàññèâû).

    5.9. Òåñòèðîâàíèå

    181

    Symfony Documentation, Âûïóñê 2.0
    5.9.6 Òåñòîâàÿ êîíôèãóðàöèÿ

    PHPUnit êîíôèãóðàöèÿ
    Êàæäîå ïðèëîæåíèå èìååò ñâîþ êîíôèãóðàöèþ PHPUnit, õðàíÿùóþñÿ â ôàéëå
    phpunit.xml.dist. Ìîæåòå îòðåäàêòèðîâàòü åãî ÷òîáû èçìåíèòü íà÷àëüíûå óñòàíîâêè
    èëè ñîçäàòü ôàéë phpunit.xml, ÷òîáû ïîäñòðîèòü êîíôèãóðàöèþ ïîä ëîêàëüíóþ ìàøèíó.
    Ñîâåò: Õðàíèòå ôàéë phpunit.xml.dist â ñâî¼ì ðåïîçèòîðèè êîäà è èãíîðèðóéòå ôàéë
    phpunit.xml.
    Òîëüêî òåñòû, õðàíÿùèåñÿ â ñòàíäàðòíûõ áàíäëàõ, çàïóñêàþòñÿ ÷åðåç phpunit ïî
    óìîë÷àíèþ (ñòàíäàðòíûìè áóäóò òåñòû èç ïðîñòðàíñòâà èì¼í Vendor\*Bundle\Tests).
    Õîòÿ ëåãêî ìîæíî äîáàâèòü åù¼ ïðîñòðàíñòâà èì¼í. Íàïðèìåð, ñëåäóþùàÿ êîíôèãóðàöèÿ äîáàâëÿåò òåñòû èç óñòàíîâëåííûõ third-party áàíäëîâ:

    <!-- hello/phpunit.xml.dist -->
    <testsuites>
    <testsuite name="Project Test Suite">
    <directory>../src/*/*Bundle/Tests</directory>
    <directory>../src/Acme/Bundle/*Bundle/Tests</directory>
    </testsuite>
    </testsuites>
    ×òîáû âêëþ÷èòü äðóãèå ïðîñòðàíñòâà èì¼í â ïîêðûòèå êîäà, ïîäïðàâüòå ðàçäåë
    <lter>:

    <lter>
    <whitelist>
    <directory>../src</directory>
    <exclude>
    <directory>../src/*/*Bundle/Resources</directory>
    <directory>../src/*/*Bundle/Tests</directory>
    <directory>../src/Acme/Bundle/*Bundle/Resources</directory>
    <directory>../src/Acme/Bundle/*Bundle/Tests</directory>
    </exclude>
    </whitelist>
    </lter>
    Êîíôèãóðàöèÿ êëèåíòà
    Êëèåíò, èñïîëüçóåìûé â ôóíêöèîíàëüíûéõ òåñòàõ, ñîçäà¼ò Kernel, êîòîðûé çàïóñêàåòñÿ
    â ñïåöèàëüíîé ñðåäå test, ò. î. ìîæíî íàñòðîèòü åãî òàê, êàê ýòî áóäåò íåîáõîäèìî:
    182

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    • YAML

    # app/cong/cong_test.yml
    imports:
    - { resource: cong_dev.yml }
    framework:
    error_handler: false
    test: ~
    web_proler:
    toolbar: false
    intercept_redirects: false
    monolog:
    handlers:
    main:
    type: stream
    path: %kernel.logs_dir%/%kernel.environment%.log
    level: debug
    • XML

    <!-- app/cong/cong_test.xml -->
    <container>
    <imports>
    <import resource="cong_dev.xml" />
    </imports>
    <webproler:cong
    toolbar="false"
    intercept-redirects="false"
    />
    <framework:cong error_handler="false">
    <framework:test />
    </framework:cong>
    <monolog:cong>
    <monolog:main
    type="stream"
    path="%kernel.logs_dir%/%kernel.environment%.log"
    level="debug"
    />
    </monolog:cong>
    </container>
    • PHP
    5.9. Òåñòèðîâàíèå

    183

    Symfony Documentation, Âûïóñê 2.0

    // app/cong/cong_test.php
    $loader->import('cong_dev.php');
    $container->loadFromExtension('framework', array(
    'error_handler' => false,
    'test'
    => true,
    ));
    $container->loadFromExtension('web_proler', array(
    'toolbar' => false,
    'intercept-redirects' => false,
    ));
    $container->loadFromExtension('monolog', array(
    'handlers' => array(
    'main' => array('type' => 'stream',
    'path' => '%kernel.logs_dir%/%kernel.environment%.log'
    'level' => 'debug')
    )));
    Òàêæå ìîæíî èçìåíèòü ñðåäó (test) è ðåæèì îòëàäêè (true), çàäàííûå ïî óìîë÷àíèþ,
    ïåðåäàâ èõ ìåòîäó createClient() â âèäå îïöèé:

    $client = static::createClient(array(
    'environment' => 'my_test_env',
    'debug'
    => false,
    ));
    Åñëè ïðèëîæåíèå çàâèñèò îò êàêèõ-ëèáî HTTP çàãîëîâêîâ, ïåðåäàéòå èõ âòîðûì àðãóìåíòîì createClient():

    $client = static::createClient(array(), array(
    'HTTP_HOST'
    => 'en.example.com',
    'HTTP_USER_AGENT' => 'MySuperBrowser/1.0',
    ));
    Òàêæå ìîæíî èçìåíÿòü HTTP çàãîëîâêè äëÿ êàæäîãî çàïðîñà:

    $client->request('GET', '/', array(), array(
    'HTTP_HOST'
    => 'en.example.com',
    'HTTP_USER_AGENT' => 'MySuperBrowser/1.0',
    ));
    Ñîâåò: ×òîáû óêàçàòü ñâîåãî ñîáñòâåííîãî êëèåíòà, èçìåíèòå ïàðàìåòð test.client.class
    èëè óñòàíîâèòå ñëóæáó test.client.

    184

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    5.9.7 Óçíàéòå áîëüøå èç Ðåöåïòîâ

    • How to simulate HTTP Authentication in a Functional Test
    • How to test the Interaction of several Clients
    • How to use the Proler in a Functional Test

    5.10 Âàëèäàöèÿ

    Âàëèäàöèÿ î÷åíü ÷àñòàÿ çàäà÷à â âåá ïðèëîæåíèÿõ. Äàííûå ââåäåíûå â ôîðìó äîëæíû
    áûòü âàëèäèðîâàíû. Äàííûå òàêæå äîëæíû ïðîéòè âàëèäàöèþ ïåðåä çàïèñüþ â áàçó
    äàííûõ èëè ïåðåäà÷è â web-ñëóæáó.
    Symfony2 ïîñòàâëÿåòñÿ ñ êîìïîíåíòîì Validator, êîòîðûé âûïîëíÿåò ýòó çàäà÷ó ëåãêî è ïðîçðà÷íî. Ýòîò êîìïîíåíò îñíîâàí íà ñïåöèôèêàöèè JSR303 Bean Validation
    specication. ×òî? Ñïåöèôèêàöèÿ Java â PHP? Âû íå îñëûøàëèñü, íî ýòî íå òàê ïëîõî,
    êàê êàæåòñÿ. Äàâàéòå ïîñìîòðèì, êàê ýòî ìîæíî èñïîëüçîâàòü â PHP.
    5.10.1 Îñíîâû âàëèäàöèè

    Ëó÷øèé ñïîñîá ïîíÿòü âàëèäàöèþ - ýòî óâèäåòü åå â äåéñòâèè. Äëÿ íà÷àëà ïðåäïîëîæèì, ÷òî âû ñîçäàëè ñòàðûé-äîáðûé PHP îáúåêò, êîòîðûé íåîáõîäèìî èñïîëüçîâàòü
    ãäå-íèáóäü â âàøåì ïðèëîæåíèè:

    // src/Acme/BlogBundle/Entity/Author.php
    namespace Acme\BlogBundle\Entity;
    class Author
    {
    public $name;
    }
    Ïîêà ýòî âñåãî ëèøü îáû÷íûé êëàññ, ñîçäàííûé ñ êàêîé-òî öåëüþ. Öåëü âàëèäàöèè â
    òîì, ÷òîáû ñîîáùèòü âàì, ÿâëÿþòñÿ ëè äàííûå îáúåêòà âàëèäíûìè èëè æå íåò. ×òîáû
    ýòî çàðàáîòàëî, âû äîëæíû ñêîíôèãóðèðîâàòü ñïèñîê ïðàâèë (íàçûâàåìûõ îãðàíè÷åíèÿìè (constraints)) êîòîðûì äîëæåí ñëåäîâàòü îáúåêò, ÷òî áû áûòü âàëèäíûì. Ýòè
    ïðàâèëà ìîãóò áûòü îïðåäåëåíû ñ ïîìîùüþ ðàçëè÷íûõ ôîðìàòîâ (YML, XML, àííîòàöèè èëè PHP).
    ×òîáû ãàðàíòèðîâàòü, ÷òî ñâîéñòâî $name íå ïóñòîå, äîáàâüòå ñëåäóþùåå:

    • YAML

    5.10. Âàëèäàöèÿ

    185

    Symfony Documentation, Âûïóñê 2.0

    # src/Acme/BlogBundle/Resources/cong/validation.yml
    Acme\BlogBundle\Entity\Author:
    properties:
    name:
    - NotBlank: ~
    • Annotations

    // src/Acme/BlogBundle/Entity/Author.php
    use Symfony\Component\Validator\Constraints as Assert;
    class Author
    {
    /**
    * @Assert\NotBlank()
    */
    public $name;
    }
    • XML

    <!-- src/Acme/BlogBundle/Resources/cong/validation.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping http://symfony.com/schema/
    <class name="Acme\BlogBundle\Entity\Author">
    <property name="name">
    <constraint name="NotBlank" />
    </property>
    </class>
    </constraint-mapping>
    • PHP

    // src/Acme/BlogBundle/Entity/Author.php
    use Symfony\Component\Validator\Mapping\ClassMetadata;
    use Symfony\Component\Validator\Constraints\NotBlank;
    class Author
    {
    public $name;
    public static function loadValidatorMetadata(ClassMetadata $metadata)
    {
    $metadata->addPropertyConstraint('name', new NotBlank());
    186

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    }

    }

    Ñîâåò: Protected è private ñâîéñòâà òàêæå ìîãóò áûòü âàëèäèðîâàíû, êàê ãåòòåðû (ñì.
    Öåëè îãðàíè÷åíèé).

    Èñïîëüçîâàíèå validator Service
    ×òîáû íà ñàìîì äåëå ïðîâåðèòü îáúåêò Author èñïîëüçóåòñÿ ìåòîä validate â ñåðâèñå validator (êëàññ Symfony\Component\Validator\Validator). Ðàáîòà validator iïðîñòà:
    ïðî÷åñòü îãðàíè÷åíèÿ (ò.å. ïðàâèëà) êëàññà è ïðîâåðèòü óäîâëåòâîðåþò ëè äàííûå ýòèì
    ïðàâèëàì èëè íåò. Åñëè âàëàäàöèÿ íå ïðîéäåíà, âîçâðàùàåòñÿ ìàññèâ îøèáîê. Ðàññìîòðèì ýòîò ïðîñòîé ïðèìåð êîíòðîëëåðà:

    use Symfony\Component\HttpFoundation\Response;
    use Acme\BlogBundle\Entity\Author;
    // ...
    public function indexAction()
    {
    $author = new Author();
    // ... do something to the $author object
    $validator = $this->get('validator');
    $errors = $validator->validate($author);

    }

    if (count($errors) > 0) {
    return new Response(print_r($errors, true));
    } else {
    return new Response('The author is valid! Yes!');
    }

    Åñëè ñâîéñòâî $name ïóñòî, âû óâèäèòå ñëåäóþùåå ñîîáùåíèå îá îøèáêå:

    Acme\BlogBundle\Author.name:
    This value should not be blank
    Åñëè â ýòî ñâîéñòâî name âñòàâèòü çíà÷åíèå, òî âåðíåòñÿ ñîîáùåíèå îá óñïåõå.
    Ñîâåò: Most of the time, you won't interact directly with the validator service or need to
    worry about printing out the errors. Most of the time, you'll use validation indirectly when
    handling submitted form data. For more information, see the Âàëèäàöèÿ è ôîðìû.

    5.10. Âàëèäàöèÿ

    187

    Symfony Documentation, Âûïóñê 2.0
    You could also pass the collection of errors into a template.

    if (count($errors) > 0) {
    return $this->render('AcmeBlogBundle:Author:validate.html.twig', array(
    'errors' => $errors,
    ));
    } else {
    // ...
    }
    Â øàáëîíå âû ìîæåòå âûâåñòè îøèáêè òàê, êàê õîòèòå:

    • Twig

    {# src/Acme/BlogBundle/Resources/views/Author/validate.html.twig #}
    <h3>The author has the following errors</h3>
    <ul>
    {% for error in errors %}
    <li>{{ error.message }}</li>
    {% endfor %}
    </ul>
    • PHP

    <!-- src/Acme/BlogBundle/Resources/views/Author/validate.html.php -->
    <h3>The author has the following errors</h3>
    <ul>
    <?php foreach ($errors as $error): ?>
    <li><?php echo $error->getMessage() ?></li>
    <?php endforeach; ?>
    </ul>
    Ïðèìå÷àíèå:
    Êàæäàÿ
    îøèáêà
    âàëèäàöèè
    øåíèå
    îãðàíè÷åíèÿ
    (constraint
    violation)),
    Symfony\Component\Validator\ConstraintViolation.

    (íàçûâàþùàÿñÿ
    íàðóïðåäñòàâëåíà
    îáúåêòîì

    Âàëèäàöèÿ è ôîðìû
    Ñåðâèñ validator ìîæåò áûòü èñïîëüçîâàí â ëþáîå âðåìÿ äëÿ ïðîâåðêè ëþáîãî îáúåêòà.
    Îäíàêî â äåéñòâèòåëüíîñòè, âû îáû÷íî áóäåòå ðàáîòàòü ñ validator ïðè ðàáîòå ñ ôîðìàìè. Áèáèëèîòåêà Symfony äëÿ ðàáîòû ñ ôîðìàìè validator èñïîëüçóåò ñåðâèñ âàëèäàöèè
    âíóòðåííå äëÿ ïðîâåðêè îáúåêòà ïîñëå òîãî, êàê äàííûå áûëè îòïðàâëåíû è ñâÿçàíû.
    Íàðóøåíèÿ îãðàíè÷åíèé îáúåêòà ïðåîáðàçóþòñÿ â îáúåêòû FieldError, êîòîðûå çàòåì
    188

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    ìîãóò îòîáðàæàòüñÿ ñ âàøåé ôîðìîé. The typical form submission workow looks like the
    following from inside a controller:

    use Acme\BlogBundle\Entity\Author;
    use Acme\BlogBundle\Form\AuthorType;
    use Symfony\Component\HttpFoundation\Request;
    // ...
    public function updateAction(Request $request)
    {
    $author = new Acme\BlogBundle\Entity\Author();
    $form = $this->createForm(new AuthorType(), $author);
    if ($request->getMethod() == 'POST') {
    $form->bindRequest($request);
    if ($form->isValid()) {
    // the validation passed, do something with the $author object

    }

    }

    }

    $this->redirect($this->generateUrl('...'));

    return $this->render('BlogBundle:Author:form.html.twig', array(
    'form' => $form->createView(),
    ));

    Ïðèìå÷àíèå: This example uses an AuthorType form class, which is not shown here.
    Äëÿ áîëüøåé èíôîðìàöèè, ñìîòðèòå ãëàâó Forms.
    5.10.2 Êîíôèãóðàöèÿ

    The Symfony2 validator is enabled by default, but you must explicitly enable annotations if
    you're using the annotation method to specify your constraints:

    • YAML

    # app/cong/cong.yml
    framework:
    validation: { enable_annotations: true }
    • XML

    5.10. Âàëèäàöèÿ

    189

    Symfony Documentation, Âûïóñê 2.0

    <!-- app/cong/cong.xml -->
    <framework:cong>
    <framework:validation enable_annotations="true" />
    </framework:cong>
    • PHP

    // app/cong/cong.php
    $container->loadFromExtension('framework', array('validation' => array(
    'enable_annotations' => true,
    )));

    5.10.3 Îãðàíè÷åíèÿ

    validator ðàçðàáîòàí äëÿ ïðîâåðêè îáúåêòîâ íà ñîîòâåòñòâèÿ îãðàíè÷åíèÿì (ò.å. ïðàâèëàì). Äëÿ âàëèäàöèè îáúåêòà, ïðîñòî ïðåäñòàâüòå îäíî èëè áîëåå îãðàíè÷åíèé â ñâîåì
    êëàññå, à çàòåì ïåðåäàéòå èõ ñåðâèñó validator.
    Îãðàíè÷åíèå ýòî ïðîñòî PHP îáúåêò, êîòîðîå ïðåäñòàâëÿåòñÿ â âèäå æåñòêîãî çàÿâëåíèÿ.  ðåàëüíîé æèçíè, îãðàíè÷åíèå ìîæåò áûòü ïðåäñòàâëåíî â âèäå: Ïèðîã íå
    äîëæåí áûòü ïîäãîðåëûì.  Symfony2 îãðàíè÷åíèÿ ïîõîæè: îíè ÿâëÿþòñÿ óòâåðæäåíèÿìè, ÷òî óñëîâèå èñòèííî. Ïîëó÷èâ çíà÷åíèå, îãðàíè÷åíèå ñîîáùàåò ñîîáùèò âàì,
    ïðèäåðæèâàåòñÿ ëè çíà÷åíèå ïðàâèëàì îãðàíè÷åíèé.
    Ïîääåðæèâàåìûå îãðàíè÷åíèÿ
    Ïàêåòû Symfony2 ñîäåðæàò áîëüøîå ÷èñëî íàèáîëåå ÷àñòî íåîáõîäèìûõ îãðàíè÷åíèé.
    Ïîëíûé ñïèñîê îãðàíè÷åíèé ñ ðàçëè÷íûìè äåòàëÿìè äîñòóïåí â ñïðàâî÷íîì ðàçäåëå
    îãðàíè÷åíèé.
    Êîíôèãóðàöèÿ îãðàíè÷åíèé
    Some constraints, like NotBlank, are simple whereas others, like the Choice constraint, have
    several conguration options available. Suppose that the Author class has another property,
    gender that can be set to either male or female:

    • YAML

    # src/Acme/BlogBundle/Resources/cong/validation.yml
    Acme\BlogBundle\Entity\Author:
    properties:
    gender:
    - Choice: { choices: [male, female], message: Choose a valid gender. }

    190

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    • Annotations

    // src/Acme/BlogBundle/Entity/Author.php
    use Symfony\Component\Validator\Constraints as Assert;
    class Author
    {
    /**
    * @Assert\Choice(
    * choices = { "male", "female" },
    * message = "Choose a valid gender."
    *)
    */
    public $gender;
    }
    • XML

    <!-- src/Acme/BlogBundle/Resources/cong/validation.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping http://symfony.com/schema/
    <class name="Acme\BlogBundle\Entity\Author">
    <property name="gender">
    <constraint name="Choice">
    <option name="choices">
    <value>male</value>
    <value>female</value>
    </option>
    <option name="message">Choose a valid gender.</option>
    </constraint>
    </property>
    </class>
    </constraint-mapping>
    • PHP

    // src/Acme/BlogBundle/Entity/Author.php
    use Symfony\Component\Validator\Mapping\ClassMetadata;
    use Symfony\Component\Validator\Constraints\NotBlank;
    class Author
    {
    public $gender;
    public static function loadValidatorMetadata(ClassMetadata $metadata)
    5.10. Âàëèäàöèÿ

    191

    Symfony Documentation, Âûïóñê 2.0

    {

    }

    }

    $metadata->addPropertyConstraint('gender', new Choice(array(
    'choices' => array('male', 'female'),
    'message' => 'Choose a valid gender.',
    )));

    The options of a constraint can always be passed in as an array. Some constraints, however,
    also allow you to pass the value of one, default, option in place of the array. In the case of
    the Choice constraint, the choices options can be specied in this way.

    • YAML

    # src/Acme/BlogBundle/Resources/cong/validation.yml
    Acme\BlogBundle\Entity\Author:
    properties:
    gender:
    - Choice: [male, female]
    • Annotations

    // src/Acme/BlogBundle/Entity/Author.php
    use Symfony\Component\Validator\Constraints as Assert;
    class Author
    {
    /**
    * @Assert\Choice({"male", "female"})
    */
    protected $gender;
    }
    • XML

    <!-- src/Acme/BlogBundle/Resources/cong/validation.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping http://symfony.com/schema/
    <class name="Acme\BlogBundle\Entity\Author">
    <property name="gender">
    <constraint name="Choice">
    <value>male</value>
    <value>female</value>
    </constraint>
    </property>
    192

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    </class>
    </constraint-mapping>
    • PHP

    // src/Acme/BlogBundle/Entity/Author.php
    use Symfony\Component\Validator\Mapping\ClassMetadata;
    use Symfony\Component\Validator\Constraints\Choice;
    class Author
    {
    protected $gender;

    }

    public static function loadValidatorMetadata(ClassMetadata $metadata)
    {
    $metadata->addPropertyConstraint('gender', new Choice(array('male', 'female')));
    }

    This is purely meant to make the conguration of the most common option of a constraint
    shorter and quicker.
    If you're ever unsure of how to specify an option, either check the API documentation for the
    constraint or play it safe by always passing in an array of options (the rst method shown
    above).
    5.10.4 Öåëè îãðàíè÷åíèé

    Îãðàíè÷åíèÿ ìîãóò áûòü ïðèìåíåíû ê ñâîéñòâó êëàññà (íàïðèìåð name) èëè ê îòêðûòîìó ãåòòåð-ìåòîäó (íàïðèìåð getFullName). The rst is the most common and easy to
    use, but the second allows you to specify more complex validation rules.
    Ñâîéñòâà
    Ïðîâåðêà ñâîéñòâ êëàññà ÿâëÿåòñÿ ñàìîé îñíîâíîé òåõíèêîé âàëèäàöèè. Symfony2 ïîçâîëÿåò âàì ïðîâåðÿòü private, protected èëè public ñâîéñòâà. Ñëåäóþùèé ëèñòèíã ïîêàçûâàåò âàì, êàê êîíôèãóðèðîâàòü ñâîéñòâà $rstName êëàññà Author ÷òîáû èìåòü ïî
    êðàéíåé-ìåðå 3 ñèìâîëà.

    • YAML

    # src/Acme/BlogBundle/Resources/cong/validation.yml
    Acme\BlogBundle\Entity\Author:
    properties:
    rstName:
    5.10. Âàëèäàöèÿ

    193

    Symfony Documentation, Âûïóñê 2.0

    - NotBlank: ~
    - MinLength: 3
    • Annotations

    // Acme/BlogBundle/Entity/Author.php
    use Symfony\Component\Validator\Constraints as Assert;
    class Author
    {
    /**
    * @Assert\NotBlank()
    * @Assert\MinLength(3)
    */
    private $rstName;
    }
    • XML

    <!-- src/Acme/BlogBundle/Resources/cong/validation.xml -->
    <class name="Acme\BlogBundle\Entity\Author">
    <property name="rstName">
    <constraint name="NotBlank" />
    <constraint name="MinLength">3</constraint>
    </property>
    </class>
    • PHP

    // src/Acme/BlogBundle/Entity/Author.php
    use Symfony\Component\Validator\Mapping\ClassMetadata;
    use Symfony\Component\Validator\Constraints\NotBlank;
    use Symfony\Component\Validator\Constraints\MinLength;
    class Author
    {
    private $rstName;

    }

    194

    public static function loadValidatorMetadata(ClassMetadata $metadata)
    {
    $metadata->addPropertyConstraint('rstName', new NotBlank());
    $metadata->addPropertyConstraint('rstName', new MinLength(3));
    }

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    Ãåòòåðû
    Îãðàíè÷åíèå òàêæå ìîæåò ïðèìåíåíî äëÿ âîçâðàùåíèÿ çíà÷åíèÿ ìåòîäà. Symfony2 ïîçâîëÿåò âàì äîáàâëÿòü îãðàíè÷åíèå public ìåòîäàì, êîòðûå íà÷èíàþòñÿ ñ get èëè is.
    Â ýòîì ðóêîâîäñòâå, îáà ýòèõ ìåòîäîâ íàçûâàþòñÿ ãåòòåðàìè.
    Ïðåèìóùåñòâî ýòîé òåõíèêè â òîì, ÷òî îíà ïîçîâîëÿåò âàì ïðîâåðèòü âàø îáúåêò äèíàìè÷åñêè. For example, suppose you want to make sure that a password eld doesn't
    match the rst name of the user (for security reasons). You can do this by creating an
    isPasswordLegal method, and then asserting that this method must return true:

    • YAML

    # src/Acme/BlogBundle/Resources/cong/validation.yml
    Acme\BlogBundle\Entity\Author:
    getters:
    passwordLegal:
    - "True": { message: "The password cannot match your rst name" }
    • Annotations

    // src/Acme/BlogBundle/Entity/Author.php
    use Symfony\Component\Validator\Constraints as Assert;
    class Author
    {
    /**
    * @Assert\True(message = "The password cannot match your rst name")
    */
    public function isPasswordLegal()
    {
    // return true or false
    }
    }
    • XML

    <!-- src/Acme/BlogBundle/Resources/cong/validation.xml -->
    <class name="Acme\BlogBundle\Entity\Author">
    <getter property="passwordLegal">
    <constraint name="True">
    <option name="message">The password cannot match your rst name</option>
    </constraint>
    </getter>
    </class>
    • PHP

    5.10. Âàëèäàöèÿ

    195

    Symfony Documentation, Âûïóñê 2.0

    // src/Acme/BlogBundle/Entity/Author.php
    use Symfony\Component\Validator\Mapping\ClassMetadata;
    use Symfony\Component\Validator\Constraints\True;
    class Author
    {
    public static function loadValidatorMetadata(ClassMetadata $metadata)
    {
    $metadata->addGetterConstraint('passwordLegal', new True(array(
    'message' => 'The password cannot match your rst name',
    )));
    }
    }
    Now, create the isPasswordLegal() method, and include the logic you need:

    public function isPasswordLegal()
    {
    return ($this->rstName != $this->password);
    }
    Ïðèìå÷àíèå: Âíèìàòåëüíûå èç âàñ çàìåòÿò, ÷òî ïðåôèêñ ãåòòåðà (get èëè is) îïóùåí
    â îòîáðàæåíèè (mapping). Ýòî ïîçâîëÿåò âàì ïåðåìåùàòü îãðàíè÷åíèå ñâîéñòâà ñ òåì
    æå èìåíåì ïîçæå (èëè íàîáîðîò) áåç èçìåíåíèÿ ëîãèêè âàëèäàöèè.

    Classes
    Some constraints apply to the entire class being validated. For example, the Callback
    constraint is a generic constraint that's applied to the class itself. When that class is validated,
    methods specied by that constraint are simply executed so that each can provide more
    custom validation.
    5.10.5 Validation Groups

    So far, you've been able to add constraints to a class and ask whether or not that class
    passes all of the dened constraints. In some cases, however, you'll need to validate an
    object against only some of the constraints on that class. To do this, you can organize each
    constraint into one or more validation groups, and then apply validation against just one
    group of constraints.
    For example, suppose you have a User class, which is used both when a user registers and
    when a user updates his/her contact information later:

    196

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    • YAML

    # src/Acme/BlogBundle/Resources/cong/validation.yml
    Acme\BlogBundle\Entity\User:
    properties:
    email:
    - Email: { groups: [registration] }
    password:
    - NotBlank: { groups: [registration] }
    - MinLength: { limit: 7, groups: [registration] }
    city:
    - MinLength: 2
    • Annotations

    // src/Acme/BlogBundle/Entity/User.php
    namespace Acme\BlogBundle\Entity;
    use Symfony\Component\Security\Core\User\UserInterface
    use Symfony\Component\Validator\Constraints as Assert;
    class User implements UserInterface
    {
    /**
    * @Assert\Email(groups={"registration"})
    */
    private $email;
    /**
    * @Assert\NotBlank(groups={"registration"})
    * @Assert\MinLength(limit=7, groups={"registration"})
    */
    private $password;

    }

    /**
    * @Assert\MinLength(2)
    */
    private $city;

    • XML

    <!-- src/Acme/BlogBundle/Resources/cong/validation.xml -->
    <class name="Acme\BlogBundle\Entity\User">
    <property name="email">
    <constraint name="Email">
    <option name="groups">
    <value>registration</value>
    5.10. Âàëèäàöèÿ

    197

    Symfony Documentation, Âûïóñê 2.0

    </option>
    </constraint>
    </property>
    <property name="password">
    <constraint name="NotBlank">
    <option name="groups">
    <value>registration</value>
    </option>
    </constraint>
    <constraint name="MinLength">
    <option name="limit">7</option>
    <option name="groups">
    <value>registration</value>
    </option>
    </constraint>
    </property>
    <property name="city">
    <constraint name="MinLength">7</constraint>
    </property>
    </class>
    • PHP

    // src/Acme/BlogBundle/Entity/User.php
    namespace Acme\BlogBundle\Entity;
    use Symfony\Component\Validator\Mapping\ClassMetadata;
    use Symfony\Component\Validator\Constraints\Email;
    use Symfony\Component\Validator\Constraints\NotBlank;
    use Symfony\Component\Validator\Constraints\MinLength;
    class User
    {
    public static function loadValidatorMetadata(ClassMetadata $metadata)
    {
    $metadata->addPropertyConstraint('email', new Email(array(
    'groups' => array('registration')
    )));
    $metadata->addPropertyConstraint('password', new NotBlank(array(
    'groups' => array('registration')
    )));
    $metadata->addPropertyConstraint('password', new MinLength(array(
    'limit' => 7,
    'groups' => array('registration')
    )));

    198

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    }

    }

    $metadata->addPropertyConstraint('city', new MinLength(3));

    With this conguration, there are two validation groups:

    • Default - contains the constraints not assigned to any other group;
    • registration - contains the constraints on the email and password elds only.
    To tell the validator to use a specic group, pass one or more group names as the second
    argument to the validate() method:

    $errors = $validator->validate($author, array('registration'));
    Of course, you'll usually work with validation indirectly through the form library. For
    information on how to use validation groups inside forms, see Âàëèäàöèîííûå ãðóïïû.
    5.10.6 Çàêëþ÷èòåëüíûå ìûñëè

     Symfony2 validator ìîùíûé èíñòðóìåíò, êîòîðûé ìîæåò áûòü èñïîëüçîâàí äëÿ ãàðàíòèðîâàíèÿ, ÷òî äàííûå ëþáîãî îáúåêòà âàëèäíû. Ìîùü âàëèäàöèè çàêëþ÷àåòñÿ â
    îãðàíè÷åíèÿõ, ïðåäñòàâëÿþùèå ñîáîé ïðàâèëà, êîòîðûå âû ìîæåòå ïðèìåíèòü ê ñâîéñòâàì èëè ãåòòåð-ìåòîäàì âàøåãî îáúåêòà. È ïîêà âû áóäåòå èñïîëüçîâàòü ôðåéìâîðê
    âàëèäàöèè âìåñòå ñ ôîðìàìè, ïîìíèòå, ÷òî îí ìîæåò áûòü èñïîëüçîâàí â ëþáîì ìåñòå
    äëÿ ïðîâåðêè ëþáîãî îáúåêòà.
    5.10.7 Óçíàéòå áîëüøå èç êíèãè ðåöåïòîâ

    • How to create a Custom Validation Constraint

    5.11 Ôîðìû

    Ðàáîòà ñ HTML ôîðìàìè ÿâëÿåòñÿ îäíîé èç ñàìûõ îáû÷íûõ è, â òî æå âðåìÿ, ñëîæíûõ
    çàäà÷ äëÿ ïðîãðàììèñòà. Symfony2 âêëþ÷àåò â ñåáÿ êîìïîíåíò Form, îáëåã÷àþùèé ðàáîòó ñ ôîðìàìè.  ýòîé ãëàâå Âû ñîçäàäèòå ñëîæíóþ ôîðìó, ïîïóòíî èçó÷àÿ íàèáîëåå
    âàæíûå îñîáåííîñòè áèáëèîòåêè ôîðì.
    Ïðèìå÷àíèå: Êîìïîíåíò ôîðì Symfony  ýòî îáîñîáëåííàÿ áèáëèîòåêà, êîòîðóþ ìîæíî
    èñïîëüçîâàòü âíå Symfony2 ïðîåêòîâ. ×òîáû ïîëó÷èòü áîëåå ïîäðîáíóþ èíôîðìàöèþ,
    ñìîòðèòå Symfony2 Form Component íà Github.

    5.11. Ôîðìû

    199

    Symfony Documentation, Âûïóñê 2.0
    5.11.1 Ñîçäàíèå ïðîñòîé ôîðìû

    Ïðåäïîëîæèì, Âû ñîçäà¼òå ïðîñòîå ïðèëîæåíèå, êîòîðîå ïîíàäîáèòñÿ äëÿ îòîáðàæåíèÿ
    çàäàíèé. Ïîñêîëüêó Âàøèì ïîëüçîâàòåëÿì ïîòðåáóåòñÿ âîçìîæíîñòü ðåäàêòèðîâàòü
    è ñîçäàâàòü çàäàíèÿ, Âàì ïîòðåáóåòñÿ ñîçäàòü ôîðìó. Îäíàêî, ïåðåä òåì, êàê íà÷àòü,
    îáðàòèòå âíèìàíèå íà êëàññ Task, ïðåäîñòàâëÿþùèé è õðàíÿùèé äàííûå äëÿ îäíîãî
    çàäàíèÿ:

    // src/Acme/TaskBundle/Entity/Task.php
    namespace Acme\TaskBundle\Entity;
    class Task
    {
    protected $task;
    protected $dueDate;
    public function getTask()
    {
    return $this->task;
    }
    public function setTask($task)
    {
    $this->task = $task;
    }

    }

    public function getDueDate()
    {
    return $this->dueDate;
    }
    public function setDueDate(\DateTime $dueDate = null)
    {
    $this->dueDate = $dueDate;
    }

    Ïðèìå÷àíèå: Åñëè Âû êîäèðóåòå, ïðèäåðæèâàÿñü äàííîãî ïðèìåðà, ñîçäàéòå, äëÿ íà÷àëà, AcmeTaskBundle ïóò¼ì âûïîëíåíèÿ ñëåäóþùåé êîìàíäû (ïðèíèìàÿ âñå ñòàíäàðòíûå îïöèè):

    php app/console generate:bundle --namespace=Acme/TaskBundle
    Ýòîò êëàññ ÿâëÿåòñÿ ñòàðûì-äîáðûì PHP îáúåêòîì, ïîñêîëüêó îí íå èìååò îòíîøåíèÿ ê Symfony èëè äðóãîé áèáëèîòåêå. Ýòî äîñòàòî÷íî ïðîñòîé PHP îáúåêò, êîòîðûé
    íàïðÿìóþ ðåøàåò çàäà÷è â Âàøåì ïðèëîæåíèè (ò.å. íåîáõîäèìîñòü ïðåäñòàâëÿòü çàäàíèå â ïðèëîæåíèè). Åñòåñòâåííî, ê çàâåðøåíèþ äàííîé ãëàâû Âû ñìîæåòå ââîäèòü
    200

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    äàííûå â Task (ïîñðåäñòâîì HTML ôîðìû), ïðîâåðÿòü äàííûå è ñîõðàíÿòü èõ â áàçå
    äàííûõ.
    Ñîçäàíèå ôîðìû
    Òåïåðü, êîãäà Âû ñîçäàëè êëàññ Task, ñëåäóþùèì øàãîì áóäåò ñîçäàíèå è âèçóàëèçàöèÿ ôàêòè÷åñêîé HTML ôîðìû.  Symfony2 ýòî ñîâåðøàåòñÿ ïóò¼ì ñîçäàíèÿ îáúåêòà
    ôîðìû è âèçóàëèçàöèè åãî â òåìïëåéòå. Òåïåðü âñ¼ ïåðå÷èñëåííîå ìîæíî ñäåëàòü â
    êîíòðîëëåðå:

    // src/Acme/TaskBundle/Controller/DefaultController.php
    namespace Acme\TaskBundle\Controller;
    use Symfony\Bundle\FrameworkBundle\Controller\Controller;
    use Acme\TaskBundle\Entity\Task;
    use Symfony\Component\HttpFoundation\Request;
    class DefaultController extends Controller
    {
    public function newAction(Request $request)
    {
    // create a task and give it some dummy data for this example
    $task = new Task();
    $task->setTask('Write a blog post');
    $task->setDueDate(new \DateTime('tomorrow'));
    $form = $this->createFormBuilder($task)
    ->add('task', 'text')
    ->add('dueDate', 'date')
    ->getForm();

    }

    }

    return $this->render('AcmeTaskBundle:Default:new.html.twig', array(
    'form' => $form->createView(),
    ));

    Ñîâåò: Ýòè ïðèìåðû äåìîíñòðèðóþò Âàì, êàêèì îáðàçîì ìîæíî ñîçäàòü ôîðìó â êîíòðîëëåðå. Ïîçæå, â ðàçäåëå Creating Form Classes, Âû íàó÷èòåñü ñîçäàâàòü ôîðìó â
    îáîñîáëåííîì êëàññå, ÷òî ðåêîìåíäóåòñÿ, ïîñêîëüêó Âàøà ôîðìà äîïóñêàåò ìíîãîêðàòíîå èñïîëüçîâàíèå.
    Äëÿ ñîçäàíèÿ ôîðìû òðåáóåò îòíîñèòåëüíî íåáîëüøîé êîä, ïîñêîëüêó îáúåêòû ôîðì
    Symfony2 ñîçäàþòñÿ ñ ïîìîùüþ ñïåöèàëüíîãî ïîñòðîèòåëÿ. Ïîñòðîèòåëü ôîðì ïðåäîñòàâëÿåò Âàì âîçìîæíîñòü íàïèñàòü ïðîñòîé ðåöåïò ôîðìû è çàñòàâèòü åãî âûïîë5.11. Ôîðìû

    201

    Symfony Documentation, Âûïóñê 2.0
    íÿòü âñþ òÿæ¼ëóþ ðàáîòó ïðè ñîçäàíèè ôàêòè÷åñêîé ôîðìû.
     ïðèâåä¼ííîì ïðèìåðå Âû äîáàâèëè â ôîðìó äâà ïîëÿ: task è dueDate, ñîîòâåòñòâóþùèå ñâîéñòâàì task è dueDate êëàññà Task. Âû òàêæå íàçíà÷èëè êàæäûé òèï (íàïðèìåð, text, date), êîòîðûé, ïîìèìî âñåãî ïðî÷åãî, îïðåäåëÿåò HTML òýã(-è) âèçóàëèçàöèè
    óêàçàííîãî ïîëÿ.
    Symfony2 ñîäåðæèò ìíîæåñòâî âñòðîåííûõ òèïîâ, î êîòîðûõ ìû âñêîðå ïîãîâîðèì (÷èòàéòå Âñòðîåííûå òèïû ïîëåé).
    Âèçóàëèçàöèÿ ôîðìû
    Ïîñëå ñîçäàíèÿ ôîðìû íàì íåîáõîäèìî âèçóàëèçèðîâàòü å¼. Ýòî ìîæíî ñäåëàòü ñ ïîìîùüþ âñòàâêè ñïåöèàëüíîãî îáúåêòà âèä â òåìïëåéò (îáðàòèòå âíèìàíèå íà $form>createView() â êîíòðîëëåëå), à òàêæå èñïîëüçîâàíèÿ íàáîðà õåëïåðîâ:

    • Twig

    {# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #}
    <form action="{{ path('task_new') }}" method="post" {{ form_enctype(form) }}>
    {{ form_widget(form) }}
    <input type="submit" />
    </form>
    • PHP

    <!-- src/Acme/TaskBundle/Resources/views/Default/new.html.php -->

    <form action="<?php echo $view['router']->generate('task_new') ?>" method="post" <?php echo $view[
    <?php echo $view['form']->widget($form) ?>
    <input type="submit" />
    </form>

    Ïðèìå÷àíèå:
    Äàííûé ïðèìåð ïðåäïîëàãàåò, ÷òî Âû óæå ñîçäàëè ìàðøðóò
    ïîä íàçâàíèåì task_new êîòîðûé óêàçûâàåò íà ñîçäàííûé ðàíåå êîíòðîëëåð
    202

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    AcmeTaskBundle:Default:new.
    Âîò è âñ¼! Ïîñëå ââåäåíèÿ form_widget(form), êàæäîå ïîëå â ôîðìå âèçóàëèçèðóåòñÿ íàðÿäó ñ ìàðêèðîâêîé è ñîîáùåíèåì îá îøèáêå (åñëè òàêîâîå èìååòñÿ). Îäíàêî, ïîêà ïîëå
    íå áóäåò äîñòàòî÷íî ãèáêèì. Îáû÷íî, Âàì áóäåò íåîáõîäèìî âèçóàëèçèðîâàòü êàæäîå
    ïîëå ôîðìû îòäåëüíî, ÷òîáû îòñëåæèâàòü âíåøíèé âèä ôîðìû. Ýòîìó Âû íàó÷èòåñü
    â ðàçäåëå Âèçóàëèçàöèÿ ôîðìû â òåìïëåéòå.
    Ïðåæäå, ÷åì èäòè äàëüøå, îáðàòèòå âíèìàíèå íà òî, êàêèì îáðàçîì ïîëå task ïîëó÷àåò çíà÷åíèå ñâîéñòâà task èç îáúåêòà $task (ò.å. Íàïèñàòü ñîîáùåíèå íà áëîãå). Â
    ýòîì çàêëþ÷àåòñÿ ïåðâàÿ çàäà÷à ôîðìû: èçûìàòü äàííûå èç îáúåêòà è ïåðåâîäèòü èõ
    â ôîðìàò, ïðèãîäíûé äëÿ âèçóàëèçàöèè â HTML ôîðìå.
    Ñîâåò: Ñèñòåìà äîñòàòî÷íî óìíà äëÿ òîãî, ÷òîáû ïîëó÷àòü äîñòóï ê çíà÷åíèþ çàùèù¼ííîãî ñâîéñòâà task, ñ ïîìîùüþ ôóíêöèé getTask() è setTask() êëàññ Task. Äî òåõ ïîð,
    ïîêà ñâîéñòâî äîñòóïíî äëÿ ñîâìåñòíîãî ïîëüçîâàíèÿ, åìó íåîáõîäèìî èìåòü ãåòòåð  è
    ñåòòåð, ÷òîáû êîìïîíåíò form ñìîã ïîëó÷àòü è âñòàâëÿòü äàííûå èç/â ñâîéñòâà. Äëÿ
    ñâîéñòâà Boolean Âû ìîæåòå èñïîëüçîâàòü ôóíêöèþ isset (íàïðèìåð, isPublished())
    âìåñòî ãåòòåðà (íàïðèìåð, getPublished()).

    Äîáàâëåíèå ôîðìû
    Âòîðàÿ çàäà÷à ôîðìû çàêëþ÷àåòñÿ â ïåðåâîäå äîáàâëåííûõ ïîëüçîâàòåëåì äàííûõ îáðàòíî â ñâîéñòâà îáúåêòà. ×òîáû ýòî ïðîèçîøëî, äîáàâëåííûå äàííûå äîëæíû îãðàíè÷èâàòüñÿ ôîðìîé. Äîáàâüòå ñëåäóþùèé ôóíêöèîíàë â êîíòðîëëåð:

    // ...
    public function newAction(Request $request)
    {
    // just setup a fresh $task object (remove the dummy data)
    $task = new Task();
    $form = $this->createFormBuilder($task)
    ->add('task', 'text')
    ->add('dueDate', 'date')
    ->getForm();
    if ($request->getMethod() == 'POST') {
    $form->bindRequest($request);
    if ($form->isValid()) {
    // perform some action, such as saving the task to the database

    5.11. Ôîðìû

    203

    Symfony Documentation, Âûïóñê 2.0

    }
    }

    }

    return $this->redirect($this->generateUrl('task_success'));

    // ...

    Òåïåðü, ïðè äîáàâëåíèè ôîðìû êîíòðîëëåð ñîåäèíÿåò ââåä¼ííûå äàííûå â ôîðìó, êîòîðàÿ ïåðåâîäèò èõ â ñâîéñòâà task è dueDate îáúåêòà $task. Âñ¼ ýòî ïðîèñõîäèò ñ
    ïîìîùüþ ôóíêöèè bindRequest().
    Ïðèìå÷àíèå: Ïîñëå âûçîâà ôóíêöèè bindRequest() äîáàâëåííûå äàííûå íåìåäëåííî
    ïåðåâîäÿòñÿ â îñíîâíîé îáúåêò. Ïðîèñõîäèò ýòî íåçàâèñèìî îò âàëèäíîñòè îñíîâíûõ
    äàííûõ.
    Êîíòðîëëåð ñëåäóåò îáùåé ìîäåëè îáðàáîòêè ôîðì è èìååò òðè âîçìîæíûõ íàïðàâëåíèÿ:
    1. Ïðè ïåðâîé çàãðóçêå ñòðàíèöû â áðàóçåðå èñïîëüçóåòñÿ ôóíêöèÿ GET. Ïðè ýòî
    ñîçäà¼òñÿ è âèçóàëèçèðóåòñÿ ôîðìà;
    2. Åñëè ïîëüçîâàòåëü äîáàâëÿåò ôîðìó (ò.å. èñïîëüçóåò ôóíêöèþ POST), èñïîëüçóÿ
    íåâåðíûå äàííûå (âàëèäàöèÿ ðàññìàòðèâàåòñÿ â ñëåäóþùåì ðàçäåëå), ýòà ôîðìà
    ñîçäà¼òñÿ è âèçóàëèçèðóåòñÿ ñ îòîáðàæåíèåì âñåõ îøèáîê ïðè âàëèäàöèè;
    3. Åñëè ïîëüçîâàòåëü äîáàâëÿåò ôîðìó ñ âåðíûìè äàííûìè, ôîðìà ñîçäà¼òñÿ è ó
    Âàñ ïîÿâëÿåòñÿ âîçìîæíîñòü ïðåäïðèíÿòü íåêîòîðûå äåéñòâèÿ, èñïîëüçóÿ îáúåêò
    $task (íàïðèìåð, ñîõðàíèòü ôîðìó â áàçå äàííûõ), ïåðåä òåì, êàê ïåðåíàïðàâèòü
    ïîëüçîâàòåëÿ íà äðóãóþ ñòðàíèöó (íàïðèìåð, thank you èëè success).
    Ïðèìå÷àíèå: Ïåðåíàïðàâëåíèå ïîëüçîâàòåëÿ ïîñëå óñïåøíîãî äîáàâëåíèÿ ôîðìû èçáàâëÿåò ïîñëåäíåãî îò îáíîâëåíèÿ ñòðàíèöû è ïîâòîðíîãî ââîäà äàííûõ.

    5.11.2 Form Validation

    Â ïðåäûäóùåì ðàçäåëå Âû óçíàëè êàê äîáàâèòü ôîðìó ñ ïîìîùüþ âåðíûõ èëè íåâåðíûõ äàííûõ. Â Symfony2 âàëèäàöèÿ ïðèìåíÿåòñÿ ê îñíîâíûì îáúåêòàì (íàïðèìåð,
    Task). Èíûìè ñëîâàìè, âîïðîñ çàêëþ÷àåòñÿ íå â âàëèäíîñòè ôîðìû, à â âàëèäíîñòè îáúåêòà $task ïîñëå ïðè¼ìà äîáàâëåííûõ â íåãî äàííûõ. Âûçîâ ôóíêöèè $form>isValid() ÿâëÿåòñÿ ÿðëûêîì, çàïðàøèâàþùèì îáúåêò $task ñ öåëüþ îïðåäåëåíèÿ âàëèäíîñòè ñîäåðæàùèõñÿ â í¼ì äàííûõ.
    Âàëèäàöèÿ âûïîëíÿåòñÿ ïóò¼ì äîáàâëåíèÿ íàáîðà ïðàâèë (íàçûâàåìûõ óòî÷íåíèÿìè)
    â êëàññ. ×òîáû óâèäåòü ïðîöåññ â äåéñòâèè, äîáàâüòå óòî÷íåíèÿ âàëèäàöèè. Ïðè ýòîì
    204

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    ïîëÿ task è dueDate äîëæíû áûòü çàïîëíåííûìè, à ïîëå dueDate äîëæíî ïðåäñòàâëÿòü
    ñîáîé âàëèäíûé DateTime îáúåêò.

    • YAML

    # Acme/TaskBundle/Resources/cong/validation.yml
    Acme\TaskBundle\Entity\Task:
    properties:
    task:
    - NotBlank: ~
    dueDate:
    - NotBlank: ~
    - Type: \DateTime
    • Annotations

    // Acme/TaskBundle/Entity/Task.php
    use Symfony\Component\Validator\Constraints as Assert;
    class Task
    {
    /**
    * @Assert\NotBlank()
    */
    public $task;

    }

    /**
    * @Assert\NotBlank()
    * @Assert\Type("\DateTime")
    */
    protected $dueDate;

    • XML

    <!-- Acme/TaskBundle/Resources/cong/validation.xml -->
    <class name="Acme\TaskBundle\Entity\Task">
    <property name="task">
    <constraint name="NotBlank" />
    </property>
    <property name="dueDate">
    <constraint name="NotBlank" />
    <constraint name="Type">
    <value>\DateTime</value>
    </constraint>
    </property>
    </class>
    • PHP
    5.11. Ôîðìû

    205

    Symfony Documentation, Âûïóñê 2.0

    // Acme/TaskBundle/Entity/Task.php
    use Symfony\Component\Validator\Mapping\ClassMetadata;
    use Symfony\Component\Validator\Constraints\NotBlank;
    use Symfony\Component\Validator\Constraints\Type;
    class Task
    {
    // ...
    public static function loadValidatorMetadata(ClassMetadata $metadata)
    {
    $metadata->addPropertyConstraint('task', new NotBlank());

    }

    }

    $metadata->addPropertyConstraint('dueDate', new NotBlank());
    $metadata->addPropertyConstraint('dueDate', new Type('\DateTime'));

    Âîò è âñ¼! Åñëè Âû ïîâòîðíî äîáàâèòå ôîðìó, ñîäåðæàùóþ íåâåðíûå äàííûå, òî óâèäèòå ñîîòâåòñòâóþùåå ñîîáùåíèå îá îøèáêå.
    HTML5 Âàëèäàöèÿ
    ×òî êàñàåòñÿ HTML5, ìíîãèå áðàóçåðû èìåþò îïðåäåë¼ííûå âñòðîåííûå âàëèäàöèîííûå óòî÷íåíèÿ ñî ñòîðîíû ïîëüçîâàòåëÿ. Ñàìûé îáû÷íûé ñïîñîá âàëèäàöèè
    àâêòèâèðóåòñÿ ïóò¼ì âèçóàëèçàöèè îïèñàòåëÿ required â îáÿçàòåëüíûõ ïîëÿõ. Äëÿ
    áðàóçåðîâ, ïîääåðæèâàþùèõ HTML5, òîò æå ðåçóëüòàò äîñòèãàåòñÿ ïóò¼ì îòîáðàæåíèÿ âñòðîåííîãî ñîîáùåíèÿ â ñëó÷àå, êîãäà ïîëüçîâàòåëü ïûòàåòñÿ äîáàâèòü
    ôîðìó ñ ïóñòûìè îáÿçàòåëüíûìè ïîëÿìè.
    Ãåíåðèðîâàííûå ôîðìû ïîëíîñòüþ èñïîëüçóåò ýòó îñîáåííîñòü ïóò¼ì äîáàâëåíèÿ
    HTML îïèñàòåëåé, ïðèâîäÿùèõ â äåéñòâèå ïðîöåññ âàëèäàöèè. Âàëèäàöèþ ñî ñòîðîíû ïîëüçîâàòåëÿ ìîæíî îòêëþ÷èòü, äîáàâèâ îïèñàòåëü novalidate ê òýãó form
    èëè formnovalidate äëÿ äîáàâëåíèÿ. Ýòî îñîáåííî ïîëåçíî, êîãäà Âû õîòèòå ïðîòåñòèðîâàòü óòî÷íåíèÿ âàëèäàöèè ñî ñòîðîíû ñåðâåðà, íî áðàóçåð íå ïîçâîëÿåò ýòîãî
    ñäåëàòü (íàïðèìåð, äîáàâèòü ôîðìó ñ ïóñòûìè ïîëÿìè).
    Âàëèäàöèÿ  î÷åíü âàæíûé ýëåìåíò Symfony2, êîòîðîìó ïîñâÿùåíà îòäåëüíàÿ ãëàâà.
    Âàëèäàöèîííûå ãðóïïû
    Ñîâåò: Åñëè Âû íå èñïîëüçóåòå âàëèäàöèîííûå ãðóïïû, òî ìîæåòå ïðîïóñòèòü ýòîò
    ðàçäåë.

    206

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    Åñëè Âàø îáúåêò èñïîëüçóåò âàëèäàöèîííûå ãðóïïû, Âàì íåîáõîäèìî áóäåò óêàçàòü òå
    èç íèõ, êîòîðûå áóäóò èñïîëüçîâàíû ôîðìîé:

    $form = $this->createFormBuilder($users, array(
    'validation_groups' => array('registration'),
    ))->add(...)
    ;
    Åñëè Âû èñïîëüçóåòå êëàññû ôîðì (õîðîøàÿ ïðàêòèêà), â ôóíêöèþ getDefaultOptions()
    Âàì íåîáõîäèìî äîáàâèòü ñëåäóþùåå:

    public function getDefaultOptions(array $options)
    {
    return array(
    'validation_groups' => array('registration')
    );
    }
     îáîèõ ñëó÷àÿõ òîëüêî ãðóïïà registration áóäåò èñïîëüçîâàòüñÿ äëÿ âàëèäàöèè îñíîâíûõ îáúåêòîâ.
    5.11.3 Âñòðîåííûå òèïû ïîëåé

    Symfony èçíà÷àëüíî ñîäåðæèò áîëüøîé íàáîð òèïîâ ïîëåé, ñîâìåñòèìûõ ñî âñåìè îáû÷íûìè ïîëÿìè ôîðì è òèïàìè äàííûõ, ñ êîòîðûìè Âû ìîæåòå ñòîëêíóòüñÿ:
    Text Fields

    • text
    • textarea
    • email
    • integer
    • money
    • number
    • password
    • percent
    • search
    • url

    5.11. Ôîðìû

    207

    Symfony Documentation, Âûïóñê 2.0
    Choice Fields

    • choice
    • entity
    • country
    • language
    • locale
    • timezone
    Date and Time Fields

    • date
    • datetime
    • time
    • birthday
    Other Fields

    • checkbox
    • le
    • radio
    Field Groups

    • collection
    • repeated
    Hidden Fields

    • hidden
    • csrf

    208

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    Base Fields

    • eld
    • form
    Âû òàêæå ìîæåòå ñîçäàâàòü ñîáñòâåííûå òèïû ïîëåé. This topic is covered in the How
    to Create a Custom Form Field Type article of the cookbook.
    Îïöèè òèïà ïîëÿ
    Êàæäûé òèï ïîëÿ èìååò íàáîð îïöèé, èñïîëüçóåìûõ äëÿ åãî íàñòðîéêè. Íàïðèìåð,
    ïîëå dueDate îòîáðàæàåòñÿ â âèäå òð¼õ îêîí âûáîðà. Îäíàêî, ïîëå date eld ìîæíî
    íàñòðîèòü òàêèì îáðàçîì, ÷òî îíî áóäåò îòîáðàæàòüñÿ â âèäå îäíîãî òåêñòîâîãî îêíà
    (êóäà ïîëüçîâàòåëü áóäåò ââîäèòü äàòó â âèäå ñòðîêè):

    ->add('dueDate', 'date', array('widget' => 'single_text'))

    Êàæäûé òèï ïîëÿ èìååò íàáîð ðàçëè÷íûõ, ïîäõîäÿùèõ äëÿ íåãî, îïöèé. Ìíîãèå èç íèõ
    ñîîòíîñÿòñÿ ñ êîíêðåòíûì òèïîì ïîëÿ (ïîäðîáíîñòè ìîæíî óçíàòü â äîêóìåíòàöèè ïî
    êàæäîìó òèïó).
    Îïöèÿ required
    Íàèáîëåå îáû÷íîé ÿâëÿåòñÿ îïöèÿ required, êîòîðóþ ìîæíî ïðèìåíèòü ê ëþáîìó ïîëþ. Ïî óìîë÷àíèþ, îïöèÿ required èìååò çíà÷åíèå true. Ýòî îçíà÷àåò, ÷òî
    áðàóçåðû, ïîääåðæèâàþùèå HTML5 áóäóò ïðèìåíÿòü âàëèäàöèþ íà ñòîðîíå ïîëüçîâàòåëÿ â ñëó÷àå, êîãäà ïîëå áóäåò íåçàïîëíåííûì. Åñëè òàêàÿ ëèíèÿ ïîâåäåíèÿ
    Âàñ íå óñòðàèâàåò, Âû ìîæåòå óñòàíîâèòü çíà÷åíèå false äëÿ îïöèè required Âàøåãî
    ïîëÿ, ëèáî îòêëþ÷èòü HTML5 âàëèäàöèþ.
    Îáðàòèòå âíèìàíèå, ÷òî óñòàíîâêà çíà÷åíèÿ true äëÿ îïöèè required íå ïðèâåä¼ò
    ê ïðèìåíåíèþ âàëèäàöèè íà ñòîðîíå ñåðâåðà. Èíûìè ñëîâàìè, åñëè ïîëüçîâàòåëü
    äîáàâëÿåò â ïîëå ïóñòîå çíà÷åíèå (íàïðèìåð, â ñîâîêóïíîñòè ñî ñòàðûì áðàóçåðîì èëè âåá-ñåðâèñîì), îíî áóäåò ñ÷èòàòüñÿ âàëèäíûì äî òåõ ïîð, ïîêà Âû íå
    èñïîëüçóåòå óòî÷íåíèÿ NotBlank èëè NotNull îò Symfony.
    Èíûìè ñëîâàìè, îïöèÿ required äîñòàòî÷íî õîðîøà, îäíàêî âñåãäà ñëåäóåò èñïîëüçîâàòü íàñòîÿùóþ âàëèäàöèþ íà ñòîðîíå ñåðâåðà.

    5.11. Ôîðìû

    209

    Symfony Documentation, Âûïóñê 2.0
    5.11.4 Óãàäûâàíèå òèïà ïîëÿ

    Òåïåðü, êîãäà Âû äîáàâèëè ìåòàäàííûå âàëèäàöèè â êëàññ Task, Symfony ïîëó÷èëà
    èíôîðìàöèþ î èñïîëüçóåìûõ Âàìè ïîëÿõ. Åñëè ïîæåëàåòå, Symfony ìîæåò óãàäàòü
    òèï Âàøåãî ïîëÿ è óñòàíîâèòü åãî. Â ñëåäóþùåì ïðèìåðå Symfony, èñïîëüçóÿ ïðàâèëà
    âàëèäàöèè, ìîæåò óãàäàòü, ÷òî ïîëå task ÿâëÿåòñÿ îáû÷íûì ïîëåì text, à ïîëå dueDate
    ÿâëÿåòñÿ ïîëåì date:

    public function newAction()
    {
    $task = new Task();

    }

    $form = $this->createFormBuilder($task)
    ->add('task')
    ->add('dueDate', null, array('widget' => 'single_text'))
    ->getForm();

    Ïðîöåññ óãàäûâàíèÿ çàïóñêàåòñÿ, êîãäà Âû ïðîïóñêàåòå âòîðîé ïàðàìåòð ôóíêöèè
    add() (èëè ââîäèòå ïàðàìåòð null). Åñëè Âû ïåðåäà¼òå ìàññèâ îïöèé â êà÷åñòâå òðåòüåãî
    ïàðàìåòðà (÷òî áûëî ñäåëàíî âûøå äëÿ dueDate), ýòè îïöèè ïðèìåíÿþòñÿ â óãàäàííîì
    ïîëå.
    Îñòîðîæíî: Åñëè ôîðìà èñïîëüçóåò îñîáóþ ãðóïïó âàëèäàöèè, ìåõàíèçì, óãàäûâàþùèé òèï ïîëÿ, áóäåò ó÷èòûâàòü âñå âàëèäàöèîííûå óòî÷íåíèÿ âî âðåìÿ óãàäûâàíèÿ òèïà ïîëÿ (âêëþ÷àÿ óòî÷íåíèÿ, íå ÿâëÿþùèåñÿ ÷àñòüþ èñïîëüçóþùèõñÿ
    âàëèäàöèîííûõ ãðóïï).

    Óãàäûâàíèå îïöèé òèïà ïîëÿ
    Âäîáàâîê ê óãàäûâàíèþ òèïà ïîëÿ, Symfony ìîæåò òàêæå ïîïûòàòüñÿ óãàäàòü ïðàâèëüíûå çíà÷åíèÿ íåêîòîðûõ îïöèé ïîëÿ.
    Ñîâåò: Êîãäà ýòè îïöèè óñòàíîâëåíû, ïîëå âèçóàëèçèðóåòñÿ ñ ïîìîùüþ îñîáûõ HTML
    àòðèáóòîâ, îáåñïå÷èâàþùèõ HTML5 âàëèäàöèþ íà ñòîðîíå ïîëüçîâàòåëÿ. Îäíàêî,
    ïðè ýòîì íå ãåíåðèðóþòñÿ ýêâèâàëåíòíûå óòî÷íåíèÿ íà ñòîðîíå ñåðâåðà (íàïðèìåð,
    Assert\MaxLength). Õîòÿ Âàì íåîáõîäèìî áóäåò âðó÷íóþ äîáàâëÿòü âàëèäàöèþ íà ñòîðîíå ñåðâåðà, ýòè îïöèè òèïà ïîëÿ çàòåì ìîãóò áûòü óãàäàíû, áëàãîäàðÿ ââåä¼ííîé
    èíôîðìàöèè.

    • required: Îïöèþ required ìîæíî óãàäàòü, èñõîäÿ èç ïðàâèë âàëèäàöèè (ò.å. ÿâëÿåòñÿ ëè ïîëå NotBlank èëè NotNull) èëè èç ìåòàäàííûõ Doctrine (ò.å. ÿâëÿåòñÿ ëè
    ïîëå nullable). Ýòî î÷åíü ïîëåçíî, ïîñêîëüêó âàëèäàöèÿ íà ñòîðîíå ïîëüçîâàòåëÿ
    àâòîìàòè÷åñêè áóäåò ñîîòâåòñòâîâàòü ïðàâèëàì âàëèäàöèè.
    210

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    • min_length: Åñëè ïîëå ÿâëÿåòñÿ êàêèì-ëèáî òåêñòîâûì ïîëåì, îïöèþ min_length
    ìîæíî óãàäàòü, áëàãîäàðÿ âàëèäàöèîííûì óòî÷íåíèÿì (ïðè èñïîëüçîâàíèè
    MinLength èëè Min) èëè ìåòàäàííûì Doctrine (ïîñðåäñòâîì äëèíû ïîëÿ).
    • max_length: Ñõîäíà ñ min_length, ìîæíî óãàäàòü ìàêñèìàëüíóþ äëèíó.
    Ïðèìå÷àíèå: Ýòè îïöèè ïîëÿ ìîæíî óãàäàòü òîëüêî òîãäà, êîãäà äëÿ óãàäûâàíèÿ
    òèïà ïîëÿ Âû èñïîëüçóåòå Symfony (ò.å. ïðîïóñêàÿ èëè äîáàâëÿÿ null â êà÷åñòâå âòîðîãî
    ïàðàìåòðà ôóíêöèè add()).
    Åñëè Âû çàõîòèòå èçìåíèòü îäíî èç óãàäàííûõ çíà÷åíèé, Âû ìîæåòå çàìåíèòü åãî ñ
    ïîìîùüþ âñòàâêè îïöèè â ìàññèâ îïöèé ïîëÿ:

    ->add('task', null, array('min_length' => 4))

    5.11.5 Âèçóàëèçàöèÿ ôîðìû â òåìïëåéòå

    Èòàê, Âû óâèäåëè, êàê ìîæíî âèçóàëèçèðîâàòü ôîðìó ñ ïîìîùüþ, âñåãî ëèøü, îäíîé
    ñòðî÷êè êîäà. Åñòåñòâåííî, Âàì ïîòðåáóåòñÿ áîëüøàÿ ãèáêîñòü â ïðîöåññå âèçóàëèçàöèè: .. conguration-block:

    .. code-block:: html+jinja
    {# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #}
    <form action="{{ path('task_new') }}" method="post" {{ form_enctype(form) }}>
    {{ form_errors(form) }}
    {{ form_row(form.task) }}
    {{ form_row(form.dueDate) }}
    {{ form_rest(form) }}
    <input type="submit" />
    </form>
    .. code-block:: html+php
    <!-- // src/Acme/TaskBundle/Resources/views/Default/newAction.html.php -->

    <form action="<?php echo $view['router']->generate('task_new') ?>" method="post" <?php echo $view['for
    <?php echo $view['form']->errors($form) ?>
    <?php echo $view['form']->row($form['task']) ?>
    <?php echo $view['form']->row($form['dueDate']) ?>
    5.11. Ôîðìû

    211

    Symfony Documentation, Âûïóñê 2.0

    <?php echo $view['form']->rest($form) ?>
    <input type="submit" />
    </form>
    Äàâàéòå ðàññìîòðèì êàæäûé ýòàï:

    • form_enctype(form) - Åñëè õîòü îäíî ïîëå ÿâëÿåòñÿ ïîëåì çàãðóçêè ôàéëîâ, îáÿçàòåëüíî âèçóàëèçèðóåòñÿ enctype="multipart/form-data";
    • form_errors(form) - Âèçóàëèçèðóåò îáùèå äëÿ âñåé ñèñòåìû îøèáêè (îøèáêè îòäåëüíûõ ïîëåé îòîáðàæàþòñÿ âñëåä çà êàæäûì ïîëåì);
    • form_row(form.dueDate) - Âèçóàëèçèðóåò ÿðëûê, ëþáûå îøèáêè, à òàêæå âèäæåò
    HTML ôîðìû äëÿ çàäàííîãî ïîëÿ (íàïðèìåð, dueDate) â ýëåìåíòå div (ïî óìîë÷àíèþ);
    • form_rest(form) - Âèçóàëèçèðóåò ëþáîå íå âèçóàëèçèðîâàííîå ïðåæäå ïîëå. Â
    íèæíåé ÷àñòè êàæäîé ôîðìû ïîëåçíî ðàçìåùàòü âûçîâ äàííîãî õåëïåðà (â ñëó÷àå,
    åñëè Âû çàáûëè âûâåñòè ïîëå èëè íå æåëàåòå òðîãàòü ñêðûòûå ïîëÿ, íàñòðàèâàåìûå âðó÷íóþ). Ýòîò õåëïåð òàêæå ïîëåçåí äëÿ èñïîëüçîâàíèÿ àâòîìàòè÷åñêîé
    çàùèòû îò CSRF.
    Áîëüøóþ ÷àñòü ðàáîòû âûïîëíÿåò õåëïåð form_row, âèçóàëèçèðóþùèé ÿðëûê, îøèáêè
    è âèäæåò HTML ôîðìû êàæäîãî ïîëÿ â ïðåäåëàõ òýãà div (ïî óìîë÷àíèþ).  ðàçäåëå
    Form Theming Âû ïîçíàêîìèòåñü ñ òåì, êàê ìîæíî ìîäèôèöèðîâàòü âûõîä form_row
    íà ðàçëè÷íûõ óðîâíÿõ.
    Âèçóàëèçàöèÿ êàæäîãî ïîëÿ âðó÷íóþ
    Õåëïåð form_row ïðåäîñòàâëÿåò Âàì âîçìîæíîñòü áûñòðî âèçóàëèçèðîâàòü êàæäîå ïîëå ôîðìû (òàêæå ìîæíî èçìåíèòü ðàçìåòêó, èñïîëüçóåìóþ äëÿ ñòðîêè). Îäíàêî, ïîñêîëüêó â æèçíè íå âñ¼ òàê ïðîñòî, Âû òàêæå ìîæåòå âèçóàëèçèðîâàòü êàæäîå ïîëå
    âðó÷íóþ. Ðåçóëüòàò íå áóäåò îòëè÷àòüñÿ îò òàêîâîãî, äîñòèãíóòîãî ñ èñïîëüçîâàíèåì
    õåëïåðà form_row:

    • Twig

    {{ form_errors(form) }}
    <div>
    {{ form_label(form.task) }}
    {{ form_errors(form.task) }}
    {{ form_widget(form.task) }}
    </div>
    <div>
    212

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    {{ form_label(form.dueDate) }}
    {{ form_errors(form.dueDate) }}
    {{ form_widget(form.dueDate) }}
    </div>
    {{ form_rest(form) }}
    • PHP

    <?php echo $view['form']->errors($form) ?>
    <div>
    <?php echo $view['form']->label($form['task']) ?>
    <?php echo $view['form']->errors($form['task']) ?>
    <?php echo $view['form']->widget($form['task']) ?>
    </div>
    <div>
    <?php echo $view['form']->label($form['dueDate']) ?>
    <?php echo $view['form']->errors($form['dueDate']) ?>
    <?php echo $view['form']->widget($form['dueDate']) ?>
    </div>
    <?php echo $view['form']->rest($form) ?>
    Åñëè ñîçäàííûé àâòîìàòè÷åñêè ÿðëûê íå ñîâñåì âåðåí, Âû ìîæåòå ïîäêîððåêòèðîâàòü
    åãî:

    • Twig

    {{ form_label(form.task, 'Task Description') }}
    • PHP

    <?php echo $view['form']->label($form['task'], 'Task Description') ?>
    Íàêîíåö, íåêîòîðûå òèïû ïîëåé èìåþò äîïîëíèòåëüíûå îïöèè âèçóàëèçàöèè, êîòîðûå
    ìîæíî ââåñòè â âèäæåò. Òàêèå îïöèè çàäîêóìåíòèðîâàíû äëÿ êàæäîãî òèïà, íî ñóùåñòâóåò îïöèÿ attr, êîòîðàÿ ïîçâîëÿåò èçìåíÿòü àòðèáóòû â ýëåìåíòå ôîðìû. Ýòî
    äîáàâëÿåò êëàññ task_eld â âèçóàëèçèðîâàííîå âõîäíîå òåêñòîâîå ïîëå:

    • Twig

    {{ form_widget(form.task, { 'attr': {'class': 'task_eld'} }) }}
    • PHP

    5.11. Ôîðìû

    213

    Symfony Documentation, Âûïóñê 2.0

    <?php echo $view['form']->widget($form['task'], array(
    'attr' => array('class' => 'task_eld'),
    )) ?>
    Twig Template Function Reference
    If you're using Twig, a full reference of the form rendering functions is available in the
    reference manual. Read this to know everything about the helpers available and the options
    that can be used with each.
    5.11.6 Creating Form Classes

    As you've seen, a form can be created and used directly in a controller. However, a better
    practice is to build the form in a separate, standalone PHP class, which can then be reused
    anywhere in your application. Create a new class that will house the logic for building the
    task form:

    // src/Acme/TaskBundle/Form/Type/TaskType.php
    namespace Acme\TaskBundle\Form\Type;
    use Symfony\Component\Form\AbstractType;
    use Symfony\Component\Form\FormBuilder;
    class TaskType extends AbstractType
    {
    public function buildForm(FormBuilder $builder, array $options)
    {
    $builder->add('task');
    $builder->add('dueDate', null, array('widget' => 'single_text'));
    }

    }

    public function getName()
    {
    return 'task';
    }

    This new class contains all the directions needed to create the task form (note that the
    getName() method should return a unique identier for this form type). It can be used to
    quickly build a form object in the controller:

    // src/Acme/TaskBundle/Controller/DefaultController.php

    214

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    // add this new use statement at the top of the class
    use Acme\TaskBundle\Form\Type\TaskType;
    public function newAction()
    {
    $task = // ...
    $form = $this->createForm(new TaskType(), $task);
    }

    // ...

    Placing the form logic into its own class means that the form can be easily reused elsewhere
    in your project. This is the best way to create forms, but the choice is ultimately up to you.
    Setting the data_class
    Every form needs to know the name of the class that holds the underlying data (e.g.
    Acme\TaskBundle\Entity\Task). Usually, this is just guessed based o of the object
    passed to the second argument to createForm (i.e. $task). Later, when you begin
    embedding forms, this will no longer be sucient. So, while not always necessary, it's
    generally a good idea to explicitly specify the data_class option by add the following
    to your form type class:

    public function getDefaultOptions(array $options)
    {
    return array(
    'data_class' => 'Acme\TaskBundle\Entity\Task',
    );
    }

    5.11.7 Forms and Doctrine

    The goal of a form is to translate data from an object (e.g. Task) to an HTML form and then
    translate user-submitted data back to the original object. As such, the topic of persisting
    the Task object to the database is entirely unrelated to the topic of forms. But, if you've
    congured the Task class to be persisted via Doctrine (i.e. you've added mapping metadata
    for it), then persisting it after a form submission can be done when the form is valid:

    if ($form->isValid()) {
    $em = $this->getDoctrine()->getEntityManager();
    $em->persist($task);
    $em->ush();
    return $this->redirect($this->generateUrl('task_success'));
    5.11. Ôîðìû

    215

    Symfony Documentation, Âûïóñê 2.0

    }
    If, for some reason, you don't have access to your original $task object, you can fetch it from
    the form:

    $task = $form->getData();
    For more information, see the Doctrine ORM chapter.
    The key thing to understand is that when the form is bound, the submitted data is transferred
    to the underlying object immediately. If you want to persist that data, you simply need to
    persist the object itself (which already contains the submitted data).
    5.11.8 Embedded Forms

    Often, you'll want to build a form that will include elds from many dierent objects. For
    example, a registration form may contain data belonging to a User object as well as many
    Address objects. Fortunately, this is easy and natural with the form component.
    Embedding a Single Object
    Suppose that each Task belongs to a simple Category object. Start, of course, by creating
    the Category object:

    // src/Acme/TaskBundle/Entity/Category.php
    namespace Acme\TaskBundle\Entity;
    use Symfony\Component\Validator\Constraints as Assert;
    class Category
    {
    /**
    * @Assert\NotBlank()
    */
    public $name;
    }
    Next, add a new category property to the Task class:

    // ...
    class Task
    {
    // ...

    216

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    /**
    * @Assert\Type(type="Acme\TaskBundle\Entity\Category")
    */
    protected $category;
    // ...
    public function getCategory()
    {
    return $this->category;
    }

    }

    public function setCategory(Category $category = null)
    {
    $this->category = $category;
    }

    Now that your application has been updated to reect the new requirements, create a form
    class so that a Category object can be modied by the user:

    // src/Acme/TaskBundle/Form/Type/CategoryType.php
    namespace Acme\TaskBundle\Form\Type;
    use Symfony\Component\Form\AbstractType;
    use Symfony\Component\Form\FormBuilder;
    class CategoryType extends AbstractType
    {
    public function buildForm(FormBuilder $builder, array $options)
    {
    $builder->add('name');
    }
    public function getDefaultOptions(array $options)
    {
    return array(
    'data_class' => 'Acme\TaskBundle\Entity\Category',
    );
    }

    }

    public function getName()
    {
    return 'category';
    }

    5.11. Ôîðìû

    217

    Symfony Documentation, Âûïóñê 2.0
    The end goal is to allow the Category of a Task to be modied right inside the task form
    itself. To accomplish this, add a category eld to the TaskType object whose type is an
    instance of the new CategoryType class:

    public function buildForm(FormBuilder $builder, array $options)
    {
    // ...
    }

    $builder->add('category', new CategoryType());

    The elds from CategoryType can now be rendered alongside those from the TaskType class.
    Render the Category elds in the same way as the original Task elds:

    • Twig

    {# ... #}
    <h3>Category</h3>
    <div class="category">
    {{ form_row(form.category.name) }}
    </div>
    {{ form_rest(form) }}
    {# ... #}
    • PHP

    <!-- ... -->
    <h3>Category</h3>
    <div class="category">
    <?php echo $view['form']->row($form['category']['name']) ?>
    </div>
    <?php echo $view['form']->rest($form) ?>
    <!-- ... -->
    When the user submits the form, the submitted data for the Category elds are used to
    construct an instance of Category, which is then set on the category eld of the Task instance.
    The Category instance is accessible naturally via $task->getCategory() and can be persisted
    to the database or used however you need.
    Embedding a Collection of Forms
    You can also embed a collection of forms into one form. This is done by using the collection
    eld type. For more information, see the collection eld type reference.
    218

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    5.11.9 Form Theming

    Every part of how a form is rendered can be customized. You're free to change how each form
    row renders, change the markup used to render errors, or even customize how a textarea
    tag should be rendered. Nothing is o-limits, and dierent customizations can be used in
    dierent places.
    Symfony uses templates to render each and every part of a form, such as label tags, input
    tags, error messages and everything else.
    In Twig, each form fragment is represented by a Twig block. To customize any part of how
    a form renders, you just need to override the appropriate block.
    In PHP, each form fragment is rendered via an individual template le. To customize any
    part of how a form renders, you just need to override the existing template by creating a
    new one.
    To understand how this works, let's customize the form_row fragment and add a class
    attribute to the div element that surrounds each row. To do this, create a new template le
    that will store the new markup:

    • Twig

    {# src/Acme/TaskBundle/Resources/views/Form/elds.html.twig #}
    {% block eld_row %}
    {% spaceless %}
    <div class="form_row">
    {{ form_label(form) }}
    {{ form_errors(form) }}
    {{ form_widget(form) }}
    </div>
    {% endspaceless %}
    {% endblock eld_row %}
    • PHP

    <!-- src/Acme/TaskBundle/Resources/views/Form/eld_row.html.php -->
    <div class="form_row">
    <?php echo $view['form']->label($form, $label) ?>
    <?php echo $view['form']->errors($form) ?>
    <?php echo $view['form']->widget($form, $parameters) ?>
    </div>
    The eld_row form fragment is used when rendering most elds via the form_row function.
    To tell the form component to use your new eld_row fragment dened above, add the
    following to the top of the template that renders the form:

    • Twig
    5.11. Ôîðìû

    219

    Symfony Documentation, Âûïóñê 2.0

    {# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #}
    {% form_theme form 'AcmeTaskBundle:Form:elds.html.twig' %}
    <form ...>
    • PHP

    <!-- src/Acme/TaskBundle/Resources/views/Default/new.html.php -->
    <?php $view['form']->setTheme($form, array('AcmeTaskBundle:Form')) ?>
    <form ...>
    The form_theme tag (in Twig) imports the fragments dened in the given template and
    uses them when rendering the form. In other words, when the ``form_row` function is called
    later in this template, it will use the eld_row block from your custom theme (instead of
    the default eld_row block that ships with Symfony).
    To customize any portion of a form, you just need to override the appropriate fragment.
    Knowing exactly which block or le to override is the subject of the next section.
    For a more extensive discussion, see How to customize Form Rendering.
    Form Fragment Naming
    In Symfony, every part a form that is rendered - HTML form elements, errors, labels, etc - is
    dened in a base theme, which is a collection of blocks in Twig and a collection of template
    les in PHP.
    In Twig, every block needed is dened in a single template le (form_div_layout.html.twig)
    that lives inside the Twig Bridge. Inside this le, you can see every block needed to render
    a form and every default eld type.
    In PHP, the fragments are individual template les. By default they are located in the
    Resources/views/Form directory of the framework bundle (view on GitHub).
    Each fragment name follows the same basic pattern and is broken up into two pieces,
    separated by a single underscore character (_). A few examples are:

    • eld_row - used by form_row to render most elds;
    • textarea_widget - used by form_widget to render a textarea eld type;
    • eld_errors - used by form_errors to render errors for a eld;
    Each fragment follows the same basic pattern: type_part. The type portion corresponds to
    the eld type being rendered (e.g. textarea, checkbox, date, etc) whereas the part portion

    220

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    corresponds to what is being rendered (e.g. label, widget, errors, etc). By default, there are
    4 possible parts of a form that can be rendered:
    label
    widget
    errors
    row

    (e.g.
    (e.g.
    (e.g.
    (e.g.

    eld_label)
    eld_widget)
    eld_errors)
    eld_row)

    renders
    renders
    renders
    renders

    the
    the
    the
    the

    eld's
    eld's
    eld's
    eld's

    label
    HTML representation
    errors
    entire row (label, widget & errors)

    Ïðèìå÷àíèå: There are actually 3 other parts - rows, rest, and enctype - but you should
    rarely if ever need to worry about overriding them.
    By knowing the eld type (e.g. textarea) and which part you want to customize (e.g. widget),
    you can construct the fragment name that needs to be overridden (e.g. textarea_widget).
    Template Fragment Inheritance
    In some cases, the fragment you want to customize will appear to be missing. For example,
    there is no textarea_errors fragment in the default themes provided with Symfony. So how
    are the errors for a textarea eld rendered?
    The answer is: via the eld_errors fragment. When Symfony renders the errors for a textarea
    type, it looks rst for a textarea_errors fragment before falling back to the eld_errors
    fragment. Each eld type has a parent type (the parent type of textarea is eld), and Symfony
    uses the fragment for the parent type if the base fragment doesn't exist.
    So, to override the errors for only textarea elds, copy the eld_errors fragment, rename it
    to textarea_errors and customize it. To override the default error rendering for all elds,
    copy and customize the eld_errors fragment directly.
    Ñîâåò: The parent type of each eld type is available in the form type reference for each
    eld type.

    Global Form Theming
    In the above example, you used the form_theme helper (in Twig) to import the custom
    form fragments into just that form. You can also tell Symfony to import form customizations
    across your entire project.

    Twig
    To automatically include the customized blocks from the elds.html.twig template created
    earlier in all templates, modify your application conguration le:

    5.11. Ôîðìû

    221

    Symfony Documentation, Âûïóñê 2.0

    • YAML

    # app/cong/cong.yml
    twig:
    form:
    resources:
    - 'AcmeTaskBundle:Form:elds.html.twig'
    # ...
    • XML

    <!-- app/cong/cong.xml -->
    <twig:cong ...>
    <twig:form>
    <resource>AcmeTaskBundle:Form:elds.html.twig</resource>
    </twig:form>
    <!-- ... -->
    </twig:cong>
    • PHP

    // app/cong/cong.php
    $container->loadFromExtension('twig', array(
    'form' => array('resources' => array(
    'AcmeTaskBundle:Form:elds.html.twig',
    ))
    // ...
    ));
    Any blocks inside the elds.html.twig template are now used globally to dene form output.

    222

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    Customizing Form Output all in a Single File with Twig
    In Twig, you can also customize a form block right inside the template where that
    customization is needed:

    {% extends '::base.html.twig' %}
    {# import "_self" as the form theme #}
    {% form_theme form _self %}
    {# make the form fragment customization #}
    {% block eld_row %}
    {# custom eld row output #}
    {% endblock eld_row %}
    {% block content %}
    {# ... #}
    {{ form_row(form.task) }}
    {% endblock %}
    The {% form_theme form _self %} tag allows form blocks to be customized directly
    inside the template that will use those customizations. Use this method to quickly make
    form output customizations that will only ever be needed in a single template.

    PHP
    To
    automatically
    include
    the
    customized
    templates
    from
    the
    Acme/TaskBundle/Resources/views/Form directory created earlier in all templates,
    modify your application conguration le:

    • YAML

    # app/cong/cong.yml
    framework:
    templating:
    form:
    resources:
    - 'AcmeTaskBundle:Form'
    # ...
    • XML

    <!-- app/cong/cong.xml -->
    <framework:cong ...>
    5.11. Ôîðìû

    223

    Symfony Documentation, Âûïóñê 2.0

    <framework:templating>
    <framework:form>
    <resource>AcmeTaskBundle:Form</resource>
    </framework:form>
    </framework:templating>
    <!-- ... -->
    </framework:cong>
    • PHP

    // app/cong/cong.php
    $container->loadFromExtension('framework', array(
    'templating' => array('form' =>
    array('resources' => array(
    'AcmeTaskBundle:Form',
    )))
    // ...
    ));
    Any fragments inside the Acme/TaskBundle/Resources/views/Form directory are now used
    globally to dene form output.
    5.11.10 CSRF Protection

    CSRF - or Cross-site request forgery - is a method by which a malicious user attempts
    to make your legitimate users unknowingly submit data that they don't intend to submit.
    Fortunately, CSRF attacks can be prevented by using a CSRF token inside your forms.
    The good news is that, by default, Symfony embeds and validates CSRF tokens automatically
    for you. This means that you can take advantage of the CSRF protection without doing
    anything. In fact, every form in this chapter has taken advantage of the CSRF protection!
    CSRF protection works by adding a hidden eld to your form - called _token by default
    - that contains a value that only you and your user knows. This ensures that the user not some other entity - is submitting the given data. Symfony automatically validates the
    presence and accuracy of this token.
    The _token eld is a hidden eld and will be automatically rendered if you include the
    form_rest() function in your template, which ensures that all un-rendered elds are output.
    The CSRF token can be customized on a form-by-form basis. For example:

    class TaskType extends AbstractType
    {
    // ...

    224

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    public function getDefaultOptions(array $options)
    {
    return array(
    'data_class'
    => 'Acme\TaskBundle\Entity\Task',
    'csrf_protection' => true,
    'csrf_eld_name' => '_token',
    // a unique key to help generate the secret token
    'intention'
    => 'task_item',
    );
    }
    }

    // ...

    To disable CSRF protection, set the csrf_protection option to false. Customizations can also
    be made globally in your project. For more information, see the form conguration reference
    section.
    Ïðèìå÷àíèå: The intention option is optional but greatly enhances the security of the
    generated token by making it dierent for each form.

    5.11.11 Final Thoughts

    You now know all of the building blocks necessary to build complex and functional forms
    for your application. When building forms, keep in mind that the rst goal of a form is to
    translate data from an object (Task) to an HTML form so that the user can modify that
    data. The second goal of a form is to take the data submitted by the user and to re-apply it
    to the object.
    There's still much more to learn about the powerful world of forms, such as how to handle
    le uploads with Doctrine or how to create a form where a dynamic number of sub-forms
    can be added (e.g. a todo list where you can keep adding more elds via Javascript before
    submitting). See the cookbook for these topics. Also, be sure to lean on the eld type reference
    documentation, which includes examples of how to use each eld type and its options.
    5.11.12 Learn more from the Cookbook

    • How to handle File Uploads with Doctrine
    • File Field Reference
    • Creating Custom Field Types
    • How to customize Form Rendering
    5.11. Ôîðìû

    225

    Symfony Documentation, Âûïóñê 2.0

    5.12 Security

    Security is a two-step process whose goal is to prevent a user from accessing a resource that
    he/she should not have access to.
    In the rst step of the process, the security system identies who the user is by requiring the
    user to submit some sort of identication. This is called authentication, and it means that
    the system is trying to nd out who you are.
    Once the system knows who you are, the next step is to determine if you should have access
    to a given resource. This part of the process is called authorization, and it means that the
    system is checking to see if you have privileges to perform a certain action.

    Since the best way to learn is to see an example, let's dive right in.
    Ïðèìå÷àíèå: Symfony's security component is available as a standalone PHP library for
    use inside any PHP project.

    5.12.1 Basic Example: HTTP Authentication

    The security component can be congured via your application conguration. In fact, most
    standard security setups are just matter of using the right conguration. The following
    conguration tells Symfony to secure any URL matching /admin/* and to ask the user for
    credentials using basic HTTP authentication (i.e. the old-school username/password box):
    226

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    • YAML

    # app/cong/security.yml
    security:
    rewalls:
    secured_area:
    pattern: ^/
    anonymous: ~
    http_basic:
    realm: "Secured Demo Area"
    access_control:
    - { path: ^/admin, roles: ROLE_ADMIN }
    providers:
    in_memory:
    users:
    ryan: { password: ryanpass, roles: 'ROLE_USER' }
    admin: { password: kitten, roles: 'ROLE_ADMIN' }
    encoders:
    Symfony\Component\Security\Core\User\User: plaintext
    • XML

    <!-- app/cong/security.xml -->
    <srv:container xmlns="http://symfony.com/schema/dic/security"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:srv="http://symfony.com/schema/dic/services"
    xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services
    <cong>
    <rewall name="secured_area" pattern="^/">
    <anonymous />
    <http-basic realm="Secured Demo Area" />
    </rewall>
    <access-control>
    <rule path="^/admin" role="ROLE_ADMIN" />
    </access-control>
    <provider name="in_memory">
    <user name="ryan" password="ryanpass" roles="ROLE_USER" />
    <user name="admin" password="kitten" roles="ROLE_ADMIN" />
    </provider>
    <encoder class="Symfony\Component\Security\Core\User\User" algorithm="plaintext" />
    </cong>
    5.12. Security

    227

    Symfony Documentation, Âûïóñê 2.0

    </srv:container>
    • PHP

    // app/cong/security.php
    $container->loadFromExtension('security', array(
    'rewalls' => array(
    'secured_area' => array(
    'pattern' => '^/',
    'anonymous' => array(),
    'http_basic' => array(
    'realm' => 'Secured Demo Area',
    ),
    ),
    ),
    'access_control' => array(
    array('path' => '^/admin', 'role' => 'ROLE_ADMIN'),
    ),
    'providers' => array(
    'in_memory' => array(
    'users' => array(
    'ryan' => array('password' => 'ryanpass', 'roles' => 'ROLE_USER'),
    'admin' => array('password' => 'kitten', 'roles' => 'ROLE_ADMIN'),
    ),
    ),
    ),
    'encoders' => array(
    'Symfony\Component\Security\Core\User\User' => 'plaintext',
    ),
    ));
    Ñîâåò: A standard Symfony distribution separates the security conguration into a separate
    le (e.g. app/cong/security.yml). If you don't have a separate security le, you can put the
    conguration directly into your main cong le (e.g. app/cong/cong.yml).
    The end result of this conguration is a fully-functional security system that looks like the
    following:

    • There are two users in the system (ryan and admin);
    • Users authenticate themselves via the basic HTTP authentication prompt;
    • Any URL matching /admin/* is secured, and only the admin user can access it;
    • All URLs not matching /admin/* are accessible by all users (and the user is never
    prompted to login).

    228

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    Let's look briey at how security works and how each part of the conguration comes into
    play.
    5.12.2 How Security Works: Authentication and Authorization

    Symfony's security system works by determining who a user is (i.e. authentication) and then
    checking to see if that user should have access to a specic resource or URL.
    Firewalls (Authentication)
    When a user makes a request to a URL that's protected by a rewall, the security system
    is activated. The job of the rewall is to determine whether or not the user needs to be
    authenticated, and if he does, to send a response back to the user initiating the authentication
    process.
    A rewall is activated when the URL of an incoming request matches the congured rewall's
    regular expression pattern cong value. In this example, the pattern (^/) will match every
    incoming request. The fact that the rewall is activated does not mean, however, that the
    HTTP authentication username and password box is displayed for every URL. For example,
    any user can access /foo without being prompted to authenticate.

    5.12. Security

    229

    Symfony Documentation, Âûïóñê 2.0
    This works rst because the rewall allows anonymous users via the anonymous conguration
    parameter. In other words, the rewall doesn't require the user to fully authenticate
    immediately. And because no special role is needed to access /foo (under the access_control
    section), the request can be fullled without ever asking the user to authenticate.
    If you remove the anonymous key, the rewall will always make a user fully authenticate
    immediately.
    Access Controls (Authorization)
    If a user requests /admin/foo, however, the process behaves dierently. This is because of the
    access_control conguration section that says that any URL matching the regular expression
    pattern ^/admin (i.e. /admin or anything matching /admin/*) requires the ROLE_ADMIN
    role. Roles are the basis for most authorization: a user can access /admin/foo only if it has
    the ROLE_ADMIN role.

    Like before, when the user originally makes the request, the rewall doesn't ask for
    any identication. However, as soon as the access control layer denies the user access
    230

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    (because the anonymous user doesn't have the ROLE_ADMIN role), the rewall jumps
    into action and initiates the authentication process. The authentication process depends
    on the authentication mechanism you're using. For example, if you're using the form login
    authentication method, the user will be redirected to the login page. If you're using HTTP
    authentication, the user will be sent an HTTP 401 response so that the user sees the username
    and password box.
    The user now has the opportunity to submit its credentials back to the application. If the
    credentials are valid, the original request can be re-tried.

    In this example, the user ryan successfully authenticates with the rewall. But since ryan
    doesn't have the ROLE_ADMIN role, he's still denied access to /admin/foo. Ultimately, this
    means that the user will see some sort of message indicating that access has been denied.
    Ñîâåò: When Symfony denies the user access, the user sees an error screen and receives a
    403 HTTP status code (Forbidden). You can customize the access denied error screen by
    following the directions in the Error Pages cookbook entry to customize the 403 error page.

    5.12. Security

    231

    Symfony Documentation, Âûïóñê 2.0
    Finally, if the admin user requests /admin/foo, a similar process takes place, except now,
    after being authenticated, the access control layer will let the request pass through:

    The request ow when a user requests a protected resource is straightforward, but incredibly
    exible. As you'll see later, authentication can be handled in any number of ways, including
    via a form login, X.509 certicate, or by authenticating the user via Twitter. Regardless of
    the authentication method, the request ow is always the same:
    1. A user accesses a protected resource;
    2. The application redirects the user to the login form;
    3. The user submits its credentials (e.g. username/password);
    4. The rewall authenticates the user;
    5. The authenticated user re-tries the original request.
    Ïðèìå÷àíèå: The exact process actually depends a little bit on which authentication
    mechanism you're using. For example, when using form login, the user submits its credentials
    to one URL that processes the form (e.g. /login_check) and then is redirected back to
    the originally requested URL (e.g. /admin/foo). But with HTTP authentication, the user
    submits its credentials directly to the original URL (e.g. /admin/foo) and then the page is
    232

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    returned to the user in that same request (i.e. no redirect).
    These types of idiosyncrasies shouldn't cause you any problems, but they're good to keep in
    mind.

    Ñîâåò: You'll also learn later how anything can be secured in Symfony2, including specic
    controllers, objects, or even PHP methods.

    5.12.3 Using a Traditional Login Form

    So far, you've seen how to blanket your application beneath a rewall and then protect
    access to certain areas with roles. By using HTTP Authentication, you can eortlessly tap
    into the native username/password box oered by all browsers. However, Symfony supports
    many authentication mechanisms out of the box. For details on all of them, see the Security
    Conguration Reference.
    In this section, you'll enhance this process by allowing the user to authenticate via a
    traditional HTML login form.
    First, enable form login under your rewall:

    • YAML

    # app/cong/cong.yml
    security:
    rewalls:
    secured_area:
    pattern: ^/
    anonymous: ~
    form_login:
    login_path: /login
    check_path: /login_check
    • XML

    <!-- app/cong/cong.xml -->
    <srv:container xmlns="http://symfony.com/schema/dic/security"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:srv="http://symfony.com/schema/dic/services"
    xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services
    <cong>
    <rewall name="secured_area" pattern="^/">
    <anonymous />
    <form-login login_path="/login" check_path="/login_check" />
    5.12. Security

    233

    Symfony Documentation, Âûïóñê 2.0

    </rewall>
    </cong>
    </srv:container>
    • PHP

    // app/cong/cong.php
    $container->loadFromExtension('security', array(
    'rewalls' => array(
    'secured_area' => array(
    'pattern' => '^/',
    'anonymous' => array(),
    'form_login' => array(
    'login_path' => '/login',
    'check_path' => '/login_check',
    ),
    ),
    ),
    ));
    Ñîâåò: If you don't need to customize your login_path or check_path values (the values
    used here are the default values), you can shorten your conguration:

    • YAML

    form_login: ~
    • XML

    <form-login />
    • PHP

    'form_login' => array(),
    Now, when the security system initiates the authentication process, it will redirect the user
    to the login form (/login by default). Implementing this login form visually is your job. First,
    create two routes: one that will display the login form (i.e. /login) and one that will handle
    the login form submission (i.e. /login_check):

    • YAML

    # app/cong/routing.yml
    login:
    pattern: /login
    defaults: { _controller: AcmeSecurityBundle:Security:login }

    234

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    login_check:
    pattern: /login_check
    • XML

    <!-- app/cong/routing.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>

    <routes xmlns="http://symfony.com/schema/routing"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing
    <route id="login" pattern="/login">
    <default key="_controller">AcmeSecurityBundle:Security:login</default>
    </route>
    <route id="login_check" pattern="/login_check" />
    </routes>
    • PHP

    // app/cong/routing.php
    use Symfony\Component\Routing\RouteCollection;
    use Symfony\Component\Routing\Route;
    $collection = new RouteCollection();
    $collection->add('login', new Route('/login', array(
    '_controller' => 'AcmeDemoBundle:Security:login',
    )));
    $collection->add('login_check', new Route('/login_check', array()));
    return $collection;
    Ïðèìå÷àíèå: You will not need to implement a controller for the /login_check URL as the
    rewall will automatically catch and process any form submitted to this URL. It's optional,
    but helpful, to create a route so that you can use it to generate the form submission URL
    in the login template below.
    Notice that the name of the login route isn't important. What's important is that the URL of
    the route (/login) matches the login_path cong value, as that's where the security system
    will redirect users that need to login.
    Next, create the controller that will display the login form:

    // src/Acme/SecurityBundle/Controller/Main;
    namespace Acme\SecurityBundle\Controller;
    5.12. Security

    235

    Symfony Documentation, Âûïóñê 2.0

    use Symfony\Bundle\FrameworkBundle\Controller\Controller;
    use Symfony\Component\Security\Core\SecurityContext;
    class SecurityController extends Controller
    {
    public function loginAction()
    {
    $request = $this->getRequest();
    $session = $request->getSession();
    // get the login error if there is one
    if ($request->attributes->has(SecurityContext::AUTHENTICATION_ERROR)) {
    $error = $request->attributes->get(SecurityContext::AUTHENTICATION_ERROR);
    } else {
    $error = $session->get(SecurityContext::AUTHENTICATION_ERROR);
    }

    }

    }

    return $this->render('AcmeSecurityBundle:Security:login.html.twig', array(
    // last username entered by the user
    'last_username' => $session->get(SecurityContext::LAST_USERNAME),
    'error'
    => $error,
    ));

    Don't let this controller confuse you. As you'll see in a moment, when the user submits the
    form, the security system automatically handles the form submission for you. If the user had
    submitted an invalid username or password, this controller reads the form submission error
    from the security system so that it can be displayed back to the user.
    In other words, your job is to display the login form and any login errors that may have
    occurred, but the security system itself takes care of checking the submitted username and
    password and authenticating the user.
    Finally, create the corresponding template:

    • Twig

    {# src/Acme/SecurityBundle/Resources/views/Security/login.html.twig #}
    {% if error %}
    <div>{{ error.message }}</div>
    {% endif %}
    <form action="{{ path('login_check') }}" method="post">
    <label for="username">Username:</label>
    <input type="text" id="username" name="_username" value="{{ last_username }}" />
    <label for="password">Password:</label>
    236

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    <input type="password" id="password" name="_password" />
    {#
    #}

    If you want to control the URL the user is redirected to on success (more details below)
    <input type="hidden" name="_target_path" value="/account" />

    <input type="submit" name="login" />
    </form>
    • PHP

    <?php // src/Acme/SecurityBundle/Resources/views/Security/login.html.php ?>
    <?php if ($error): ?>
    <div><?php echo $error->getMessage() ?></div>
    <?php endif; ?>
    <form action="<?php echo $view['router']->generate('login_check') ?>" method="post">
    <label for="username">Username:</label>
    <input type="text" id="username" name="_username" value="<?php echo $last_username ?>" />
    <label for="password">Password:</label>
    <input type="password" id="password" name="_password" />
    <!-If you want to control the URL the user is redirected to on success (more details below)
    <input type="hidden" name="_target_path" value="/account" />
    -->
    <input type="submit" name="login" />
    </form>
    Ñîâåò:
    The error variable passed into the template is an instance of
    Symfony\Component\Security\Core\Exception\AuthenticationException. It may contain
    more information - or even sensitive information - about the authentication failure, so use
    it wisely!
    The form has very few requirements. First, by submitting the form to /login_check (via the
    login_check route), the security system will intercept the form submission and process the
    form for you automatically. Second, the security system expects the submitted elds to be
    called _username and _password (these eld names can be congured).
    And that's it! When you submit the form, the security system will automatically check the
    user's credentials and either authenticate the user or send the user back to the login form
    where the error can be displayed.

    5.12. Security

    237

    Symfony Documentation, Âûïóñê 2.0
    Let's review the whole process:
    1. The user tries to access a resource that is protected;
    2. The rewall initiates the authentication process by redirecting the user to the login
    form (/login);
    3. The /login page renders login form via the route and controller created in this example;
    4. The user submits the login form to /login_check;
    5. The security system intercepts the request, checks the user's submitted credentials,
    authenticates the user if they are correct, and sends the user back to the login form if
    they are not.
    By default, if the submitted credentials are correct, the user will be redirected to the original
    page that was requested (e.g. /admin/foo). If the user originally went straight to the login
    page, he'll be redirected to the homepage. This can be highly customized, allowing you to,
    for example, redirect the user to a specic URL.
    For more details on this and how to customize the form login process in general, see How to
    customize your Form Login.

    238

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    Avoid Common Pitfalls
    When setting up your login form, watch out for a few common pitfalls.
    1. Create the correct routes
    First, be sure that you've dened the /login and /login_check routes correctly and that
    they correspond to the login_path and check_path cong values. A misconguration
    here can mean that you're redirected to a 404 page instead of the login page, or that
    submitting the login form does nothing (you just see the login form over and over again).
    2. Be sure the login page isn't secure
    Also, be sure that the login page does not require any roles to be viewed. For example, the
    following conguration - which requires the ROLE_ADMIN role for all URLs (including
    the /login URL), will cause a redirect loop:
    • YAML

    access_control:
    - { path: ^/, roles: ROLE_ADMIN }
    • XML

    <access-control>
    <rule path="^/" role="ROLE_ADMIN" />
    </access-control>
    • PHP

    'access_control' => array(
    array('path' => '^/', 'role' => 'ROLE_ADMIN'),
    ),
    Removing the access control on the /login URL xes the problem:
    • YAML

    access_control:
    - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
    - { path: ^/, roles: ROLE_ADMIN }
    • XML

    <access-control>
    <rule path="^/login" role="IS_AUTHENTICATED_ANONYMOUSLY" />
    <rule path="^/" role="ROLE_ADMIN" />
    </access-control>
    • PHP

    'access_control' => array(
    array('path' => '^/login', 'role' => 'IS_AUTHENTICATED_ANONYMOUSLY'),
    array('path' => '^/', 'role' => 'ROLE_ADMIN'),
    ),
    Also, if your rewall does not allow for anonymous users, you'll need to create a special
    rewall that allows anonymous users for the login page:
    • YAML
    5.12. Security
    rewalls:

    login_rewall:
    pattern: ^/login$

    239

    Symfony Documentation, Âûïóñê 2.0
    5.12.4 Authorization

    The rst step in security is always authentication: the process of verifying who the user
    is. With Symfony, authentication can be done in any way - via a form login, basic HTTP
    Authentication, or even via Facebook.
    Once the user has been authenticated, authorization begins. Authorization provides a
    standard and powerful way to decide if a user can access any resource (a URL, a model
    object, a method call, ...). This works by assigning specic roles to each user, and then
    requiring dierent roles for dierent resources.
    The process of authorization has two dierent sides:
    1. The user has a specic set of roles;
    2. A resource requires a specic role in order to be accessed.
    In this section, you'll focus on how to secure dierent resources (e.g. URLs, method calls,
    etc) with dierent roles. Later, you'll learn more about how roles are created and assigned
    to users.
    Securing Specic URL Patterns
    The most basic way to secure part of your application is to secure an entire URL pattern.
    You've seen this already in the rst example of this chapter, where anything matching the
    regular expression pattern ^/admin requires the ROLE_ADMIN role.
    You can dene as many URL patterns as you need - each is a regular expression.

    • YAML

    # app/cong/cong.yml
    security:
    # ...
    access_control:
    - { path: ^/admin/users, roles: ROLE_SUPER_ADMIN }
    - { path: ^/admin, roles: ROLE_ADMIN }
    • XML

    <!-- app/cong/cong.xml -->
    <cong>
    <!-- ... -->
    <rule path="^/admin/users" role="ROLE_SUPER_ADMIN" />
    <rule path="^/admin" role="ROLE_ADMIN" />
    </cong>
    • PHP

    240

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    // app/cong/cong.php
    $container->loadFromExtension('security', array(
    // ...
    'access_control' => array(
    array('path' => '^/admin/users', 'role' => 'ROLE_SUPER_ADMIN'),
    array('path' => '^/admin', 'role' => 'ROLE_ADMIN'),
    ),
    ));
    Ñîâåò: Prepending the path with ^ ensures that only URLs beginning with the pattern
    are matched. For example, a path of simply /admin (without the ^) would correctly match
    /admin/foo but would also match URLs like /foo/admin.
    For each incoming request, Symfony2 tries to nd a matching access control rule (the rst one
    wins). If the user isn't authenticated yet, the authentication process is initiated (i.e. the user is
    given a chance to login). However, if the user is authenticated but doesn't have the required
    role, an Symfony\Component\Security\Core\Exception\AccessDeniedException exception
    is thrown, which you can handle and turn into a nice access denied error page for the user.
    See Êàê ñîçäàòü ñîáñòâåííûå ñòðàíèöû îøèáîê for more information.
    Since Symfony uses the rst access control rule it matches, a URL like /admin/users/new
    will match the rst rule and require only the ROLE_SUPER_ADMIN role. Any URL like
    /admin/blog will match the second rule and require ROLE_ADMIN.
    Securing by IP
    Certain situations may arise when you may need to restrict access to a given route based on
    IP. This is particularly relevant in the case of Edge Side Includes (ESI), for example, which
    utilize a route named _internal. When ESI is used, the _internal route is required by the
    gateway cache to enable dierent caching options for subsections within a given page. This
    route comes with the ^/_internal prex by default in the standard edition (assuming you've
    uncommented those lines from the routing le).
    Here is an example of how you might secure this route from outside access:

    • YAML

    # app/cong/security.yml
    security:
    # ...
    access_control:
    - { path: ^/_internal, roles: IS_AUTHENTICATED_ANONYMOUSLY, ip: 127.0.0.1 }
    • XML

    5.12. Security

    241

    Symfony Documentation, Âûïóñê 2.0

    <access-control>
    <rule path="^/_internal" role="IS_AUTHENTICATED_ANONYMOUSLY" ip="127.0.0.1" />
    </access-control>
    • PHP

    'access_control' => array(
    array('path' => '^/_internal', 'role' => 'IS_AUTHENTICATED_ANONYMOUSLY', 'ip' => '127.0.0
    ),
    Securing by Channel
    Much like securing based on IP, requiring the use of SSL is as simple as adding a new
    access_control entry:

    • YAML

    # app/cong/security.yml
    security:
    # ...
    access_control:
    - { path: ^/cart/checkout, roles: IS_AUTHENTICATED_ANONYMOUSLY, requires_channel: http
    • XML

    <access-control>
    <rule path="^/cart/checkout" role="IS_AUTHENTICATED_ANONYMOUSLY" requires_channel: h
    </access-control>
    • PHP

    'access_control' => array(
    array('path' => '^/cart/checkout', 'role' => 'IS_AUTHENTICATED_ANONYMOUSLY', 'requires_ch
    ),
    Securing a Controller
    Protecting your application based on URL patterns is easy, but may not be ne-grained
    enough in certain cases. When necessary, you can easily force authorization from inside a
    controller:

    use Symfony\Component\Security\Core\Exception\AccessDeniedException
    // ...
    public function helloAction($name)
    {
    242

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    if (false === $this->get('security.context')->isGranted('ROLE_ADMIN')) {
    throw new AccessDeniedException();
    }
    }

    // ...

    You can also choose to install and use the optional JMSSecurityExtraBundle, which can
    secure your controller using annotations:

    use JMS\SecurityExtraBundle\Annotation\Secure;
    /**
    * @Secure(roles="ROLE_ADMIN")
    */
    public function helloAction($name)
    {
    // ...
    }
    For more information, see the JMSSecurityExtraBundle documentation. If you're using
    Symfony's Standard Distribution, this bundle is available by default. If not, you can easily
    download and install it.
    Securing other Services
    In fact, anything in Symfony can be protected using a strategy similar to the one seen in the
    previous section. For example, suppose you have a service (i.e. a PHP class) whose job is to
    send emails from one user to another. You can restrict use of this class - no matter where
    it's being used from - to users that have a specic role.
    For more information on how you can use the security component to secure dierent
    services and methods in your application, see How to secure any Service or Method in your
    Application.
    Access Control Lists (ACLs): Securing Individual Database Objects
    Imagine you are designing a blog system where your users can comment on your posts. Now,
    you want a user to be able to edit his own comments, but not those of other users. Also, as
    the admin user, you yourself want to be able to edit all comments.
    The security component comes with an optional access control list (ACL) system that you
    can use when you need to control access to individual instances of an object in your system.
    Without ACL, you can secure your system so that only certain users can edit blog comments
    in general. But with ACL, you can restrict or allow access on a comment-by-comment basis.
    5.12. Security

    243

    Symfony Documentation, Âûïóñê 2.0
    For more information, see the cookbook article: Access Control Lists (ACLs).
    5.12.5 Users

    In the previous sections, you learned how you can protect dierent resources by requiring a
    set of roles for a resource. In this section we'll explore the other side of authorization: users.
    Where do Users come from? (User Providers)
    During authentication, the user submits a set of credentials (usually a username and
    password). The job of the authentication system is to match those credentials against some
    pool of users. So where does this list of users come from?
    In Symfony2, users can come from anywhere - a conguration le, a database table, a web
    service, or anything else you can dream up. Anything that provides one or more users to
    the authentication system is known as a user provider. Symfony2 comes standard with the
    two most common user providers: one that loads users from a conguration le and one that
    loads users from a database table.

    Specifying Users in a Conguration File
    The easiest way to specify your users is directly in a conguration le. In fact, you've seen
    this already in the example in this chapter.

    • YAML

    # app/cong/cong.yml
    security:
    # ...
    providers:
    default_provider:
    users:
    ryan: { password: ryanpass, roles: 'ROLE_USER' }
    admin: { password: kitten, roles: 'ROLE_ADMIN' }
    • XML

    <!-- app/cong/cong.xml -->
    <cong>
    <!-- ... -->
    <provider name="default_provider">
    <user name="ryan" password="ryanpass" roles="ROLE_USER" />
    <user name="admin" password="kitten" roles="ROLE_ADMIN" />
    </provider>
    </cong>
    244

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    • PHP

    // app/cong/cong.php
    $container->loadFromExtension('security', array(
    // ...
    'providers' => array(
    'default_provider' => array(
    'users' => array(
    'ryan' => array('password' => 'ryanpass', 'roles' => 'ROLE_USER'),
    'admin' => array('password' => 'kitten', 'roles' => 'ROLE_ADMIN'),
    ),
    ),
    ),
    ));
    This user provider is called the in-memory user provider, since the users aren't
    stored anywhere in a database. The actual user object is provided by Symfony
    (Symfony\Component\Security\Core\User\User).
    Ñîâåò: Any user provider can load users directly from conguration by specifying the users
    conguration parameter and listing the users beneath it.
    Îñòîðîæíî: If your username is completely numeric (e.g. 77) or contains a dash (e.g.
    user-name), you should use that alternative syntax when specifying users in YAML:

    users:
    - { name: 77, password: pass, roles: 'ROLE_USER' }
    - { name: user-name, password: pass, roles: 'ROLE_USER' }
    For smaller sites, this method is quick and easy to setup. For more complex systems, you'll
    want to load your users from the database.

    Loading Users from the Database
    If you'd like to load your users via the Doctrine ORM, you can easily do this by creating a
    User class and conguring the entity provider.
    With this approach, you'll rst create your own User class, which will be stored in the
    database.

    // src/Acme/UserBundle/Entity/User.php
    namespace Acme\UserBundle\Entity;
    use Symfony\Component\Security\Core\User\UserInterface;
    use Doctrine\ORM\Mapping as ORM;
    5.12. Security

    245

    Symfony Documentation, Âûïóñê 2.0

    /**
    * @ORM\Entity
    */
    class User implements UserInterface
    {
    /**
    * @ORM\Column(type="string", length="255")
    */
    protected $username;
    }

    // ...

    As far as the security system is concerned, the only requirement for your custom user class
    is that it implements the Symfony\Component\Security\Core\User\UserInterface interface.
    This means that your concept of a user can be anything, as long as it implements this
    interface.
    Ïðèìå÷àíèå: The user object will be serialized and saved in the session during requests,
    therefore it is recommended that you implement the Serializable interface in your user object.
    This is especially important if your User class has a parent class with private properties.
    Next, congure an entity user provider, and point it to your User class:

    • YAML

    # app/cong/security.yml
    security:
    providers:
    main:
    entity: { class: Acme\UserBundle\Entity\User, property: username }
    • XML

    <!-- app/cong/security.xml -->
    <cong>
    <provider name="main">
    <entity class="Acme\UserBundle\Entity\User" property="username" />
    </provider>
    </cong>
    • PHP

    // app/cong/security.php
    $container->loadFromExtension('security', array(
    'providers' => array(
    246

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    ));

    ),

    'main' => array(
    'entity' => array('class' => 'Acme\UserBundle\Entity\User', 'property' => 'username'),
    ),

    With the introduction of this new provider, the authentication system will attempt to load
    a User object from the database by using the username eld of that class.
    Ïðèìå÷àíèå: This example is just meant to show you the basic idea behind the entity
    provider. For a full working example, see How to load Security Users from the Database (the
    entity Provider).
    For more information on creating your own custom provider (e.g. if you needed to load users
    via a web service), see How to create a custom User Provider.
    Encoding the User's Password
    So far, for simplicity, all the examples have stored the users' passwords in plain text (whether
    those users are stored in a conguration le or in a database somewhere). Of course, in a real
    application, you'll want to encode your users' passwords for security reasons. This is easily
    accomplished by mapping your User class to one of several built-in encoders. For example,
    to store your users in memory, but obscure their passwords via sha1, do the following:

    • YAML

    # app/cong/cong.yml
    security:
    # ...
    providers:
    in_memory:
    users:
    ryan: { password: bb87a29949f3a1ee0559f8a57357487151281386, roles: 'ROLE_USER' }
    admin: { password: 74913f5cd5f61ec0bcfdb775414c2fb3d161b620, roles: 'ROLE_ADMIN' }
    encoders:
    Symfony\Component\Security\Core\User\User:
    algorithm: sha1
    iterations: 1
    encode_as_base64: false
    • XML

    <!-- app/cong/cong.xml -->
    <cong>
    5.12. Security

    247

    Symfony Documentation, Âûïóñê 2.0

    <!-- ... -->
    <provider name="in_memory">
    <user name="ryan" password="bb87a29949f3a1ee0559f8a57357487151281386" roles="ROLE_USER
    <user name="admin" password="74913f5cd5f61ec0bcfdb775414c2fb3d161b620" roles="ROLE_ADM
    </provider>

    <encoder class="Symfony\Component\Security\Core\User\User" algorithm="sha1" iterations="1" enc
    </cong>
    • PHP

    // app/cong/cong.php
    $container->loadFromExtension('security', array(
    // ...
    'providers' => array(
    'in_memory' => array(
    'users' => array(
    'ryan' => array('password' => 'bb87a29949f3a1ee0559f8a57357487151281386', 'roles' => 'ROL
    'admin' => array('password' => '74913f5cd5f61ec0bcfdb775414c2fb3d161b620', 'roles' => 'RO
    ),
    ),
    ),
    'encoders' => array(
    'Symfony\Component\Security\Core\User\User' => array(
    'algorithm'
    => 'sha1',
    'iterations'
    => 1,
    'encode_as_base64' => false,
    ),
    ),
    ));
    By setting the iterations to 1 and the encode_as_base64 to false, the password is simply
    run through the sha1 algorithm one time and without any extra encoding. You can now
    calculate the hashed password either programmatically (e.g. hash('sha1', 'ryanpass')) or via
    some online tool like functions-online.com
    If you're creating your users dynamically (and storing them in a database), you can use even
    tougher hashing algorithms and then rely on an actual password encoder object to help you
    encode passwords. For example, suppose your User object is Acme\UserBundle\Entity\User
    (like in the above example). First, congure the encoder for that user:

    • YAML

    # app/cong/cong.yml
    security:
    # ...
    encoders:
    248

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    Acme\UserBundle\Entity\User: sha512
    • XML

    <!-- app/cong/cong.xml -->
    <cong>
    <!-- ... -->
    <encoder class="Acme\UserBundle\Entity\User" algorithm="sha512" />
    </cong>
    • PHP

    // app/cong/cong.php
    $container->loadFromExtension('security', array(
    // ...

    ));

    'encoders' => array(
    'Acme\UserBundle\Entity\User' => 'sha512',
    ),

    In this case, you're using the stronger sha512 algorithm. Also, since you've simply specied
    the algorithm (sha512) as a string, the system will default to hashing your password 5000
    times in a row and then encoding it as base64. In other words, the password has been
    greatly obfuscated so that the hashed password can't be decoded (i.e. you can't determine
    the password from the hashed password).
    If you have some sort of registration form for users, you'll need to be able to determine the
    hashed password so that you can set it on your user. No matter what algorithm you congure
    for your user object, the hashed password can always be determined in the following way
    from a controller:

    $factory = $this->get('security.encoder_factory');
    $user = new Acme\UserBundle\Entity\User();
    $encoder = $factory->getEncoder($user);
    $password = $encoder->encodePassword('ryanpass', $user->getSalt());
    $user->setPassword($password);
    Retrieving the User Object
    After authentication, the User object of the current user can be accessed via the
    security.context service. From inside a controller, this will look like:

    5.12. Security

    249

    Symfony Documentation, Âûïóñê 2.0

    public function indexAction()
    {
    $user = $this->get('security.context')->getToken()->getUser();
    }
    Ïðèìå÷àíèå:
    Anonymous users are technically authenticated, meaning that the
    isAuthenticated() method of an anonymous user object will return true. To check if your
    user is actually authenticated, check for the IS_AUTHENTICATED_FULLY role.

    Using Multiple User Providers
    Each authentication mechanism (e.g. HTTP Authentication, form login, etc) uses exactly
    one user provider, and will use the rst declared user provider by default. But what if you
    want to specify a few users via conguration and the rest of your users in the database? This
    is possible by creating a new provider that chains the two together:

    • YAML

    # app/cong/security.yml
    security:
    providers:
    chain_provider:
    providers: [in_memory, user_db]
    in_memory:
    users:
    foo: { password: test }
    user_db:
    entity: { class: Acme\UserBundle\Entity\User, property: username }
    • XML

    <!-- app/cong/cong.xml -->
    <cong>
    <provider name="chain_provider">
    <provider>in_memory</provider>
    <provider>user_db</provider>
    </provider>
    <provider name="in_memory">
    <user name="foo" password="test" />
    </provider>
    <provider name="user_db">
    <entity class="Acme\UserBundle\Entity\User" property="username" />
    </provider>
    </cong>

    250

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    • PHP

    // app/cong/cong.php
    $container->loadFromExtension('security', array(
    'providers' => array(
    'chain_provider' => array(
    'providers' => array('in_memory', 'user_db'),
    ),
    'in_memory' => array(
    'users' => array(
    'foo' => array('password' => 'test'),
    ),
    ),
    'user_db' => array(
    'entity' => array('class' => 'Acme\UserBundle\Entity\User', 'property' => 'username'),
    ),
    ),
    ));
    Now, all authentication mechanisms will use the chain_provider, since it's the rst specied.
    The chain_provider will, in turn, try to load the user from both the in_memory and user_db
    providers.
    Ñîâåò: If you have no reasons to separate your in_memory users from your user_db users,
    you can accomplish this even more easily by combining the two sources into a single provider:

    • YAML

    # app/cong/security.yml
    security:
    providers:
    main_provider:
    users:
    foo: { password: test }
    entity: { class: Acme\UserBundle\Entity\User, property: username }
    • XML

    <!-- app/cong/cong.xml -->
    <cong>
    <provider name=="main_provider">
    <user name="foo" password="test" />
    <entity class="Acme\UserBundle\Entity\User" property="username" />
    </provider>
    </cong>
    • PHP

    5.12. Security

    251

    Symfony Documentation, Âûïóñê 2.0

    // app/cong/cong.php
    $container->loadFromExtension('security', array(
    'providers' => array(
    'main_provider' => array(
    'users' => array(
    'foo' => array('password' => 'test'),
    ),
    'entity' => array('class' => 'Acme\UserBundle\Entity\User', 'property' => 'username'),
    ),
    ),
    ));
    You can also congure the rewall or individual authentication mechanisms to use a specic
    provider. Again, unless a provider is specied explicitly, the rst provider is always used:

    • YAML

    # app/cong/cong.yml
    security:
    rewalls:
    secured_area:
    # ...
    provider: user_db
    http_basic:
    realm: "Secured Demo Area"
    provider: in_memory
    form_login: ~
    • XML

    <!-- app/cong/cong.xml -->
    <cong>
    <rewall name="secured_area" pattern="^/" provider="user_db">
    <!-- ... -->
    <http-basic realm="Secured Demo Area" provider="in_memory" />
    <form-login />
    </rewall>
    </cong>
    • PHP

    // app/cong/cong.php
    $container->loadFromExtension('security', array(
    'rewalls' => array(
    'secured_area' => array(
    // ...
    'provider' => 'user_db',
    252

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    ));

    ),

    ),

    'http_basic' => array(
    // ...
    'provider' => 'in_memory',
    ),
    'form_login' => array(),

    In this example, if a user tries to login via HTTP authentication, the authentication system
    will use the in_memory user provider. But if the user tries to login via the form login, the
    user_db provider will be used (since it's the default for the rewall as a whole).
    For more information about user provider and rewall conguration, see the Security
    Conguration Reference.
    5.12.6 Roles

    The idea of a role is key to the authorization process. Each user is assigned a set of roles
    and then each resource requires one or more roles. If the user has the required roles, access
    is granted. Otherwise access is denied.
    Roles are pretty simple, and are basically strings that you can invent and use as needed
    (though roles are objects internally). For example, if you need to start limiting access
    to the blog admin section of your website, you could protect that section using a
    ROLE_BLOG_ADMIN role. This role doesn't need to be dened anywhere - you can just
    start using it.
    Ïðèìå÷àíèå: All roles must begin with the ROLE_ prex to be managed by Symfony2.
    If you dene your own roles with a dedicated Role class (more advanced), don't use the
    ROLE_ prex.

    Hierarchical Roles
    Instead of associating many roles to users, you can dene role inheritance rules by creating
    a role hierarchy:

    • YAML

    # app/cong/security.yml
    security:
    role_hierarchy:
    ROLE_ADMIN:
    ROLE_USER
    ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
    5.12. Security

    253

    Symfony Documentation, Âûïóñê 2.0

    • XML

    <!-- app/cong/security.xml -->
    <cong>
    <role id="ROLE_ADMIN">ROLE_USER</role>
    <role id="ROLE_SUPER_ADMIN">ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH</role>
    </cong>
    • PHP

    // app/cong/security.php
    $container->loadFromExtension('security', array(
    'role_hierarchy' => array(
    'ROLE_ADMIN'
    => 'ROLE_USER',
    'ROLE_SUPER_ADMIN' => array('ROLE_ADMIN', 'ROLE_ALLOWED_TO_SWITCH'),
    ),
    ));
    In the above conguration, users with ROLE_ADMIN role will also have the
    ROLE_USER role. The ROLE_SUPER_ADMIN role has ROLE_ADMIN,
    ROLE_ALLOWED_TO_SWITCH and ROLE_USER (inherited from ROLE_ADMIN).
    5.12.7 Logging Out

    Usually, you'll also want your users to be able to log out. Fortunately, the rewall can handle
    this automatically for you when you activate the logout cong parameter:

    • YAML

    # app/cong/cong.yml
    security:
    rewalls:
    secured_area:
    # ...
    logout:
    path: /logout
    target: /
    # ...
    • XML

    <!-- app/cong/cong.xml -->
    <cong>
    <rewall name="secured_area" pattern="^/">
    <!-- ... -->
    <logout path="/logout" target="/" />
    </rewall>
    254

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    <!-- ... -->
    </cong>
    • PHP

    // app/cong/cong.php
    $container->loadFromExtension('security', array(
    'rewalls' => array(
    'secured_area' => array(
    // ...
    'logout' => array('path' => 'logout', 'target' => '/'),
    ),
    ),
    // ...
    ));
    Once this is congured under your rewall, sending a user to /logout (or whatever you
    congure the path to be), will un-authenticate the current user. The user will then be sent
    to the homepage (the value dened by the target parameter). Both the path and target cong
    parameters default to what's specied here. In other words, unless you need to customize
    them, you can omit them entirely and shorten your conguration:

    • YAML

    logout: ~
    • XML

    <logout />
    • PHP

    'logout' => array(),
    Note that you will not need to implement a controller for the /logout URL as the rewall
    takes care of everything. You may, however, want to create a route so that you can use it to
    generate the URL:

    • YAML

    # app/cong/routing.yml
    logout:
    pattern: /logout
    • XML

    <!-- app/cong/routing.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>

    5.12. Security

    255

    Symfony Documentation, Âûïóñê 2.0

    <routes xmlns="http://symfony.com/schema/routing"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing
    <route id="logout" pattern="/logout" />
    </routes>
    • PHP

    // app/cong/routing.php
    use Symfony\Component\Routing\RouteCollection;
    use Symfony\Component\Routing\Route;
    $collection = new RouteCollection();
    $collection->add('logout', new Route('/logout', array()));
    return $collection;
    Once the user has been logged out, he will be redirected to whatever path is dened by the
    target parameter above (e.g. the homepage). For more information on conguring the logout,
    see the Security Conguration Reference.
    5.12.8 Access Control in Templates

    If you want to check if the current user has a role inside a template, use the built-in helper
    function:

    • Twig

    {% if is_granted('ROLE_ADMIN') %}
    <a href="...">Delete</a>
    {% endif %}
    • PHP

    <?php if ($view['security']->isGranted('ROLE_ADMIN')): ?>
    <a href="...">Delete</a>
    <?php endif; ?>
    Ïðèìå÷àíèå: If you use this function and are not at a URL where there is a rewall active,
    an exception will be thrown. Again, it's almost always a good idea to have a main rewall
    that covers all URLs (as has been shown in this chapter).

    256

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    5.12.9 Access Control in Controllers

    If you want to check if the current user has a role in your controller, use the isGranted
    method of the security context:

    public function indexAction()
    {
    // show dierent content to admin users
    if($this->get('security.context')->isGranted('ADMIN')) {
    // Load admin content here
    }
    // load other regular content here
    }
    Ïðèìå÷àíèå: A rewall must be active or an exception will be thrown when the isGranted
    method is called. See the note above about templates for more details.

    5.12.10 Impersonating a User

    Sometimes, it's useful to be able to switch from one user to another without having to logout
    and login again (for instance when you are debugging or trying to understand a bug a user
    sees that you can't reproduce). This can be easily done by activating the switch_user rewall
    listener:

    • YAML

    # app/cong/security.yml
    security:
    rewalls:
    main:
    # ...
    switch_user: true
    • XML

    <!-- app/cong/security.xml -->
    <cong>
    <rewall>
    <!-- ... -->
    <switch-user />
    </rewall>
    </cong>
    • PHP

    5.12. Security

    257

    Symfony Documentation, Âûïóñê 2.0

    // app/cong/security.php
    $container->loadFromExtension('security', array(
    'rewalls' => array(
    'main'=> array(
    // ...
    'switch_user' => true
    ),
    ),
    ));
    To switch to another user, just add a query string with the _switch_user parameter and
    the username as the value to the current URL:
    http://example.com/somewhere?_switch_user=thomas
    To switch back to the original user, use the special _exit username:
    http://example.com/somewhere?_switch_user=_exit
    Of course, this feature needs to be made available to a small group of users. By default,
    access is restricted to users having the ROLE_ALLOWED_TO_SWITCH role. The name
    of this role can be modied via the role setting. For extra security, you can also change the
    query parameter name via the parameter setting:

    • YAML

    # app/cong/security.yml
    security:
    rewalls:
    main:
    // ...
    switch_user: { role: ROLE_ADMIN, parameter: _want_to_be_this_user }
    • XML

    <!-- app/cong/security.xml -->
    <cong>
    <rewall>
    <!-- ... -->
    <switch-user role="ROLE_ADMIN" parameter="_want_to_be_this_user" />
    </rewall>
    </cong>
    • PHP

    // app/cong/security.php
    $container->loadFromExtension('security', array(
    'rewalls' => array(
    'main'=> array(
    258

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    ));

    ),

    ),

    // ...
    'switch_user' => array('role' => 'ROLE_ADMIN', 'parameter' => '_want_to_be_this_user'),

    5.12.11 Stateless Authentication

    By default, Symfony2 relies on a cookie (the Session) to persist the security context of the
    user. But if you use certicates or HTTP authentication for instance, persistence is not
    needed as credentials are available for each request. In that case, and if you don't need to
    store anything else between requests, you can activate the stateless authentication (which
    means that no cookie will be ever created by Symfony2):

    • YAML

    # app/cong/security.yml
    security:
    rewalls:
    main:
    http_basic: ~
    stateless: true
    • XML

    <!-- app/cong/security.xml -->
    <cong>
    <rewall stateless="true">
    <http-basic />
    </rewall>
    </cong>
    • PHP

    // app/cong/security.php
    $container->loadFromExtension('security', array(
    'rewalls' => array(
    'main' => array('http_basic' => array(), 'stateless' => true),
    ),
    ));
    Ïðèìå÷àíèå: If you use a form login, Symfony2 will create a cookie even if you set stateless
    to true.

    5.12. Security

    259

    Symfony Documentation, Âûïóñê 2.0
    5.12.12 Final Words

    Security can be a deep and complex issue to solve correctly in your application.
    Fortunately, Symfony's security component follows a well-proven security model based
    around authentication and authorization. Authentication, which always happens rst, is
    handled by a rewall whose job is to determine the identity of the user through several
    dierent methods (e.g. HTTP authentication, login form, etc). In the cookbook, you'll
    nd examples of other methods for handling authentication, including how to implement
    a remember me cookie functionality.
    Once a user is authenticated, the authorization layer can determine whether or not the user
    should have access to a specic resource. Most commonly, roles are applied to URLs, classes
    or methods and if the current user doesn't have that role, access is denied. The authorization
    layer, however, is much deeper, and follows a system of voting so that multiple parties can
    determine if the current user should have access to a given resource. Find out more about
    this and other topics in the cookbook.
    5.12.13 Learn more from the Cookbook

    • Forcing HTTP/HTTPS
    • Blacklist users by IP address with a custom voter
    • Access Control Lists (ACLs)
    • How to add Remember Me Login Functionality

    5.13 HTTP Cache

    The nature of rich web applications means that they're dynamic. No matter how ecient
    your application, each request will always contain more overhead than serving a static le.
    And for most Web applications, that's ne. Symfony2 is lightning fast, and unless you're
    doing some serious heavy-lifting, each request will come back quickly without putting too
    much stress on your server.
    But as your site grows, that overhead can become a problem. The processing that's normally
    performed on every request should be done only once. This is exactly what caching aims to
    accomplish.
    5.13.1 Caching on the Shoulders of Giants

    The most eective way to improve performance of an application is to cache the full output
    of a page and then bypass the application entirely on each subsequent request. Of course,
    260

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    this isn't always possible for highly dynamic websites, or is it? In this chapter, we'll show you
    how the Symfony2 cache system works and why we think this is the best possible approach.
    The Symfony2 cache system is dierent because it relies on the simplicity and power of
    the HTTP cache as dened in the HTTP specication. Instead of reinventing a caching
    methodology, Symfony2 embraces the standard that denes basic communication on the
    Web. Once you understand the fundamental HTTP validation and expiration caching models,
    you'll be ready to master the Symfony2 cache system.
    For the purposes of learning how to cache with Symfony2, we'll cover the subject in four
    steps:

    • Step 1: A gateway cache, or reverse proxy, is an independent layer that sits in front
    of your application. The reverse proxy caches responses as they're returned from
    your application and answers requests with cached responses before they hit your
    application. Symfony2 provides its own reverse proxy, but any reverse proxy can be
    used.
    • Step 2: HTTP cache headers are used to communicate with the gateway cache and
    any other caches between your application and the client. Symfony2 provides sensible
    defaults and a powerful interface for interacting with the cache headers.
    • Step 3: HTTP expiration and validation are the two models used for determining
    whether cached content is fresh (can be reused from the cache) or stale (should be
    regenerated by the application).
    • Step 4: Edge Side Includes (ESI) allow HTTP cache to be used to cache page fragments
    (even nested fragments) independently. With ESI, you can even cache an entire page
    for 60 minutes, but an embedded sidebar for only 5 minutes.
    Since caching with HTTP isn't unique to Symfony, many articles already exist on the topic. If
    you're new to HTTP caching, we highly recommend Ryan Tomayko's article Things Caches
    Do. Another in-depth resource is Mark Nottingham's Cache Tutorial.
    5.13.2 Caching with a Gateway Cache

    When caching with HTTP, the cache is separated from your application entirely and sits
    between your application and the client making the request.
    The job of the cache is to accept requests from the client and pass them back to your
    application. The cache will also receive responses back from your application and forward
    them on to the client. The cache is the middle-man of the request-response communication
    between the client and your application.
    Along the way, the cache will store each response that is deemed cacheable (See
    Introduction to HTTP Caching). If the same resource is requested again, the cache sends
    the cached response to the client, ignoring your application entirely.

    5.13. HTTP Cache

    261

    Symfony Documentation, Âûïóñê 2.0
    This type of cache is known as a HTTP gateway cache and many exist such as Varnish,
    Squid in reverse proxy mode, and the Symfony2 reverse proxy.
    Types of Caches
    But a gateway cache isn't the only type of cache. In fact, the HTTP cache headers sent by
    your application are consumed and interpreted by up to three dierent types of caches:

    • Browser caches: Every browser comes with its own local cache that is mainly useful
    for when you hit back or for images and other assets. The browser cache is a private
    cache as cached resources aren't shared with anyone else.
    • Proxy caches: A proxy is a shared cache as many people can be behind a single one.
    It's usually installed by large corporations and ISPs to reduce latency and network
    trac.
    • Gateway caches: Like a proxy, it's also a shared cache but on the server side. Installed
    by network administrators, it makes websites more scalable, reliable and performant.
    Ñîâåò: Gateway caches are sometimes referred to as reverse proxy caches, surrogate caches,
    or even HTTP accelerators.

    Ïðèìå÷àíèå: The signicance of private versus shared caches will become more obvious as
    we talk about caching responses containing content that is specic to exactly one user (e.g.
    account information).
    Each response from your application will likely go through one or both of the rst two cache
    types. These caches are outside of your control but follow the HTTP cache directions set in
    the response.
    Symfony2 Reverse Proxy
    Symfony2 comes with a reverse proxy (also called a gateway cache) written in PHP. Enable
    it and cacheable responses from your application will start to be cached right away. Installing
    it is just as easy. Each new Symfony2 application comes with a pre-congured caching kernel
    (AppCache) that wraps the default one (AppKernel). The caching Kernel is the reverse
    proxy.
    To enable caching, modify the code of a front controller to use the caching kernel:

    // web/app.php
    require_once __DIR__.'/../app/bootstrap.php.cache';
    require_once __DIR__.'/../app/AppKernel.php';
    262

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    require_once __DIR__.'/../app/AppCache.php';
    use Symfony\Component\HttpFoundation\Request;
    $kernel = new AppKernel('prod', false);
    $kernel->loadClassCache();
    // wrap the default AppKernel with the AppCache one
    $kernel = new AppCache($kernel);
    $kernel->handle(Request::createFromGlobals())->send();
    The caching kernel will immediately act as a reverse proxy - caching responses from your
    application and returning them to the client.
    Ñîâåò: The cache kernel has a special getLog() method that returns a string representation
    of what happened in the cache layer. In the development environment, use it to debug and
    validate your cache strategy:

    error_log($kernel->getLog());
    The AppCache object has a sensible default conguration, but it can be nely tuned via a
    set of options you can set by overriding the getOptions() method:

    // app/AppCache.php
    class AppCache extends Cache
    {
    protected function getOptions()
    {
    return array(
    'debug'
    => false,
    'default_ttl'
    => 0,
    'private_headers'
    => array('Authorization', 'Cookie'),
    'allow_reload'
    => false,
    'allow_revalidate'
    => false,
    'stale_while_revalidate' => 2,
    'stale_if_error'
    => 60,
    );
    }
    }
    Ñîâåò: Unless overridden in getOptions(), the debug option will be set to automatically be
    the debug value of the wrapped AppKernel.
    Here is a list of the main options:

    • default_ttl: The number of seconds that a cache entry should be considered fresh when
    5.13. HTTP Cache

    263

    Symfony Documentation, Âûïóñê 2.0
    no explicit freshness information is provided in a response. Explicit Cache-Control or
    Expires headers override this value (default: 0);

    • private_headers: Set of request headers that trigger private Cache-Control behavior
    on responses that don't explicitly state whether the response is public or private via a
    Cache-Control directive. (default: Authorization and Cookie);
    • allow_reload: Species whether the client can force a cache reload by including a
    Cache-Control no-cache directive in the request. Set it to true for compliance with
    RFC 2616 (default: false);
    • allow_revalidate: Species whether the client can force a cache revalidate by including
    a Cache-Control max-age=0 directive in the request. Set it to true for compliance
    with RFC 2616 (default: false);
    • stale_while_revalidate: Species the default number of seconds (the granularity is
    the second as the Response TTL precision is a second) during which the cache
    can immediately return a stale response while it revalidates it in the background
    (default: 2); this setting is overridden by the stale-while-revalidate HTTP CacheControl extension (see RFC 5861);
    • stale_if_error: Species the default number of seconds (the granularity is the second)
    during which the cache can serve a stale response when an error is encountered (default:
    60). This setting is overridden by the stale-if-error HTTP Cache-Control extension (see
    RFC 5861).
    If debug is true, Symfony2 automatically adds a X-Symfony-Cache header to the response
    containing useful information about cache hits and misses.
    Changing from one Reverse Proxy to Another
    The Symfony2 reverse proxy is a great tool to use when developing your website or when
    you deploy your website to a shared host where you cannot install anything beyond PHP
    code. But being written in PHP, it cannot be as fast as a proxy written in C. That's
    why we highly recommend you to use Varnish or Squid on your production servers if
    possible. The good news is that the switch from one proxy server to another is easy and
    transparent as no code modication is needed in your application. Start easy with the
    Symfony2 reverse proxy and upgrade later to Varnish when your trac increases.
    For more information on using Varnish with Symfony2, see the How to use Varnish
    cookbook chapter.

    Ïðèìå÷àíèå: The performance of the Symfony2 reverse proxy is independent of the
    complexity of the application. That's because the application kernel is only booted when
    the request needs to be forwarded to it.

    264

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    5.13.3 Introduction to HTTP Caching

    To take advantage of the available cache layers, your application must be able to communicate
    which responses are cacheable and the rules that govern when/how that cache should become
    stale. This is done by setting HTTP cache headers on the response.
    Ñîâåò: Keep in mind that HTTP is nothing more than the language (a simple text
    language) that web clients (e.g. browsers) and web servers use to communicate with each
    other. When we talk about HTTP caching, we're talking about the part of that language
    that allows clients and servers to exchange information related to caching.
    HTTP species four response cache headers that we're concerned with:

    • Cache-Control
    • Expires
    • ETag
    • Last-Modied
    The most important and versatile header is the Cache-Control header, which is actually a
    collection of various cache information.
    Ïðèìå÷àíèå: Each of the headers will be explained in full detail in the HTTP Expiration
    and Validation section.

    The Cache-Control Header
    The Cache-Control header is unique in that it contains not one, but various pieces of
    information about the cacheability of a response. Each piece of information is separated
    by a comma:
    Cache-Control: private, max-age=0, must-revalidate
    Cache-Control: max-age=3600, must-revalidate
    Symfony provides an abstraction around the Cache-Control header to make its creation more
    manageable:

    $response = new Response();
    // mark the response as either public or private
    $response->setPublic();
    $response->setPrivate();
    // set the private or shared max age
    5.13. HTTP Cache

    265

    Symfony Documentation, Âûïóñê 2.0

    $response->setMaxAge(600);
    $response->setSharedMaxAge(600);
    // set a custom Cache-Control directive
    $response->headers->addCacheControlDirective('must-revalidate', true);
    Public vs Private Responses
    Both gateway and proxy caches are considered shared caches as the cached content is
    shared by more than one user. If a user-specic response were ever mistakenly stored by a
    shared cache, it might be returned later to any number of dierent users. Imagine if your
    account information were cached and then returned to every subsequent user who asked for
    their account page!
    To handle this situation, every response may be set to be public or private:

    • public: Indicates that the response may be cached by both private and shared caches;
    • private: Indicates that all or part of the response message is intended for a single user
    and must not be cached by a shared cache.
    Symfony conservatively defaults each response to be private. To take advantage of shared
    caches (like the Symfony2 reverse proxy), the response will need to be explicitly set as public.
    Safe Methods
    HTTP caching only works for safe HTTP methods (like GET and HEAD). Being safe means
    that you never change the application's state on the server when serving the request (you
    can of course log information, cache data, etc). This has two very reasonable consequences:

    • You should never change the state of your application when responding to a GET or
    HEAD request. Even if you don't use a gateway cache, the presence of proxy caches
    mean that any GET or HEAD request may or may not actually hit your server.
    • Don't expect PUT, POST or DELETE methods to cache. These methods are meant to
    be used when mutating the state of your application (e.g. deleting a blog post). Caching
    them would prevent certain requests from hitting and mutating your application.
    Caching Rules and Defaults
    HTTP 1.1 allows caching anything by default unless there is an explicit Cache-Control
    header. In practice, most caches do nothing when requests have a cookie, an authorization
    header, use a non-safe method (i.e. PUT, POST, DELETE), or when responses have a
    redirect status code.

    266

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    Symfony2 automatically sets a sensible and conservative Cache-Control header when none
    is set by the developer by following these rules:

    • If no cache header is dened (Cache-Control, Expires, ETag or Last-Modied), CacheControl is set to no-cache, meaning that the response will not be cached;
    • If Cache-Control is empty (but one of the other cache headers is present), its value is
    set to private, must-revalidate;
    • But if at least one Cache-Control directive is set, and no `public' or private directives
    have been explicitly added, Symfony2 adds the private directive automatically (except
    when s-maxage is set).
    5.13.4 HTTP Expiration and Validation

    The HTTP specication denes two caching models:

    • With the expiration model, you simply specify how long a response should be considered
    fresh by including a Cache-Control and/or an Expires header. Caches that understand
    expiration will not make the same request until the cached version reaches its expiration
    time and becomes stale.
    • When pages are really dynamic (i.e. their representation changes often), the validation
    model model is often necessary. With this model, the cache stores the response, but
    asks the server on each request whether or not the cached response is still valid. The
    application uses a unique response identier (the Etag header) and/or a timestamp
    (the Last-Modied header) to check if the page has changed since being cached.
    The goal of both models is to never generate the same response twice by relying on a cache
    to store and return fresh responses.
    Reading the HTTP Specication
    The HTTP specication denes a simple but powerful language in which clients and
    servers can communicate. As a web developer, the request-response model of the
    specication dominates our work. Unfortunately, the actual specication document RFC 2616 - can be dicult to read.
    There is an on-going eort (HTTP Bis) to rewrite the RFC 2616. It does not describe
    a new version of HTTP, but mostly claries the original HTTP specication. The
    organization is also improved as the specication is split into seven parts; everything
    related to HTTP caching can be found in two dedicated parts (P4 - Conditional Requests
    and P6 - Caching: Browser and intermediary caches).
    As a web developer, we strongly urge you to read the specication. Its clarity and power
    - even more than ten years after its creation - is invaluable. Don't be put-o by the
    appearance of the spec - its contents are much more beautiful than its cover.

    5.13. HTTP Cache

    267

    Symfony Documentation, Âûïóñê 2.0
    Expiration
    The expiration model is the more ecient and straightforward of the two caching models
    and should be used whenever possible. When a response is cached with an expiration, the
    cache will store the response and return it directly without hitting the application until it
    expires.
    The expiration model can be accomplished using one of two, nearly identical, HTTP headers:
    Expires or Cache-Control.
    Expiration with the Expires Header
    According to the HTTP specication, the Expires header eld gives the date/time after
    which the response is considered stale. The Expires header can be set with the setExpires()
    Response method. It takes a DateTime instance as an argument:

    $date = new DateTime();
    $date->modify('+600 seconds');
    $response->setExpires($date);
    The resulting HTTP header will look like this:

    Expires: Thu, 01 Mar 2011 16:00:00 GMT
    Ïðèìå÷àíèå: The setExpires() method automatically converts the date to the GMT
    timezone as required by the specication.
    The Expires header suers from two limitations. First, the clocks on the Web server and the
    cache (e.g. the browser) must be synchronized. Then, the specication states that HTTP/1.1
    servers should not send Expires dates more than one year in the future.
    Expiration with the Cache-Control Header
    Because of the Expires header limitations, most of the time, you should use the Cache-Control
    header instead. Recall that the Cache-Control header is used to specify many dierent cache
    directives. For expiration, there are two directives, max-age and s-maxage. The rst one is
    used by all caches, whereas the second one is only taken into account by shared caches:

    // Sets the number of seconds after which the response
    // should no longer be considered fresh
    $response->setMaxAge(600);

    268

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    // Same as above but only for shared caches
    $response->setSharedMaxAge(600);
    The Cache-Control header would take on the following format (it may have additional
    directives):

    Cache-Control: max-age=600, s-maxage=600
    Validation
    When a resource needs to be updated as soon as a change is made to the underlying data,
    the expiration model falls short. With the expiration model, the application won't be asked
    to return the updated response until the cache nally becomes stale.
    The validation model addresses this issue. Under this model, the cache continues to store
    responses. The dierence is that, for each request, the cache asks the application whether
    or not the cached response is still valid. If the cache is still valid, your application should
    return a 304 status code and no content. This tells the cache that it's ok to return the cached
    response.
    Under this model, you mainly save bandwidth as the representation is not sent twice to the
    same client (a 304 response is sent instead). But if you design your application carefully, you
    might be able to get the bare minimum data needed to send a 304 response and save CPU
    also (see below for an implementation example).
    Ñîâåò: The 304 status code means Not Modied. It's important because with this status
    code do not contain the actual content being requested. Instead, the response is simply a
    light-weight set of directions that tell cache that it should use its stored version.
    Like with expiration, there are two dierent HTTP headers that can be used to implement
    the validation model: ETag and Last-Modied.
    Validation with the ETag Header
    The ETag header is a string header (called the entity-tag) that uniquely identies one
    representation of the target resource. It's entirely generated and set by your application so
    that you can tell, for example, if the /about resource that's stored by the cache is up-to-date
    with what your application would return. An ETag is like a ngerprint and is used to quickly
    compare if two dierent versions of a resource are equivalent. Like ngerprints, each ETag
    must be unique across all representations of the same resource.
    Let's walk through a simple implementation that generates the ETag as the md5 of the
    content:

    5.13. HTTP Cache

    269

    Symfony Documentation, Âûïóñê 2.0

    public function indexAction()
    {
    $response = $this->render('MyBundle:Main:index.html.twig');
    $response->setETag(md5($response->getContent()));
    $response->isNotModied($this->getRequest());
    }

    return $response;

    The Response::isNotModied() method compares the ETag sent with the Request with the
    one set on the Response. If the two match, the method automatically sets the Response
    status code to 304.
    This algorithm is simple enough and very generic, but you need to create the whole Response
    before being able to compute the ETag, which is sub-optimal. In other words, it saves on
    bandwidth, but not CPU cycles.
    In the Optimizing your Code with Validation section, we'll show how validation can be used
    more intelligently to determine the validity of a cache without doing so much work.
    Ñîâåò: Symfony2 also supports weak ETags by passing true as the second argument to the
    :method:`Symfony\\Component\\HttpFoundation\\Response::setETag` method.

    Validation with the Last-Modied Header
    The Last-Modied header is the second form of validation. According to the HTTP
    specication, The Last-Modied header eld indicates the date and time at which the origin
    server believes the representation was last modied. In other words, the application decides
    whether or not the cached content has been updated based on whether or not it's been
    updated since the response was cached.
    For instance, you can use the latest update date for all the objects needed to compute the
    resource representation as the value for the Last-Modied header value:

    public function showAction($articleSlug)
    {
    // ...
    $articleDate = new \DateTime($article->getUpdatedAt());
    $authorDate = new \DateTime($author->getUpdatedAt());
    $date = $authorDate > $articleDate ? $authorDate : $articleDate;
    $response->setLastModied($date);
    $response->isNotModied($this->getRequest());
    270

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    }

    return $response;

    The Response::isNotModied() method compares the If-Modied-Since header sent by the
    request with the Last-Modied header set on the response. If they are equivalent, the
    Response will be set to a 304 status code.
    Ïðèìå÷àíèå: The If-Modied-Since request header equals the Last-Modied header of the
    last response sent to the client for the particular resource. This is how the client and server
    communicate with each other and decide whether or not the resource has been updated since
    it was cached.

    Optimizing your Code with Validation
    The main goal of any caching strategy is to lighten the load on the application. Put
    another way, the less you do in your application to return a 304 response, the better.
    The Response::isNotModied() method does exactly that by exposing a simple and ecient
    pattern:

    public function showAction($articleSlug)
    {
    // Get the minimum information to compute
    // the ETag or the Last-Modied value
    // (based on the Request, data is retrieved from
    // a database or a key-value store for instance)
    $article = // ...
    // create a Response with a ETag and/or a Last-Modied header
    $response = new Response();
    $response->setETag($article->computeETag());
    $response->setLastModied($article->getPublishedAt());
    // Check that the Response is not modied for the given Request
    if ($response->isNotModied($this->getRequest())) {
    // return the 304 Response immediately
    return $response;
    } else {
    // do more work here - like retrieving more data
    $comments = // ...
    // or render a template with the $response you've already started
    return $this->render(
    'MyBundle:MyController:article.html.twig',
    array('article' => $article, 'comments' => $comments),
    5.13. HTTP Cache

    271

    Symfony Documentation, Âûïóñê 2.0

    }

    }

    );

    $response

    When the Response is not modied, the isNotModied() automatically
    sets
    the
    response
    status
    code
    to
    304,
    removes
    the
    content,
    and
    removes some headers that must not be present for 304 responses (see
    :method:`Symfony\\Component\\HttpFoundation\\Response::setNotModied`).
    Varying the Response
    So far, we've assumed that each URI has exactly one representation of the target resource.
    By default, HTTP caching is done by using the URI of the resource as the cache key. If
    two people request the same URI of a cacheable resource, the second person will receive the
    cached version.
    Sometimes this isn't enough and dierent versions of the same URI need to be cached
    based on one or more request header values. For instance, if you compress pages when the
    client supports it, any given URI has two representations: one when the client supports
    compression, and one when it does not. This determination is done by the value of the
    Accept-Encoding request header.
    In this case, we need the cache to store both a compressed and uncompressed version of the
    response for the particular URI and return them based on the request's Accept-Encoding
    value. This is done by using the Vary response header, which is a comma-separated list of
    dierent headers whose values trigger a dierent representation of the requested resource:

    Vary: Accept-Encoding, User-Agent
    Ñîâåò: This particular Vary header would cache dierent versions of each resource based
    on the URI and the value of the Accept-Encoding and User-Agent request header.
    The Response object oers a clean interface for managing the Vary header:

    // set one vary header
    $response->setVary('Accept-Encoding');
    // set multiple vary headers
    $response->setVary(array('Accept-Encoding', 'User-Agent'));
    The setVary() method takes a header name or an array of header names for which the
    response varies.

    272

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    Expiration and Validation
    You can of course use both validation and expiration within the same Response. As expiration
    wins over validation, you can easily benet from the best of both worlds. In other words, by
    using both expiration and validation, you can instruct the cache to serve the cached content,
    while checking back at some interval (the expiration) to verify that the content is still valid.
    More Response Methods
    The Response class provides many more methods related to the cache. Here are the most
    useful ones:

    // Marks the Response stale
    $response->expire();
    // Force the response to return a proper 304 response with no content
    $response->setNotModied();
    Additionally, most cache-related HTTP headers can be set via the single setCache() method:

    // Set cache settings in one call
    $response->setCache(array(
    'etag'
    => $etag,
    'last_modied' => $date,
    'max_age'
    => 10,
    's_maxage'
    => 10,
    'public'
    => true,
    // 'private' => true,
    ));

    5.13.5 Using Edge Side Includes

    Gateway caches are a great way to make your website perform better. But they have one
    limitation: they can only cache whole pages. If you can't cache whole pages or if parts of
    a page has more dynamic parts, you are out of luck. Fortunately, Symfony2 provides a
    solution for these cases, based on a technology called ESI, or Edge Side Includes. Akama
    wrote this specication almost 10 years ago, and it allows specic parts of a page to have a
    dierent caching strategy than the main page.
    The ESI specication describes tags you can embed in your pages to communicate with the
    gateway cache. Only one tag is implemented in Symfony2, include, as this is the only useful
    one outside of Akama context:

    <html>
    <body>
    5.13. HTTP Cache

    273

    Symfony Documentation, Âûïóñê 2.0

    Some content
    <!-- Embed the content of another page here -->
    <esi:include src="http://..." />
    More content
    </body>
    </html>
    Ïðèìå÷àíèå: Notice from the example that each ESI tag has a fully-qualied URL. An ESI
    tag represents a page fragment that can be fetched via the given URL.
    When a request is handled, the gateway cache fetches the entire page from its cache or
    requests it from the backend application. If the response contains one or more ESI tags,
    these are processed in the same way. In other words, the gateway cache either retrieves
    the included page fragment from its cache or requests the page fragment from the backend
    application again. When all the ESI tags have been resolved, the gateway cache merges each
    into the main page and sends the nal content to the client.
    All of this happens transparently at the gateway cache level (i.e. outside of your application).
    As you'll see, if you choose to take advantage of ESI tags, Symfony2 makes the process of
    including them almost eortless.
    Using ESI in Symfony2
    First, to use ESI, be sure to enable it in your application conguration:

    • YAML

    # app/cong/cong.yml
    framework:
    # ...
    esi: { enabled: true }
    • XML

    <!-- app/cong/cong.xml -->
    <framework:cong ...>
    <!-- ... -->
    <framework:esi enabled="true" />
    </framework:cong>
    • PHP

    274

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    // app/cong/cong.php
    $container->loadFromExtension('framework', array(
    // ...
    'esi' => array('enabled' => true),
    ));
    Now, suppose we have a page that is relatively static, except for a news ticker at the bottom
    of the content. With ESI, we can cache the news ticker independent of the rest of the page.

    public function indexAction()
    {
    $response = $this->render('MyBundle:MyController:index.html.twig');
    $response->setSharedMaxAge(600);
    }

    return $response;

    In this example, we've given the full-page cache a lifetime of ten minutes. Next, let's include
    the news ticker in the template by embedding an action. This is done via the render helper
    (See templating-embedding-controller for more details).
    As the embedded content comes from another page (or controller for that matter), Symfony2
    uses the standard render helper to congure ESI tags:

    • Twig

    {% render '...:news' with {}, {'standalone': true} %}
    • PHP

    <?php echo $view['actions']->render('...:news', array(), array('standalone' => true)) ?>
    By setting standalone to true, you tell Symfony2 that the action should be rendered as an
    ESI tag. You might be wondering why you would want to use a helper instead of just writing
    the ESI tag yourself. That's because using a helper makes your application work even if there
    is no gateway cache installed. Let's see how it works.
    When standalone is false (the default), Symfony2 merges the included page content within
    the main one before sending the response to the client. But when standalone is true, and
    if Symfony2 detects that it's talking to a gateway cache that supports ESI, it generates an
    ESI include tag. But if there is no gateway cache or if it does not support ESI, Symfony2
    will just merge the included page content within the main one as it would have done were
    standalone set to false.
    Ïðèìå÷àíèå: Symfony2 detects if a gateway cache supports ESI via another Akama
    specication that is supported out of the box by the Symfony2 reverse proxy.

    5.13. HTTP Cache

    275

    Symfony Documentation, Âûïóñê 2.0
    The embedded action can now specify its own caching rules, entirely independent of the
    master page.

    public function newsAction()
    {
    // ...
    }

    $response->setSharedMaxAge(60);

    With ESI, the full page cache will be valid for 600 seconds, but the news component cache
    will only last for 60 seconds.
    A requirement of ESI, however, is that the embedded action be accessible via a URL so the
    gateway cache can fetch it independently of the rest of the page. Of course, an action can't
    be accessed via a URL unless it has a route that points to it. Symfony2 takes care of this
    via a generic route and controller. For the ESI include tag to work properly, you must dene
    the _internal route:

    • YAML

    # app/cong/routing.yml
    _internal:
    resource: "@FrameworkBundle/Resources/cong/routing/internal.xml"
    prex: /_internal
    • XML

    <!-- app/cong/routing.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>

    <routes xmlns="http://symfony.com/schema/routing"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing

    <import resource="@FrameworkBundle/Resources/cong/routing/internal.xml" prex="/_internal" />
    </routes>
    • PHP

    // app/cong/routing.php
    use Symfony\Component\Routing\RouteCollection;
    use Symfony\Component\Routing\Route;

    $collection->addCollection($loader->import('@FrameworkBundle/Resources/cong/routing/internal.xml', '
    return $collection;

    276

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    Ñîâåò: Since this route allows all actions to be accessed via a URL, you might want to
    protect it by using the Symfony2 rewall feature (by allowing access to your reverse proxy's
    IP range). See the Securing by IP section of the Security Chapter for more information on
    how to do this.
    One great advantage of this caching strategy is that you can make your application as
    dynamic as needed and at the same time, hit the application as little as possible.
    Ïðèìå÷àíèå: Once you start using ESI, remember to always use the s-maxage directive
    instead of max-age. As the browser only ever receives the aggregated resource, it is not
    aware of the sub-components, and so it will obey the max-age directive and cache the entire
    page. And you don't want that.
    The render helper supports two other useful options:

    • alt: used as the alt attribute on the ESI tag, which allows you to specify an alternative
    URL to be used if the src cannot be found;
    • ignore_errors: if set to true, an onerror attribute will be added to the ESI with a value
    of continue indicating that, in the event of a failure, the gateway cache will simply
    remove the ESI tag silently.
    5.13.6 Cache Invalidation

    There are only two hard things in Computer Science: cache invalidation and
    naming things. Phil Karlton
    You should never need to invalidate cached data because invalidation is already taken into
    account natively in the HTTP cache models. If you use validation, you never need to
    invalidate anything by denition; and if you use expiration and need to invalidate a resource,
    it means that you set the expires date too far away in the future.
    Ïðèìå÷àíèå: It's also because there is no invalidation mechanism that you can use any
    reverse proxy without changing anything in your application code.
    Actually, all reverse proxies provide ways to purge cached data, but you should avoid them
    as much as possible. The most standard way is to purge the cache for a given URL by
    requesting it with the special PURGE HTTP method.
    Here is how you can congure the Symfony2 reverse proxy to support the PURGE HTTP
    method:

    // app/AppCache.php
    class AppCache extends Cache
    5.13. HTTP Cache

    277

    Symfony Documentation, Âûïóñê 2.0

    {

    protected function invalidate(Request $request)
    {
    if ('PURGE' !== $request->getMethod()) {
    return parent::invalidate($request);
    }
    $response = new Response();
    if (!$this->store->purge($request->getUri())) {
    $response->setStatusCode(404, 'Not purged');
    } else {
    $response->setStatusCode(200, 'Purged');
    }

    }

    }

    return $response;

    Îñòîðîæíî: You must protect the PURGE HTTP method somehow to avoid random
    people purging your cached data.

    5.13.7 Summary

    Symfony2 was designed to follow the proven rules of the road: HTTP. Caching is no exception.
    Mastering the Symfony2 cache system means becoming familiar with the HTTP cache
    models and using them eectively. This means that, instead of relying only on Symfony2
    documentation and code examples, you have access to a world of knowledge related to HTTP
    caching and gateway caches such as Varnish.
    5.13.8 Learn more from the Cookbook

    • How to use Varnish to speedup my Website

    5.14 Translations

    The term internationalization refers to the process of abstracting strings and other localespecic pieces out of your application and into a layer where they can be translated and
    converted based on the user's locale (i.e. language and country). For text, this means
    wrapping each with a function capable of translating the text (or message) into the language
    of the user:

    278

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    // text will *always* print out in English
    echo 'Hello World';
    // text can be translated into the end-user's language or default to English
    echo $translator->trans('Hello World');
    Ïðèìå÷àíèå: The term locale refers roughly to the user's language and country. It can be any
    string that your application then uses to manage translations and other format dierences
    (e.g. currency format). We recommended the ISO639-1 language code, an underscore (_),
    then the ISO3166 country code (e.g. fr_FR for French/France).
    In this chapter, we'll learn how to prepare an application to support multiple locales and
    then how to create translations for multiple locales. Overall, the process has several common
    steps:
    1. Enable and congure Symfony's Translation component;
    2. Abstract strings (i.e. messages) by wrapping them in calls to the Translator;
    3. Create translation resources for each supported locale that translate each message in
    the application;
    4. Determine, set and manage the user's locale in the session.
    5.14.1 Conguration

    Translations are handled by a Translator service that uses the user's locale to lookup and
    return translated messages. Before using it, enable the Translator in your conguration:

    • YAML

    # app/cong/cong.yml
    framework:
    translator: { fallback: en }
    • XML

    <!-- app/cong/cong.xml -->
    <framework:cong>
    <framework:translator fallback="en" />
    </framework:cong>
    • PHP

    // app/cong/cong.php
    $container->loadFromExtension('framework', array(
    5.14. Translations

    279

    Symfony Documentation, Âûïóñê 2.0

    ));

    'translator' => array('fallback' => 'en'),

    The fallback option denes the fallback locale when a translation does not exist in the user's
    locale.
    Ñîâåò: When a translation does not exist for a locale, the translator rst tries to nd the
    translation for the language (fr if the locale is fr_FR for instance). If this also fails, it looks
    for a translation using the fallback locale.
    The locale used in translations is the one stored in the user session.
    5.14.2 Basic Translation

    Translation
    of
    text
    is
    done
    through
    the
    translator
    service
    (Symfony\Component\Translation\Translator). To translate a block of text (called
    a message), use the :method:`Symfony\\Component\\Translation\\Translator::trans`
    method. Suppose, for example, that we're translating a simple message from inside a
    controller:

    public function indexAction()
    {
    $t = $this->get('translator')->trans('Symfony2 is great');
    }

    return new Response($t);

    When this code is executed, Symfony2 will attempt to translate the message Symfony2 is
    great based on the locale of the user. For this to work, we need to tell Symfony2 how to
    translate the message via a translation resource, which is a collection of message translations
    for a given locale. This dictionary of translations can be created in several dierent formats,
    XLIFF being the recommended format:

    • XML

    <!-- messages.fr.xli -->
    <?xml version="1.0"?>
    <xli version="1.2" xmlns="urn:oasis:names:tc:xli:document:1.2">
    <le source-language="en" datatype="plaintext" original="le.ext">
    <body>
    <trans-unit id="1">
    <source>Symfony2 is great</source>
    <target>J'aime Symfony2</target>
    </trans-unit>
    </body>
    280

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    </le>
    </xli>
    • PHP

    // messages.fr.php
    return array(
    'Symfony2 is great' => 'J\'aime Symfony2',
    );
    • YAML

    # messages.fr.yml
    Symfony2 is great: J'aime Symfony2
    Now, if the language of the user's locale is French (e.g. fr_FR or fr_BE), the message will
    be translated into J'aime Symfony2.
    The Translation Process
    To actually translate the message, Symfony2 uses a simple process:

    • The locale of the current user, which is stored in the session, is determined;
    • A catalog of translated messages is loaded from translation resources dened for the
    locale (e.g. fr_FR). Messages from the fallback locale are also loaded and added to the
    catalog if they don't already exist. The end result is a large dictionary of translations.
    See Message Catalogues for more details;
    • If the message is located in the catalog, the translation is returned. If not, the translator
    returns the original message.
    When using the trans() method, Symfony2 looks for the exact string inside the appropriate
    message catalog and returns it (if it exists).
    Message Placeholders
    Sometimes, a message containing a variable needs to be translated:

    public function indexAction($name)
    {
    $t = $this->get('translator')->trans('Hello '.$name);
    }

    return new Response($t);

    5.14. Translations

    281

    Symfony Documentation, Âûïóñê 2.0
    However, creating a translation for this string is impossible since the translator will try
    to look up the exact message, including the variable portions (e.g. Hello Ryan or Hello
    Fabien). Instead of writing a translation for every possible iteration of the $name variable,
    we can replace the variable with a placeholder:

    public function indexAction($name)
    {
    $t = $this->get('translator')->trans('Hello %name%', array('%name%' => $name));
    }

    new Response($t);

    Symfony2 will now look for a translation of the raw message (Hello %name%) and then
    replace the placeholders with their values. Creating a translation is done just as before:

    • XML

    <!-- messages.fr.xli -->
    <?xml version="1.0"?>
    <xli version="1.2" xmlns="urn:oasis:names:tc:xli:document:1.2">
    <le source-language="en" datatype="plaintext" original="le.ext">
    <body>
    <trans-unit id="1">
    <source>Hello %name%</source>
    <target>Bonjour %name%</target>
    </trans-unit>
    </body>
    </le>
    </xli>
    • PHP

    // messages.fr.php
    return array(
    'Hello %name%' => 'Bonjour %name%',
    );
    • YAML

    # messages.fr.yml
    'Hello %name%': Hello %name%
    Ïðèìå÷àíèå: The placeholders can take on any form as the full message is reconstructed
    using the PHP strtr function. However, the %var% notation is required when translating in
    Twig templates, and is overall a sensible convention to follow.
    As we've seen, creating a translation is a two-step process:
    282

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    1. Abstract the message that needs to be translated by processing it through the
    Translator.
    2. Create a translation for the message in each locale that you choose to support.
    The second step is done by creating message catalogues that dene the translations for any
    number of dierent locales.
    5.14.3 Message Catalogues

    When a message is translated, Symfony2 compiles a message catalogue for the user's locale
    and looks in it for a translation of the message. A message catalogue is like a dictionary
    of translations for a specic locale. For example, the catalogue for the fr_FR locale might
    contain the following translation:
    Symfony2 is Great => J'aime Symfony2
    It's the responsibility of the developer (or translator) of an internationalized application
    to create these translations. Translations are stored on the lesystem and discovered by
    Symfony, thanks to some conventions.
    Ñîâåò: Each time you create a new translation resource (or install a bundle that includes
    a translation resource), be sure to clear your cache so that Symfony can discover the new
    translation resource:

    php app/console cache:clear

    Translation Locations and Naming Conventions
    Symfony2 looks for message les (i.e. translations) in two locations:

    • For messages found in a bundle, the corresponding message les should live in the
    Resources/translations/ directory of the bundle;
    • To override any bundle translations,
    app/Resources/translations directory.

    place

    message

    les

    in

    the

    The lename of the translations is also important as Symfony2 uses a convention to determine
    details about the translations. Each message le must be named according to the following
    pattern: domain.locale.loader:

    • domain: An optional way to organize messages into groups (e.g. admin, navigation or
    the default messages) - see Using Message Domains;
    • locale: The locale that the translations are for (e.g. en_GB, en, etc);
    • loader: How Symfony2 should load and parse the le (e.g. xli, php or yml).
    5.14. Translations

    283

    Symfony Documentation, Âûïóñê 2.0
    The loader can be the name of any registered loader. By default, Symfony provides the
    following loaders:

    • xli: XLIFF le;
    • php: PHP le;
    • yml: YAML le.
    The choice of which loader to use is entirely up to you and is a matter of taste.
    Ïðèìå÷àíèå: You can also store translations in a database, or any other storage by providing
    a custom class implementing the Symfony\Component\Translation\Loader\LoaderInterface
    interface. See Custom Translation Loaders below to learn how to register custom loaders.

    Creating Translations
    Each le consists of a series of id-translation pairs for the given domain and locale. The id is
    the identier for the individual translation, and can be the message in the main locale (e.g.
    Symfony is great) of your application or a unique identier (e.g. symfony2.great - see the
    sidebar below):

    • XML

    <!-- src/Acme/DemoBundle/Resources/translations/messages.fr.xli -->
    <?xml version="1.0"?>
    <xli version="1.2" xmlns="urn:oasis:names:tc:xli:document:1.2">
    <le source-language="en" datatype="plaintext" original="le.ext">
    <body>
    <trans-unit id="1">
    <source>Symfony2 is great</source>
    <target>J'aime Symfony2</target>
    </trans-unit>
    <trans-unit id="2">
    <source>symfony2.great</source>
    <target>J'aime Symfony2</target>
    </trans-unit>
    </body>
    </le>
    </xli>
    • PHP

    // src/Acme/DemoBundle/Resources/translations/messages.fr.php
    return array(
    'Symfony2 is great' => 'J\'aime Symfony2',

    284

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    );

    'symfony2.great'

    => 'J\'aime Symfony2',

    • YAML

    # src/Acme/DemoBundle/Resources/translations/messages.fr.yml
    Symfony2 is great: J'aime Symfony2
    symfony2.great: J'aime Symfony2
    Symfony2 will discover these les and use them when translating either Symfony2 is great
    or symfony2.great into a French language locale (e.g. fr_FR or fr_BE).

    5.14. Translations

    285

    Symfony Documentation, Âûïóñê 2.0
    Using Real or Keyword Messages
    This example illustrates the two dierent philosophies when creating messages to be
    translated:

    $t = $translator->trans('Symfony2 is great');
    $t = $translator->trans('symfony2.great');
    In the rst method, messages are written in the language of the default locale (English
    in this case). That message is then used as the id when creating translations.
    In the second method, messages are actually keywords that convey the idea of the
    message. The keyword message is then used as the id for any translations. In this
    case, translations must be made for the default locale (i.e. to translate symfony2.great
    to Symfony2 is great).
    The second method is handy because the message key won't need to be changed in every
    translation le if we decide that the message should actually read Symfony2 is really
    great in the default locale.
    The choice of which method to use is entirely up to you, but the keyword format is
    often recommended.
    Additionally, the php and yaml le formats support nested ids to avoid repeating yourself
    if you use keywords instead of real text for your ids:
    • YAML

    symfony2:
    is:
    great: Symfony2 is great
    amazing: Symfony2 is amazing
    has:
    bundles: Symfony2 has bundles
    user:
    login: Login
    • PHP

    return array(
    'symfony2' => array(
    'is' => array(
    'great' => 'Symfony2 is great',
    'amazing' => 'Symfony2 is amazing',
    ),
    'has' => array(
    'bundles' => 'Symfony2 has bundles',
    ),
    ),
    'user' => array(
    'login' => 'Login',
    ),
    );
    The multiple levels are attened into single id/translation pairs by adding a dot (.)
    286
    Ãëàâà 5. Êíèãà
    between every level, therefore the above examples are equivalent to the following:
    • YAML

    symfony2.is.great: Symfony2 is great

    Symfony Documentation, Âûïóñê 2.0
    5.14.4 Using Message Domains

    As we've seen, message les are organized into the dierent locales that they translate. The
    message les can also be organized further into domains. When creating message les, the
    domain is the rst portion of the lename. The default domain is messages. For example,
    suppose that, for organization, translations were split into three dierent domains: messages,
    admin and navigation. The French translation would have the following message les:

    • messages.fr.xli
    • admin.fr.xli
    • navigation.fr.xli
    When translating strings that are not in the default domain (messages), you must specify
    the domain as the third argument of trans():

    $this->get('translator')->trans('Symfony2 is great', array(), 'admin');
    Symfony2 will now look for the message in the admin domain of the user's locale.
    5.14.5 Handling the User's Locale

    The locale of the current user is stored in the session and is accessible via the session service:

    $locale = $this->get('session')->getLocale();
    $this->get('session')->setLocale('en_US');
    Fallback and Default Locale
    If the locale hasn't been set explicitly in the session, the fallback_locale conguration
    parameter will be used by the Translator. The parameter defaults to en (see Conguration).
    Alternatively, you can guarantee that a locale is set on the user's session by dening a
    default_locale for the session service:

    • YAML

    # app/cong/cong.yml
    framework:
    session: { default_locale: en }
    • XML

    <!-- app/cong/cong.xml -->
    <framework:cong>
    5.14. Translations

    287

    Symfony Documentation, Âûïóñê 2.0

    <framework:session default-locale="en" />
    </framework:cong>
    • PHP

    // app/cong/cong.php
    $container->loadFromExtension('framework', array(
    'session' => array('default_locale' => 'en'),
    ));
    The Locale and the URL
    Since the locale of the user is stored in the session, it may be tempting to use the same URL
    to display a resource in many dierent languages based on the user's locale. For example,
    http://www.example.com/contact could show content in English for one user and French for
    another user. Unfortunately, this violates a fundamental rule of the Web: that a particular
    URL returns the same resource regardless of the user. To further muddy the problem, which
    version of the content would be indexed by search engines?
    A better policy is to include the locale in the URL. This is fully-supported by the routing
    system using the special _locale parameter:

    • YAML

    contact:
    pattern: /{_locale}/contact
    defaults: { _controller: AcmeDemoBundle:Contact:index, _locale: en }
    requirements:
    _locale: en|fr|de
    • XML

    <route id="contact" pattern="/{_locale}/contact">
    <default key="_controller">AcmeDemoBundle:Contact:index</default>
    <default key="_locale">en</default>
    <requirement key="_locale">en|fr|de</requirement>
    </route>
    • PHP

    use Symfony\Component\Routing\RouteCollection;
    use Symfony\Component\Routing\Route;
    $collection = new RouteCollection();
    $collection->add('contact', new Route('/{_locale}/contact', array(
    '_controller' => 'AcmeDemoBundle:Contact:index',
    '_locale' => 'en',
    288

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    ), array(
    '_locale'
    )));

    => 'en|fr|de'

    return $collection;
    When using the special _locale parameter in a route, the matched locale will automatically
    be set on the user's session. In other words, if a user visits the URI /fr/contact, the locale fr
    will automatically be set as the locale for the user's session.
    You can now use the user's locale to create routes to other translated pages in your
    application.
    5.14.6 Pluralization

    Message pluralization is a tough topic as the rules can be quite complex. For instance, here
    is the mathematic representation of the Russian pluralization rules:

    (($number % 10 == 1) && ($number % 100 != 11)) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) &&
    As you can see, in Russian, you can have three dierent plural forms, each given an index
    of 0, 1 or 2. For each form, the plural is dierent, and so the translation is also dierent.
    When a translation has dierent forms due to pluralization, you can provide all the forms
    as a string separated by a pipe (|):

    'There is one apple|There are %count% apples'

    To translate pluralized messages, use the :method:`Symfony\\Component\\Translation\\Translator::transC
    method:

    $t = $this->get('translator')->transChoice(
    'There is one apple|There are %count% apples',
    10,
    array('%count%' => 10)
    );
    The second argument (10 in this example), is the number of objects being described and is
    used to determine which translation to use and also to populate the %count% placeholder.
    Based on the given number, the translator chooses the right plural form. In English, most
    words have a singular form when there is exactly one object and a plural form for all other
    numbers (0, 2, 3...). So, if count is 1, the translator will use the rst string (There is one
    apple) as the translation. Otherwise it will use There are %count% apples.
    Here is the French translation:

    5.14. Translations

    289

    Symfony Documentation, Âûïóñê 2.0

    'Il y a %count% pomme|Il y a %count% pommes'
    Even if the string looks similar (it is made of two sub-strings separated by a pipe), the French
    rules are dierent: the rst form (no plural) is used when count is 0 or 1. So, the translator
    will automatically use the rst string (Il y a %count% pomme) when count is 0 or 1.
    Each locale has its own set of rules, with some having as many as six dierent plural forms
    with complex rules behind which numbers map to which plural form. The rules are quite
    simple for English and French, but for Russian, you'd may want a hint to know which rule
    matches which string. To help translators, you can optionally tag each string:

    'one: There is one apple|some: There are %count% apples'
    'none_or_one: Il y a %count% pomme|some: Il y a %count% pommes'
    The tags are really only hints for translators and don't aect the logic used to determine
    which plural form to use. The tags can be any descriptive string that ends with a colon (:).
    The tags also do not need to be the same in the original message as in the translated one.
    Explicit Interval Pluralization
    The easiest way to pluralize a message is to let Symfony2 use internal logic to choose which
    string to use based on a given number. Sometimes, you'll need more control or want a
    dierent translation for specic cases (for 0, or when the count is negative, for example). For
    such cases, you can use explicit math intervals:

    '{0} There is no apples|{1} There is one apple|]1,19] There are %count% apples|[20,Inf] There are many apples'
    The intervals follow the ISO 31-11 notation. The above string species four dierent intervals:
    exactly 0, exactly 1, 2-19, and 20 and higher.
    You can also mix explicit math rules and standard rules. In this case, if the count is not
    matched by a specic interval, the standard rules take eect after removing the explicit rules:

    '{0} There is no apples|[20,Inf] There are many apples|There is one apple|a_few: There are %count% apples'
    For example, for 1 apple, the standard rule There is one apple will be used. For 2-19 apples,
    the second standard rule There are %count% apples will be selected.
    An Symfony\Component\Translation\Interval can represent a nite set of numbers:

    {1,2,3,4}
    Or numbers between two other numbers:

    [1, +Inf[
    ]-1,2[
    290

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    The left delimiter can be [ (inclusive) or ] (exclusive). The right delimiter can be [ (exclusive)
    or ] (inclusive). Beside numbers, you can use -Inf and +Inf for the innite.
    5.14.7 Translations in Templates

    Most of the time, translation occurs in templates. Symfony2 provides native support for both
    Twig and PHP templates.
    Twig Templates
    Symfony2 provides specialized Twig tags (trans and transchoice) to help with message
    translation of static blocks of text:

    {% trans %}Hello %name%{% endtrans %}
    {% transchoice count %}
    {0} There is no apples|{1} There is one apple|]1,Inf] There are %count% apples
    {% endtranschoice %}
    The transchoice tag automatically gets the %count% variable from the current context and
    passes it to the translator. This mechanism only works when you use a placeholder following
    the %var% pattern.
    Ñîâåò: If you need to use the percent character (%) in a string, escape it by doubling it:
    {% trans %}Percent: %percent%%%{% endtrans %}
    You can also specify the message domain and pass some additional variables:

    {% trans with {'%name%': 'Fabien'} from "app" %}Hello %name%{% endtrans %}
    {% transchoice count with {'%name%': 'Fabien'} from "app" %}
    {0} There is no apples|{1} There is one apple|]1,Inf] There are %count% apples
    {% endtranschoice %}
    The trans and transchoice lters can be used to translate variable texts and complex
    expressions:

    {{ message | trans }}
    {{ message | transchoice(5) }}
    {{ message | trans({'%name%': 'Fabien'}, "app") }}
    {{ message | transchoice(5, {'%name%': 'Fabien'}, 'app') }}
    5.14. Translations

    291

    Symfony Documentation, Âûïóñê 2.0

    Ñîâåò: Using the translation tags or lters have the same eect, but with one subtle
    dierence: automatic output escaping is only applied to variables translated using a lter.
    In other words, if you need to be sure that your translated variable is not output escaped,
    you must apply the raw lter after the translation lter:

    {# text translated between tags is never escaped #}
    {% trans %}
    <h3>foo</h3>
    {% endtrans %}
    {% set message = '<h3>foo</h3>' %}
    {# a variable translated via a lter is escaped by default #}
    {{ message | trans | raw }}
    {# but static strings are never escaped #}
    {{ '<h3>foo</h3>' | trans }}

    PHP Templates
    The translator service is accessible in PHP templates through the translator helper:

    <?php echo $view['translator']->trans('Symfony2 is great') ?>
    <?php echo $view['translator']->transChoice(
    '{0} There is no apples|{1} There is one apple|]1,Inf[ There are %count% apples',
    10,
    array('%count%' => 10)
    ) ?>

    5.14.8 Forcing the Translator Locale

    When translating a message, Symfony2 uses the locale from the user's session or the fallback
    locale if necessary. You can also manually specify the locale to use for translation:

    $this->get('translator')->trans(
    'Symfony2 is great',
    array(),
    'messages',
    'fr_FR',
    );
    $this->get('translator')->trans(
    292

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    );

    '{0} There is no apples|{1} There is one apple|]1,Inf[ There are %count% apples',
    10,
    array('%count%' => 10),
    'messages',
    'fr_FR',

    5.14.9 Translating Database Content

    The translation of database content should be handled by Doctrine through the Translatable
    Extension. For more information, see the documentation for that library.
    5.14.10 Summary

    With the Symfony2 Translation component, creating an internationalized application no
    longer needs to be a painful process and boils down to just a few basic steps:

    • Abstract messages in your application by wrapping each in either
    the
    :method:`Symfony\\Component\\Translation\\Translator::trans`
    or
    :method:`Symfony\\Component\\Translation\\Translator::transChoice` methods;
    • Translate each message into multiple locales by creating translation message les.
    Symfony2 discovers and processes each le because its name follows a specic
    convention;
    • Manage the user's locale, which is stored in the session.

    5.15 Service Container

    A modern PHP application is full of objects. One object may facilitate the delivery of
    email messages while another may allow you to persist information into a database. In
    your application, you may create an object that manages your product inventory, or another
    object that processes data from a third-party API. The point is that a modern application
    does many things and is organized into many objects that handle each task.
    In this chapter, we'll talk about a special PHP object in Symfony2 that helps you instantiate,
    organize and retrieve the many objects of your application. This object, called a service
    container, will allow you to standardize and centralize the way objects are constructed in
    your application. The container makes your life easier, is super fast, and emphasizes an
    architecture that promotes reusable and decoupled code. And since all core Symfony2 classes
    use the container, you'll learn how to extend, congure and use any object in Symfony2. In
    large part, the service container is the biggest contributor to the speed and extensibility of
    Symfony2.
    5.15. Service Container

    293

    Symfony Documentation, Âûïóñê 2.0
    Finally, conguring and using the service container is easy. By the end of this chapter, you'll
    be comfortable creating your own objects via the container and customizing objects from any
    third-party bundle. You'll begin writing code that is more reusable, testable and decoupled,
    simply because the service container makes writing good code so easy.
    5.15.1 What is a Service?

    Put simply, a Service is any PHP object that performs some sort of global task. It's a
    purposefully-generic name used in computer science to describe an object that's created for
    a specic purpose (e.g. delivering emails). Each service is used throughout your application
    whenever you need the specic functionality it provides. You don't have to do anything
    special to make a service: simply write a PHP class with some code that accomplishes a
    specic task. Congratulations, you've just created a service!
    Ïðèìå÷àíèå: As a rule, a PHP object is a service if it is used globally in your application.
    A single Mailer service is used globally to send email messages whereas the many Message
    objects that it delivers are not services. Similarly, a Product object is not a service, but an
    object that persists Product objects to a database is a service.
    So what's the big deal then? The advantage of thinking about services is that you begin
    to think about separating each piece of functionality in your application into a series of
    services. Since each service does just one job, you can easily access each service and use
    its functionality wherever you need it. Each service can also be more easily tested and
    congured since it's separated from the other functionality in your application. This idea is
    called service-oriented architecture and is not unique to Symfony2 or even PHP. Structuring
    your application around a set of independent service classes is a well-known and trusted
    object-oriented best-practice. These skills are key to being a good developer in almost any
    language.
    5.15.2 What is a Service Container?

    A Service Container (or dependency injection container) is simply a PHP object that manages
    the instantiation of services (i.e. objects). For example, suppose we have a simple PHP class
    that delivers email messages. Without a service container, we must manually create the
    object whenever we need it:

    use Acme\HelloBundle\Mailer;
    $mailer = new Mailer('sendmail');
    $mailer->send('ryan@foobar.net', ... );
    This is easy enough. The imaginary Mailer class allows us to congure the method used
    to deliver the email messages (e.g. sendmail, smtp, etc). But what if we wanted to use the
    294

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    mailer service somewhere else? We certainly don't want to repeat the mailer conguration
    every time we need to use the Mailer object. What if we needed to change the transport
    from sendmail to smtp everywhere in the application? We'd need to hunt down every place
    we create a Mailer service and change it.
    5.15.3 Creating/Conguring Services in the Container

    A better answer is to let the service container create the Mailer object for you. In order for
    this to work, we must teach the container how to create the Mailer service. This is done via
    conguration, which can be specied in YAML, XML or PHP:

    • YAML

    # app/cong/cong.yml
    services:
    my_mailer:
    class:
    Acme\HelloBundle\Mailer
    arguments: [sendmail]
    • XML

    <!-- app/cong/cong.xml -->
    <services>
    <service id="my_mailer" class="Acme\HelloBundle\Mailer">
    <argument>sendmail</argument>
    </service>
    </services>
    • PHP

    // app/cong/cong.php
    use Symfony\Component\DependencyInjection\Denition;
    $container->setDenition('my_mailer', new Denition(
    'Acme\HelloBundle\Mailer',
    array('sendmail')
    ));
    Ïðèìå÷àíèå: When Symfony2 initializes, it builds the service container using the application
    conguration (app/cong/cong.yml by default). The exact le that's loaded is dictated
    by the AppKernel::registerContainerConguration() method, which loads an environmentspecic conguration le (e.g. cong_dev.yml for the dev environment or cong_prod.yml
    for prod).
    An instance of the Acme\HelloBundle\Mailer object is now available via the service
    container. The container is available in any traditional Symfony2 controller where you can
    5.15. Service Container

    295

    Symfony Documentation, Âûïóñê 2.0
    access the services of the container via the get() shortcut method:

    class HelloController extends Controller
    {
    // ...

    }

    public function sendEmailAction()
    {
    // ...
    $mailer = $this->get('my_mailer');
    $mailer->send('ryan@foobar.net', ... );
    }

    When we ask for the my_mailer service from the container, the container constructs the
    object and returns it. This is another major advantage of using the service container. Namely,
    a service is never constructed until it's needed. If you dene a service and never use it on
    a request, the service is never created. This saves memory and increases the speed of your
    application. This also means that there's very little or no performance hit for dening lots
    of services. Services that are never used are never constructed.
    As an added bonus, the Mailer service is only created once and the same instance is returned
    each time you ask for the service. This is almost always the behavior you'll need (it's more
    exible and powerful), but we'll learn later how you can congure a service that has multiple
    instances.
    5.15.4 Service Parameters

    The creation of new services (i.e. objects) via the container is pretty straightforward.
    Parameters make dening services more organized and exible:

    • YAML

    # app/cong/cong.yml
    parameters:
    my_mailer.class:
    Acme\HelloBundle\Mailer
    my_mailer.transport: sendmail
    services:
    my_mailer:
    class:
    %my_mailer.class%
    arguments: [%my_mailer.transport%]
    • XML

    <!-- app/cong/cong.xml -->
    <parameters>
    296

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    <parameter key="my_mailer.class">Acme\HelloBundle\Mailer</parameter>
    <parameter key="my_mailer.transport">sendmail</parameter>
    </parameters>
    <services>
    <service id="my_mailer" class="%my_mailer.class%">
    <argument>%my_mailer.transport%</argument>
    </service>
    </services>
    • PHP

    // app/cong/cong.php
    use Symfony\Component\DependencyInjection\Denition;
    $container->setParameter('my_mailer.class', 'Acme\HelloBundle\Mailer');
    $container->setParameter('my_mailer.transport', 'sendmail');
    $container->setDenition('my_mailer', new Denition(
    '%my_mailer.class%',
    array('%my_mailer.transport%')
    ));
    The end result is exactly the same as before - the dierence is only in how we dened the
    service. By surrounding the my_mailer.class and my_mailer.transport strings in percent
    (%) signs, the container knows to look for parameters with those names. When the container
    is built, it looks up the value of each parameter and uses it in the service denition.
    The purpose of parameters is to feed information into services. Of course there was nothing
    wrong with dening the service without using any parameters. Parameters, however, have
    several advantages:

    • separation and organization of all service options under a single parameters key;
    • parameter values can be used in multiple service denitions;
    • when creating a service in a bundle (we'll show this shortly), using parameters allows
    the service to be easily customized in your application.
    The choice of using or not using parameters is up to you. High-quality third-party bundles
    will always use parameters as they make the service stored in the container more congurable.
    For the services in your application, however, you may not need the exibility of parameters.
    5.15.5 Importing other Container Conguration Resources

    Ñîâåò: In this section, we'll refer to service conguration les as resources. This is to
    highlight that fact that, while most conguration resources will be les (e.g. YAML, XML,
    5.15. Service Container

    297

    Symfony Documentation, Âûïóñê 2.0
    PHP), Symfony2 is so exible that conguration could be loaded from anywhere (e.g. a
    database or even via an external web service).
    The service container is built using a single conguration resource (app/cong/cong.yml
    by default). All other service conguration (including the core Symfony2 and third-party
    bundle conguration) must be imported from inside this le in one way or another. This
    gives you absolute exibility over the services in your application.
    External service conguration can be imported in two dierent ways. First, we'll talk about
    the method that you'll use most commonly in your application: the imports directive. In
    the following section, we'll introduce the second method, which is the exible and preferred
    method for importing service conguration from third-party bundles.
    Importing Conguration with imports
    So far, we've placed our my_mailer service container denition directly in the application
    conguration le (e.g. app/cong/cong.yml). Of course, since the Mailer class itself lives
    inside the AcmeHelloBundle, it makes more sense to put the my_mailer container denition
    inside the bundle as well.
    First, move the my_mailer container denition into a new container resource le inside
    AcmeHelloBundle. If the Resources or Resources/cong directories don't exist, create them.

    • YAML

    # src/Acme/HelloBundle/Resources/cong/services.yml
    parameters:
    my_mailer.class:
    Acme\HelloBundle\Mailer
    my_mailer.transport: sendmail
    services:
    my_mailer:
    class:
    %my_mailer.class%
    arguments: [%my_mailer.transport%]
    • XML

    <!-- src/Acme/HelloBundle/Resources/cong/services.xml -->
    <parameters>
    <parameter key="my_mailer.class">Acme\HelloBundle\Mailer</parameter>
    <parameter key="my_mailer.transport">sendmail</parameter>
    </parameters>
    <services>
    <service id="my_mailer" class="%my_mailer.class%">
    <argument>%my_mailer.transport%</argument>

    298

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    </service>
    </services>
    • PHP

    // src/Acme/HelloBundle/Resources/cong/services.php
    use Symfony\Component\DependencyInjection\Denition;
    $container->setParameter('my_mailer.class', 'Acme\HelloBundle\Mailer');
    $container->setParameter('my_mailer.transport', 'sendmail');
    $container->setDenition('my_mailer', new Denition(
    '%my_mailer.class%',
    array('%my_mailer.transport%')
    ));
    The denition itself hasn't changed, only its location. Of course the service container doesn't
    know about the new resource le. Fortunately, we can easily import the resource le using
    the imports key in the application conguration.

    • YAML

    # app/cong/cong.yml
    imports:
    hello_bundle:
    resource: @AcmeHelloBundle/Resources/cong/services.yml
    • XML

    <!-- app/cong/cong.xml -->
    <imports>
    <import resource="@AcmeHelloBundle/Resources/cong/services.xml"/>
    </imports>
    • PHP

    // app/cong/cong.php
    $this->import('@AcmeHelloBundle/Resources/cong/services.php');
    The imports directive allows your application to include service container conguration
    resources from any other location (most commonly from bundles). The resource location,
    for les, is the absolute path to the resource le. The special @AcmeHello syntax resolves
    the directory path of the AcmeHelloBundle bundle. This helps you specify the path to the
    resource without worrying later if you move the AcmeHelloBundle to a dierent directory.

    5.15. Service Container

    299

    Symfony Documentation, Âûïóñê 2.0
    Importing Conguration via Container Extensions
    When developing in Symfony2, you'll most commonly use the imports directive to import
    container conguration from the bundles you've created specically for your application.
    Third-party bundle container conguration, including Symfony2 core services, are usually
    loaded using another method that's more exible and easy to congure in your application.
    Here's how it works. Internally, each bundle denes its services very much like we've seen so
    far. Namely, a bundle uses one or more conguration resource les (usually XML) to specify
    the parameters and services for that bundle. However, instead of importing each of these
    resources directly from your application conguration using the imports directive, you can
    simply invoke a service container extension inside the bundle that does the work for you. A
    service container extension is a PHP class created by the bundle author to accomplish two
    things:

    • import all service container resources needed to congure the services for the bundle;
    • provide semantic, straightforward conguration so that the bundle can be congured
    without interacting with the at parameters of the bundle's service container
    conguration.
    In other words, a service container extension congures the services for a bundle on your
    behalf. And as we'll see in a moment, the extension provides a sensible, high-level interface
    for conguring the bundle.
    Take the FrameworkBundle - the core Symfony2 framework bundle - as an example. The
    presence of the following code in your application conguration invokes the service container
    extension inside the FrameworkBundle:

    • YAML

    # app/cong/cong.yml
    framework:
    secret:
    xxxxxxxxxx
    charset:
    UTF-8
    form:
    true
    csrf_protection: true
    router:
    { resource: "%kernel.root_dir%/cong/routing.yml" }
    # ...
    • XML

    <!-- app/cong/cong.xml -->
    <framework:cong charset="UTF-8" secret="xxxxxxxxxx">
    <framework:form />
    <framework:csrf-protection />
    <framework:router resource="%kernel.root_dir%/cong/routing.xml" />
    <!-- ... -->
    </framework>
    300

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    • PHP

    // app/cong/cong.php
    $container->loadFromExtension('framework', array(
    'secret'
    => 'xxxxxxxxxx',
    'charset'
    => 'UTF-8',
    'form'
    => array(),
    'csrf-protection' => array(),
    'router'
    => array('resource' => '%kernel.root_dir%/cong/routing.php'),
    // ...
    ));
    When the conguration is parsed, the container looks for an extension that can handle
    the framework conguration directive. The extension in question, which lives in the
    FrameworkBundle, is invoked and the service conguration for the FrameworkBundle is
    loaded. If you remove the framework key from your application conguration le entirely,
    the core Symfony2 services won't be loaded. The point is that you're in control: the Symfony2
    framework doesn't contain any magic or perform any actions that you don't have control
    over.
    Of course you can do much more than simply activate the service container extension of
    the FrameworkBundle. Each extension allows you to easily customize the bundle, without
    worrying about how the internal services are dened.
    In this case, the extension allows you to customize the charset, error_handler,
    csrf_protection, router conguration and much more. Internally, the FrameworkBundle uses
    the options specied here to dene and congure the services specic to it. The bundle takes
    care of creating all the necessary parameters and services for the service container, while
    still allowing much of the conguration to be easily customized. As an added bonus, most
    service container extensions are also smart enough to perform validation - notifying you of
    options that are missing or the wrong data type.
    When installing or conguring a bundle, see the bundle's documentation for how the services
    for the bundle should be installed and congured. The options available for the core bundles
    can be found inside the Reference Guide.
    Ïðèìå÷àíèå: Natively, the service container only recognizes the parameters, services, and
    imports directives. Any other directives are handled by a service container extension.

    5.15.6 Referencing (Injecting) Services

    So far, our original my_mailer service is simple: it takes just one argument in its constructor,
    which is easily congurable. As you'll see, the real power of the container is realized when
    you need to create a service that depends on one or more other services in the container.

    5.15. Service Container

    301

    Symfony Documentation, Âûïóñê 2.0
    Let's start with an example. Suppose we have a new service, NewsletterManager, that helps
    to manage the preparation and delivery of an email message to a collection of addresses. Of
    course the my_mailer service is already really good at delivering email messages, so we'll
    use it inside NewsletterManager to handle the actual delivery of the messages. This pretend
    class might look something like this:

    namespace Acme\HelloBundle\Newsletter;
    use Acme\HelloBundle\Mailer;
    class NewsletterManager
    {
    protected $mailer;
    public function __construct(Mailer $mailer)
    {
    $this->mailer = $mailer;
    }
    }

    // ...

    Without using the service container, we can create a new NewsletterManager fairly easily
    from inside a controller:

    public function sendNewsletterAction()
    {
    $mailer = $this->get('my_mailer');
    $newsletter = new Acme\HelloBundle\Newsletter\NewsletterManager($mailer);
    // ...
    }
    This approach is ne, but what if we decide later that the NewsletterManager class needs a
    second or third constructor argument? What if we decide to refactor our code and rename
    the class? In both cases, you'd need to nd every place where the NewsletterManager is
    instantiated and modify it. Of course, the service container gives us a much more appealing
    option:

    • YAML

    # src/Acme/HelloBundle/Resources/cong/services.yml
    parameters:
    # ...
    newsletter_manager.class: Acme\HelloBundle\Newsletter\NewsletterManager
    services:
    my_mailer:
    # ...
    302

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    newsletter_manager:
    class: %newsletter_manager.class%
    arguments: [@my_mailer]
    • XML

    <!-- src/Acme/HelloBundle/Resources/cong/services.xml -->
    <parameters>
    <!-- ... -->
    <parameter key="newsletter_manager.class">Acme\HelloBundle\Newsletter\NewsletterManager</par
    </parameters>
    <services>
    <service id="my_mailer" ... >
    <!-- ... -->
    </service>
    <service id="newsletter_manager" class="%newsletter_manager.class%">
    <argument type="service" id="my_mailer"/>
    </service>
    </services>
    • PHP

    // src/Acme/HelloBundle/Resources/cong/services.php
    use Symfony\Component\DependencyInjection\Denition;
    use Symfony\Component\DependencyInjection\Reference;

    // ...
    $container->setParameter('newsletter_manager.class', 'Acme\HelloBundle\Newsletter\NewsletterManager'
    $container->setDenition('my_mailer', ... );
    $container->setDenition('newsletter_manager', new Denition(
    '%newsletter_manager.class%',
    array(new Reference('my_mailer'))
    ));
    In YAML, the special @my_mailer syntax tells the container to look for a service named
    my_mailer and to pass that object into the constructor of NewsletterManager. In this case,
    however, the specied service my_mailer must exist. If it does not, an exception will be
    thrown. You can mark your dependencies as optional - this will be discussed in the next
    section.
    Using references is a very powerful tool that allows you to create independent service classes
    with well-dened dependencies. In this example, the newsletter_manager service needs the
    my_mailer service in order to function. When you dene this dependency in the service
    container, the container takes care of all the work of instantiating the objects.

    5.15. Service Container

    303

    Symfony Documentation, Âûïóñê 2.0
    Optional Dependencies: Setter Injection
    Injecting dependencies into the constructor in this manner is an excellent way of ensuring
    that the dependency is available to use. If you have optional dependencies for a class, then
    setter injection may be a better option. This means injecting the dependency using a
    method call rather than through the constructor. The class would look like this:

    namespace Acme\HelloBundle\Newsletter;
    use Acme\HelloBundle\Mailer;
    class NewsletterManager
    {
    protected $mailer;
    public function setMailer(Mailer $mailer)
    {
    $this->mailer = $mailer;
    }
    }

    // ...

    Injecting the dependency by the setter method just needs a change of syntax:

    • YAML

    # src/Acme/HelloBundle/Resources/cong/services.yml
    parameters:
    # ...
    newsletter_manager.class: Acme\HelloBundle\Newsletter\NewsletterManager
    services:
    my_mailer:
    # ...
    newsletter_manager:
    class: %newsletter_manager.class%
    calls:
    - [ setMailer, [ @my_mailer ] ]
    • XML

    <!-- src/Acme/HelloBundle/Resources/cong/services.xml -->
    <parameters>
    <!-- ... -->
    <parameter key="newsletter_manager.class">Acme\HelloBundle\Newsletter\NewsletterManager</par
    </parameters>

    304

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    <services>
    <service id="my_mailer" ... >
    <!-- ... -->
    </service>
    <service id="newsletter_manager" class="%newsletter_manager.class%">
    <call method="setMailer">
    <argument type="service" id="my_mailer" />
    </call>
    </service>
    </services>
    • PHP

    // src/Acme/HelloBundle/Resources/cong/services.php
    use Symfony\Component\DependencyInjection\Denition;
    use Symfony\Component\DependencyInjection\Reference;

    // ...
    $container->setParameter('newsletter_manager.class', 'Acme\HelloBundle\Newsletter\NewsletterManager'
    $container->setDenition('my_mailer', ... );
    $container->setDenition('newsletter_manager', new Denition(
    '%newsletter_manager.class%'
    ))->addMethodCall('setMailer', array(
    new Reference('my_mailer')
    ));
    Ïðèìå÷àíèå: The approaches presented in this section are called constructor injection
    and setter injection. The Symfony2 service container also supports property injection.

    5.15.7 Making References Optional

    Sometimes, one of your services may have an optional dependency, meaning that the
    dependency is not required for your service to work properly. In the example above, the
    my_mailer service must exist, otherwise an exception will be thrown. By modifying the
    newsletter_manager service denition, you can make this reference optional. The container
    will then inject it if it exists and do nothing if it doesn't:

    • YAML

    # src/Acme/HelloBundle/Resources/cong/services.yml
    parameters:
    # ...
    services:
    5.15. Service Container

    305

    Symfony Documentation, Âûïóñê 2.0

    newsletter_manager:
    class: %newsletter_manager.class%
    arguments: [@?my_mailer]
    • XML

    <!-- src/Acme/HelloBundle/Resources/cong/services.xml -->
    <services>
    <service id="my_mailer" ... >
    <!-- ... -->
    </service>
    <service id="newsletter_manager" class="%newsletter_manager.class%">
    <argument type="service" id="my_mailer" on-invalid="ignore" />
    </service>
    </services>
    • PHP

    // src/Acme/HelloBundle/Resources/cong/services.php
    use Symfony\Component\DependencyInjection\Denition;
    use Symfony\Component\DependencyInjection\Reference;
    use Symfony\Component\DependencyInjection\ContainerInterface;

    // ...
    $container->setParameter('newsletter_manager.class', 'Acme\HelloBundle\Newsletter\NewsletterManager'
    $container->setDenition('my_mailer', ... );
    $container->setDenition('newsletter_manager', new Denition(
    '%newsletter_manager.class%',
    array(new Reference('my_mailer', ContainerInterface::IGNORE_ON_INVALID_REFERENCE))
    ));
    In YAML, the special @? syntax tells the service container that the dependency is optional.
    Of course, the NewsletterManager must also be written to allow for an optional dependency:

    public function __construct(Mailer $mailer = null)
    {
    // ...
    }

    5.15.8 Core Symfony and Third-Party Bundle Services

    Since Symfony2 and all third-party bundles congure and retrieve their services via the
    container, you can easily access them or even use them in your own services. To keep
    things simple, Symfony2 by default does not require that controllers be dened as services.
    306

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    Furthermore Symfony2 injects the entire service container into your controller. For example,
    to handle the storage of information on a user's session, Symfony2 provides a session service,
    which you can access inside a standard controller as follows:

    public function indexAction($bar)
    {
    $session = $this->get('session');
    $session->set('foo', $bar);
    }

    // ...

    In Symfony2, you'll constantly use services provided by the Symfony core or other thirdparty bundles to perform tasks such as rendering templates (templating), sending emails
    (mailer), or accessing information on the request (request).
    We can take this a step further by using these services inside services that you've created
    for your application. Let's modify the NewsletterManager to use the real Symfony2 mailer
    service (instead of the pretend my_mailer). Let's also pass the templating engine service to
    the NewsletterManager so that it can generate the email content via a template:

    namespace Acme\HelloBundle\Newsletter;
    use Symfony\Component\Templating\EngineInterface;
    class NewsletterManager
    {
    protected $mailer;
    protected $templating;
    public function __construct(\Swift_Mailer $mailer, EngineInterface $templating)
    {
    $this->mailer = $mailer;
    $this->templating = $templating;
    }
    }

    // ...

    Conguring the service container is easy:

    • YAML

    services:
    newsletter_manager:
    class: %newsletter_manager.class%
    arguments: [@mailer, @templating]
    5.15. Service Container

    307

    Symfony Documentation, Âûïóñê 2.0

    • XML

    <service id="newsletter_manager" class="%newsletter_manager.class%">
    <argument type="service" id="mailer"/>
    <argument type="service" id="templating"/>
    </service>
    • PHP

    $container->setDenition('newsletter_manager', new Denition(
    '%newsletter_manager.class%',
    array(
    new Reference('mailer'),
    new Reference('templating')
    )
    ));
    The newsletter_manager service now has access to the core mailer and templating services.
    This is a common way to create services specic to your application that leverage the power
    of dierent services within the framework.
    Ñîâåò: Be sure that swiftmailer entry appears in your application conguration. As we
    mentioned in Importing Conguration via Container Extensions, the swiftmailer key invokes
    the service extension from the SwiftmailerBundle, which registers the mailer service.

    5.15.9 Advanced Container Conguration

    As we've seen, dening services inside the container is easy, generally involving a service
    conguration key and a few parameters. However, the container has several other tools
    available that help to tag services for special functionality, create more complex services,
    and perform operations after the container is built.
    Marking Services as public / private
    When dening services, you'll usually want to be able to access these denitions within
    your application code. These services are called public. For example, the doctrine service
    registered with the container when using the DoctrineBundle is a public service as you can
    access it via:

    $doctrine = $container->get('doctrine');
    However, there are use-cases when you don't want a service to be public. This is common
    when a service is only dened because it could be used as an argument for another service.

    308

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    Ïðèìå÷àíèå: If you use a private service as an argument to more than one other service,
    this will result in two dierent instances being used as the instantiation of the private service
    is done inline (e.g. new PrivateFooBar()).
    Simply said: A service will be private when you do not want to access it directly from your
    code.
    Here is an example:

    • YAML

    services:
    foo:
    class: Acme\HelloBundle\Foo
    public: false
    • XML

    <service id="foo" class="Acme\HelloBundle\Foo" public="false" />
    • PHP

    $denition = new Denition('Acme\HelloBundle\Foo');
    $denition->setPublic(false);
    $container->setDenition('foo', $denition);
    Now that the service is private, you cannot call:

    $container->get('foo');
    However, if a service has been marked as private, you can still alias it (see below) to access
    this service (via the alias).
    Ïðèìå÷àíèå: Services are by default public.

    Aliasing
    When using core or third party bundles within your application, you may want to use
    shortcuts to access some services. You can do so by aliasing them and, furthermore, you
    can even alias non-public services.

    • YAML

    services:
    foo:
    class: Acme\HelloBundle\Foo

    5.15. Service Container

    309

    Symfony Documentation, Âûïóñê 2.0

    bar:
    alias: foo
    • XML

    <service id="foo" class="Acme\HelloBundle\Foo"/>
    <service id="bar" alias="foo" />
    • PHP

    $denition = new Denition('Acme\HelloBundle\Foo');
    $container->setDenition('foo', $denition);
    $containerBuilder->setAlias('bar', 'foo');
    This means that when using the container directly, you can access the foo service by asking
    for the bar service like this:

    $container->get('bar'); // Would return the foo service
    Requiring les
    There might be use cases when you need to include another le just before the service itself
    gets loaded. To do so, you can use the le directive.

    • YAML

    services:
    foo:
    class: Acme\HelloBundle\Foo\Bar
    le: %kernel.root_dir%/src/path/to/le/foo.php
    • XML

    <service id="foo" class="Acme\HelloBundle\Foo\Bar">
    <le>%kernel.root_dir%/src/path/to/le/foo.php</le>
    </service>
    • PHP

    $denition = new Denition('Acme\HelloBundle\Foo\Bar');
    $denition->setFile('%kernel.root_dir%/src/path/to/le/foo.php');
    $container->setDenition('foo', $denition);
    Notice that symfony will internally call the PHP function require_once which means that
    your le will be included only once per request.

    310

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    Tags (tags)
    In the same way that a blog post on the Web might be tagged with things such as Symfony
    or PHP, services congured in your container can also be tagged. In the service container,
    a tag implies that the service is meant to be used for a specic purpose. Take the following
    example:

    • YAML

    services:
    foo.twig.extension:
    class: Acme\HelloBundle\Extension\FooExtension
    tags:
    - { name: twig.extension }
    • XML

    <service id="foo.twig.extension" class="Acme\HelloBundle\Extension\FooExtension">
    <tag name="twig.extension" />
    </service>
    • PHP

    $denition = new Denition('Acme\HelloBundle\Extension\FooExtension');
    $denition->addTag('twig.extension');
    $container->setDenition('foo.twig.extension', $denition);
    The twig.extension tag is a special tag that the TwigBundle uses during conguration. By
    giving the service this twig.extension tag, the bundle knows that the foo.twig.extension
    service should be registered as a Twig extension with Twig. In other words, Twig nds all
    services tagged with twig.extension and automatically registers them as extensions.
    Tags, then, are a way to tell Symfony2 or other third-party bundles that your service should
    be registered or used in some special way by the bundle.
    The following is a list of tags available with the core Symfony2 bundles. Each of these has
    a dierent eect on your service and many tags require additional arguments (beyond just
    the name parameter).

    • assetic.lter
    • assetic.templating.php
    • data_collector
    • form.eld_factory.guesser
    • kernel.cache_warmer
    • kernel.event_listener
    • monolog.logger
    5.15. Service Container

    311

    Symfony Documentation, Âûïóñê 2.0

    • routing.loader
    • security.listener.factory
    • security.voter
    • templating.helper
    • twig.extension
    • translation.loader
    • validator.constraint_validator
    5.15.10 Learn more from the Cookbook

    • How to Use a Factory to Create Services
    • How to Manage Common Dependencies with Parent Services
    • How to dene Controllers as Services

    5.16 Performance

    Symfony2 is fast, right out of the box. Of course, if you really need speed, there are many
    ways that you can make Symfony even faster. In this chapter, you'll explore many of the
    most common and powerful ways to make your Symfony application even faster.
    5.16.1 Use a Byte Code Cache (e.g. APC)

    One the best (and easiest) things that you should do to improve your performance is to
    use a byte code cache. The idea of a byte code cache is to remove the need to constantly
    recompile the PHP source code. There are a number of byte code caches available, some of
    which are open source. The most widely used byte code cache is probably APC
    Using a byte code cache really has no downside, and Symfony2 has been architected to
    perform really well in this type of environment.
    Further Optimizations
    Byte code caches usually monitor the source les for changes. This ensures that if the source
    of a le changes, the byte code is recompiled automatically. This is really convenient, but
    obviously adds overhead.

    312

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    For this reason, some byte code caches oer an option to disable these checks. Obviously,
    when disabling these checks, it will be up to the server admin to ensure that the cache is
    cleared whenever any source les change. Otherwise, the updates you've made won't be seen.
    For example, to disable these checks in APC, simply add apc.stat=0 to your php.ini
    conguration.
    5.16.2 Use an Autoloader that caches (e.g. ApcUniversalClassLoader)

    By default, the Symfony2 standard edition uses the UniversalClassLoader in the
    autoloader.php le. This autoloader is easy to use, as it will automatically nd any new
    classes that you've placed in the registered directories.
    Unfortunately, this comes at a cost, as the loader iterates over all congured namespaces to
    nd a particular le, making le_exists calls until it nally nds the le it's looking for.
    The simplest solution is to cache the location of each class after it's located the rst
    time. Symfony comes with a class - ApcUniversalClassLoader - loader that extends the
    UniversalClassLoader and stores the class locations in APC.
    To use this class loader, simply adapt your autoloader.php as follows:

    // app/autoload.php
    require __DIR__.'/../vendor/symfony/src/Symfony/Component/ClassLoader/ApcUniversalClassLoader.php';
    use Symfony\Component\ClassLoader\ApcUniversalClassLoader;
    $loader = new ApcUniversalClassLoader('some caching unique prex');
    // ...
    Ïðèìå÷àíèå: When using the APC autoloader, if you add new classes, they will be found
    automatically and everything will work the same as before (i.e. no reason to clear the
    cache). However, if you change the location of a particular namespace or prex, you'll need
    to ush your APC cache. Otherwise, the autoloader will still be looking at the old location
    for all classes inside that namespace.

    5.16.3 Use Bootstrap Files

    To ensure optimal exibility and code reuse, Symfony2 applications leverage a variety of
    classes and 3rd party components. But loading all of these classes from separate les on
    each request can result in some overhead. To reduce this overhead, the Symfony2 Standard
    Edition provides a script to generate a so-called bootstrap le, consisting of multiple classes
    denitions in a single le. By including this le (which contains a copy of many of the core

    5.16. Performance

    313

    Symfony Documentation, Âûïóñê 2.0
    classes), Symfony no longer needs to include any of the source les containing those classes.
    This will reduce disc IO quite a bit.
    If you're using the Symfony2 Standard Edition, then you're probably already using the
    bootstrap le. To be sure, open your front controller (usually app.php) and check to make
    sure that the following line exists:

    require_once __DIR__.'/../app/bootstrap.php.cache';
    Note that there are two disadvantages when using a bootstrap le:

    • the le needs to be regenerated whenever any of the original sources change (i.e. when
    you update the Symfony2 source or vendor libraries);
    • when debugging, one will need to place break points inside the bootstrap le.
    If you're using Symfony2 Standard Edition, the bootstrap le is automatically rebuilt after
    updating the vendor libraries via the php bin/vendors install command.
    Bootstrap Files and Byte Code Caches
    Even when using a byte code cache, performance will improve when using a bootstrap le
    since there will be less les to monitor for changes. Of course if this feature is disabled in
    the byte code cache (e.g. apc.stat=0 in APC), there is no longer a reason to use a bootstrap
    le.

    5.17 Internals

    Looks like you want to understand how Symfony2 works and how to extend it. That makes
    me very happy! This section is an in-depth explanation of the Symfony2 internals.
    Ïðèìå÷àíèå: You need to read this section only if you want to understand how Symfony2
    works behind the scene, or if you want to extend Symfony2.

    5.17.1 Overview

    The Symfony2 code is made of several independent layers. Each layer is built on top of the
    previous one.
    Ñîâåò: Autoloading is not managed by the framework directly; it's done independently
    with the help of the Symfony\Component\ClassLoader\UniversalClassLoader class and the
    src/autoload.php le. Read the dedicated chapter for more information.
    314

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    HttpFoundation Component
    The deepest level is the :namespace:`Symfony\\Component\\HttpFoundation` component.
    HttpFoundation provides the main objects needed to deal with HTTP. It is an ObjectOriented abstraction of some native PHP functions and variables:

    • The Symfony\Component\HttpFoundation\Request class abstracts the main PHP
    global variables like $_GET, $_POST, $_COOKIE, $_FILES, and $_SERVER;
    • The Symfony\Component\HttpFoundation\Response class abstracts some PHP
    functions like header(), setcookie(), and echo;
    • The
    Symfony\Component\HttpFoundation\Session
    class
    Symfony\Component\HttpFoundation\SessionStorage\SessionStorageInterface
    interface abstract session management session_*() functions.

    and

    HttpKernel Component
    On top of HttpFoundation is the :namespace:`Symfony\\Component\\HttpKernel`
    component. HttpKernel handles the dynamic part of HTTP; it is a thin wrapper on top
    of the Request and Response classes to standardize the way requests are handled. It also
    provides extension points and tools that makes it the ideal starting point to create a Web
    framework without too much overhead.
    It also optionally adds congurability and extensibility, thanks to the Dependency Injection
    component and a powerful plugin system (bundles).
    Ñì.òàêæå:
    Read more about the HttpKernel component. Read more about Dependency Injection and
    Bundles.
    FrameworkBundle Bundle
    The :namespace:`Symfony\\Bundle\\FrameworkBundle` bundle is the bundle that ties the
    main components and libraries together to make a lightweight and fast MVC framework. It
    comes with a sensible default conguration and conventions to ease the learning curve.
    5.17.2 Kernel

    The Symfony\Component\HttpKernel\HttpKernel class is the central class
    of Symfony2 and is responsible for handling client requests. Its main goal
    is to convert a Symfony\Component\HttpFoundation\Request object to a
    Symfony\Component\HttpFoundation\Response object.
    Every Symfony2 Kernel implements Symfony\Component\HttpKernel\HttpKernelInterface:
    5.17. Internals

    315

    Symfony Documentation, Âûïóñê 2.0

    function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true)
    Controllers
    To convert a Request to a Response, the Kernel relies on a Controller. A Controller can be
    any valid PHP callable.
    The Kernel delegates the selection of what Controller should be executed to an
    implementation of Symfony\Component\HttpKernel\Controller\ControllerResolverInterface:

    public function getController(Request $request);
    public function getArguments(Request $request, $controller);

    The :method:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface::getController
    method returns the Controller (a PHP callable) associated with the given Request. The
    default implementation (Symfony\Component\HttpKernel\Controller\ControllerResolver)
    looks for a _controller request attribute that represents the controller name (a
    class::method string, like Bundle\BlogBundle\PostController:indexAction).

    Ñîâåò: The default implementation uses the Symfony\Bundle\FrameworkBundle\EventListener\RouterLis
    to dene the _controller Request attribute (see kernel.request Event).

    The :method:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface::getArgument
    method returns an array of arguments to pass to the Controller callable. The default
    implementation automatically resolves the method arguments, based on the Request
    attributes.
    Matching Controller method arguments from Request attributes
    For each method argument, Symfony2 tries to get the value of a Request attribute with
    the same name. If it is not dened, the argument default value is used if dened:

    // Symfony2 will look for an 'id' attribute (mandatory)
    // and an 'admin' one (optional)
    public function showAction($id, $admin = true)
    {
    // ...
    }

    316

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    Handling Requests
    The handle() method takes a Request and always returns a Response. To convert the Request,
    handle() relies on the Resolver and an ordered chain of Event notications (see the next
    section for more information about each Event):
    1. Before doing anything else, the kernel.request event is notied  if one of the listeners
    returns a Response, it jumps to step 8 directly;
    2. The Resolver is called to determine the Controller to execute;
    3. Listeners of the kernel.controller event can now manipulate the Controller callable the
    way they want (change it, wrap it, ...);
    4. The Kernel checks that the Controller is actually a valid PHP callable;
    5. The Resolver is called to determine the arguments to pass to the Controller;
    6. The Kernel calls the Controller;
    7. If the Controller does not return a Response, listeners of the kernel.view event can
    convert the Controller return value to a Response;
    8. Listeners of the kernel.response event can manipulate the Response (content and
    headers);
    9. The Response is returned.
    If an Exception is thrown during processing, the kernel.exception is notied and listeners are
    given a chance to convert the Exception to a Response. If that works, the kernel.response
    event is notied; if not, the Exception is re-thrown.
    If you don't want Exceptions to be caught (for embedded requests for instance), disable the
    kernel.exception event by passing false as the third argument to the handle() method.
    Internal Requests
    At any time during the handling of a request (the `master' one), a sub-request can be handled.
    You can pass the request type to the handle() method (its second argument):

    • HttpKernelInterface::MASTER_REQUEST;
    • HttpKernelInterface::SUB_REQUEST.
    The type is passed to all events and listeners can act accordingly (some processing must only
    occur on the master request).

    5.17. Internals

    317

    Symfony Documentation, Âûïóñê 2.0
    Events
    Each
    event
    thrown
    by
    the
    Kernel
    is
    a
    Symfony\Component\HttpKernel\Event\KernelEvent. This means
    has access to the same basic information:

    subclass
    of
    that each event

    • getRequestType()
    returns
    the
    type
    of
    the
    request
    (HttpKernelInterface::MASTER_REQUEST or HttpKernelInterface::SUB_REQUEST);
    • getKernel() - returns the Kernel handling the request;
    • getRequest() - returns the current Request being handled.

    getRequestType()
    The getRequestType() method allows listeners to know the type of the request. For instance,
    if a listener must only be active for master requests, add the following code at the beginning
    of your listener method:

    use Symfony\Component\HttpKernel\HttpKernelInterface;
    if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
    // return immediately
    return;
    }
    Ñîâåò: If you are not yet familiar with the Symfony2 Event Dispatcher, read the Events
    section rst.

    kernel.request Event
    Event Class: Symfony\Component\HttpKernel\Event\GetResponseEvent
    The goal of this event is to either return a Response object immediately or setup variables
    so that a Controller can be called after the event. Any listener can return a Response object
    via the setResponse() method on the event. In this case, all other listeners won't be called.
    This event is used by FrameworkBundle to populate the _controller Request attribute, via
    the Symfony\Bundle\FrameworkBundle\EventListener\RouterListener. RequestListener
    uses a Symfony\Component\Routing\RouterInterface object to match the Request and
    determine the Controller name (stored in the _controller Request attribute).

    318

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    kernel.controller Event
    Event Class: Symfony\Component\HttpKernel\Event\FilterControllerEvent
    This event is not used by FrameworkBundle, but can be an entry point used to modify the
    controller that should be executed:

    use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
    public function onKernelController(FilterControllerEvent $event)
    {
    $controller = $event->getController();
    // ...

    }

    // the controller can be changed to any PHP callable
    $event->setController($controller);

    kernel.view Event
    Event Class: Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent
    This event is not used by FrameworkBundle, but it can be used to implement a view subsystem. This event is called only if the Controller does not return a Response object. The
    purpose of the event is to allow some other return value to be converted into a Response.
    The value returned by the Controller is accessible via the getControllerResult method:

    use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
    use Symfony\Component\HttpFoundation\Response;
    public function onKernelView(GetResponseForControllerResultEvent $event)
    {
    $val = $event->getReturnValue();
    $response = new Response();
    // some how customize the Response from the return value
    }

    $event->setResponse($response);

    kernel.response Event
    Event Class: Symfony\Component\HttpKernel\Event\FilterResponseEvent
    The purpose of this event is to allow other systems to modify or replace the Response object
    after its creation:
    5.17. Internals

    319

    Symfony Documentation, Âûïóñê 2.0

    public function onKernelResponse(FilterResponseEvent $event)
    {
    $response = $event->getResponse();
    // .. modify the response object
    }
    The FrameworkBundle registers several listeners:

    • Symfony\Component\HttpKernel\EventListener\ProlerListener: collects data for the
    current request;
    • Symfony\Bundle\WebProlerBundle\EventListener\WebDebugToolbarListener:
    injects the Web Debug Toolbar;
    • Symfony\Component\HttpKernel\EventListener\ResponseListener:
    Response Content-Type based on the request format;

    xes

    the

    • Symfony\Component\HttpKernel\EventListener\EsiListener: adds a SurrogateControl HTTP header when the Response needs to be parsed for ESI tags.

    kernel.exception Event
    Event Class: Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent
    FrameworkBundle registers an Symfony\Component\HttpKernel\EventListener\ExceptionListener
    that forwards the Request to a given Controller (the value of the exception_listener.controller
    parameter  must be in the class::method notation).
    A listener on this event can create and set a Response object, create and set a new Exception
    object, or do nothing:

    use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
    use Symfony\Component\HttpFoundation\Response;
    public function onKernelException(GetResponseForExceptionEvent $event)
    {
    $exception = $event->getException();
    $response = new Response();
    // setup the Response object based on the caught exception
    $event->setResponse($response);

    }

    // you can alternatively set a new Exception
    // $exception = new \Exception('Some special exception');
    // $event->setException($exception);

    320

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0
    5.17.3 The Event Dispatcher

    Objected Oriented code has gone a long way to ensuring code extensibility. By creating classes
    that have well dened responsibilities, your code becomes more exible and a developer can
    extend them with subclasses to modify their behaviors. But if he wants to share his changes
    with other developers who have also made their own subclasses, code inheritance is moot.
    Consider the real-world example where you want to provide a plugin system for your project.
    A plugin should be able to add methods, or do something before or after a method is executed,
    without interfering with other plugins. This is not an easy problem to solve with single
    inheritance, and multiple inheritance (were it possible with PHP) has its own drawbacks.
    The Symfony2 Event Dispatcher implements the Observer pattern in a simple and eective
    way to make all these things possible and to make your projects truly extensible.
    Take a simple example from the Symfony2 HttpKernel component. Once a Response object
    has been created, it may be useful to allow other elements in the system to modify it (e.g. add
    some cache headers) before it's actually used. To make this possible, the Symfony2 kernel
    throws an event - kernel.response. Here's how it work:

    • A listener (PHP object) tells a central dispatcher object that it wants to listen to the
    kernel.response event;
    • At some point, the Symfony2 kernel tells the dispatcher object to dispatch the
    kernel.response event, passing with it an Event object that has access to the Response
    object;
    • The dispatcher noties (i.e. calls a method on) all listeners of the kernel.response event,
    allowing each of them to make modications to the Response object.
    Events
    When an event is dispatched, it's identied by a unique name (e.g.
    kernel.response), which any number of listeners might be listening to. An
    Symfony\Component\EventDispatcher\Event instance is also created and passed to
    all of the listeners. As you'll see later, the Event object itself often contains data about the
    event being dispatched.

    Naming Conventions
    The unique event name can be any string, but optionally follows a few simple naming
    conventions:

    • use only lowercase letters, numbers, dots (.), and underscores (_);
    • prex names with a namespace followed by a dot (e.g. kernel.);

    5.17. Internals

    321

    Symfony Documentation, Âûïóñê 2.0

    • end names with a verb that indicates what action is being taken (e.g. request).
    Here are some examples of good event names:

    • kernel.response
    • form.pre_set_data

    Event Names and Event Objects
    When the dispatcher noties listeners, it passes an actual Event object to those listeners.
    The base Event class is very simple: it contains a method for stopping event propagation,
    but not much else.
    Often times, data about a specic event needs to be passed along with the Event
    object so that the listeners have needed information. In the case of the kernel.response
    event, the Event object that's created and passed to each listener is actually of type
    Symfony\Component\HttpKernel\Event\FilterResponseEvent, a subclass of the base Event
    object. This class contains methods such as getResponse and setResponse, allowing listeners
    to get or even replace the Response object.
    The moral of the story is this: when creating a listener to an event, the Event object that's
    passed to the listener may be a special subclass that has additional methods for retrieving
    information from and responding to the event.
    The Dispatcher
    The dispatcher is the central object of the event dispatcher system. In general, a single
    dispatcher is created, which maintains a registry of listeners. When an event is dispatched
    via the dispatcher, it noties all listeners registered with that event.

    use Symfony\Component\EventDispatcher\EventDispatcher;
    $dispatcher = new EventDispatcher();
    Connecting Listeners
    To take advantage of an existing event, you need to connect a listener to the dispatcher so
    that it can be notied when the event is dispatched. A call to the dispatcher addListener()
    method associates any valid PHP callable to an event:

    $listener = new AcmeListener();
    $dispatcher->addListener('foo.action', array($listener, 'onFooAction'));
    The addListener() method takes up to three arguments:
    322

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    • The event name (string) that this listener wants to listen to;
    • A PHP callable that will be notied when an event is thrown that it listens to;
    • An optional priority integer (higher equals more important) that determines when a
    listener is triggered versus other listeners (defaults to 0). If two listeners have the same
    priority, they are executed in the order that they were added to the dispatcher.
    Ïðèìå÷àíèå: A PHP callable is a PHP variable that can be used by the call_user_func()
    function and returns true when passed to the is_callable() function. It can be a \Closure
    instance, a string representing a function, or an array representing an object method or a
    class method.
    So far, you've seen how PHP objects can be registered as listeners. You can also register
    PHP Closures as event listeners:

    use Symfony\Component\EventDispatcher\Event;
    $dispatcher->addListener('foo.action', function (Event $event) {
    // will be executed when the foo.action event is dispatched
    });
    Once a listener is registered with the dispatcher, it waits until the event is notied.
    In the above example, when the foo.action event is dispatched, the dispatcher calls the
    AcmeListener::onFooAction method and passes the Event object as the single argument:

    use Symfony\Component\EventDispatcher\Event;
    class AcmeListener
    {
    // ...

    }

    public function onFooAction(Event $event)
    {
    // do something
    }

    Ñîâåò: If you use the Symfony2 MVC framework, listeners can be registered via your
    conguration. As an added bonus, the listener objects are instantiated only when needed.
    In many cases, a special Event subclass that's specic to the given event is
    passed to the listener. This gives the listener access to special information
    about the event. Check the documentation or implementation of each event
    to determine the exact Symfony\Component\EventDispatcher\Event instance

    5.17. Internals

    323

    Symfony Documentation, Âûïóñê 2.0
    that's being passed. For example, the kernel.event event passes an instance of
    Symfony\Component\HttpKernel\Event\FilterResponseEvent:

    use Symfony\Component\HttpKernel\Event\FilterResponseEvent
    public function onKernelResponse(FilterResponseEvent $event)
    {
    $response = $event->getResponse();
    $request = $event->getRequest();
    }

    // ...

    Creating and Dispatching an Event
    In addition to registering listeners with existing events, you can create and throw your own
    events. This is useful when creating third-party libraries and also when you want to keep
    dierent components of your own system exible and decoupled.

    The Static Events Class
    Suppose you want to create a new Event - store.order - that is dispatched each time an order
    is created inside your application. To keep things organized, start by creating a StoreEvents
    class inside your application that serves to dene and document your event:

    namespace Acme\StoreBundle;
    nal class StoreEvents
    {
    /**
    * The store.order event is thrown each time an order is created
    * in the system.
    *
    * The event listener receives an Acme\StoreBundle\Event\FilterOrderEvent
    * instance.
    *
    * @var string
    */
    const onStoreOrder = 'store.order';
    }
    Notice that this class doesn't actually do anything. The purpose of the StoreEvents class is
    just to be a location where information about common events can be centralized. Notice also
    that a special FilterOrderEvent class will be passed to each listener of this event.

    324

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    Creating an Event object
    Later, when you dispatch this new event, you'll create an Event instance and pass it to the
    dispatcher. The dispatcher then passes this same instance to each of the listeners of the
    event. If you don't need to pass any information to your listeners, you can use the default
    Symfony\Component\EventDispatcher\Event class. Most of the time, however, you will need
    to pass information about the event to each listener. To accomplish this, you'll create a new
    class that extends Symfony\Component\EventDispatcher\Event.
    In this example, each listener will need access to some pretend Order object. Create an Event
    class that makes this possible:

    namespace Acme\StoreBundle\Event;
    use Symfony\Component\EventDispatcher\Event;
    use Acme\StoreBundle\Order;
    class FilterOrderEvent extends Event
    {
    protected $order;
    public function __construct(Order $order)
    {
    $this->order = $order;
    }

    }

    public function getOrder()
    {
    return $this->order;
    }

    Each listener now has access to the Order object via the getOrder method.

    Dispatch the Event
    The
    :method:`Symfony\\Component\\EventDispatcher\\EventDispatcher::dispatch`
    method noties all listeners of the given event. It takes two arguments: the name of the
    event to dispatch and the Event instance to pass to each listener of that event:

    use Acme\StoreBundle\StoreEvents;
    use Acme\StoreBundle\Order;
    use Acme\StoreBundle\Event\FilterOrderEvent;
    // the order is somehow created or retrieved
    $order = new Order();
    5.17. Internals

    325

    Symfony Documentation, Âûïóñê 2.0

    // ...
    // create the FilterOrderEvent and dispatch it
    $event = new FilterOrderEvent($order);
    $dispatcher->dispatch(StoreEvents::onStoreOrder, $event);
    Notice that the special FilterOrderEvent object is created and passed to the dispatch method.
    Now, any listener to the store.order event will receive the FilterOrderEvent and have access
    to the Order object via the getOrder method:

    // some listener class that's been registered for onStoreOrder
    use Acme\StoreBundle\Event\FilterOrderEvent;
    public function onStoreOrder(FilterOrderEvent $event)
    {
    $order = $event->getOrder();
    // do something to or with the order
    }
    Passing along the Event Dispatcher Object
    If you have a look at the EventDispatcher class, you will notice that the class does not act as
    a Singleton (there is no getInstance() static method). That is intentional, as you might want
    to have several concurrent event dispatchers in a single PHP request. But it also means that
    you need a way to pass the dispatcher to the objects that need to connect or notify events.
    The best practice is to inject the event dispatcher object into your objects, aka dependency
    injection.
    You can use constructor injection:

    class Foo
    {
    protected $dispatcher = null;

    }

    public function __construct(EventDispatcher $dispatcher)
    {
    $this->dispatcher = $dispatcher;
    }

    Or setter injection:

    class Foo
    {
    protected $dispatcher = null;

    326

    Ãëàâà 5. Êíèãà

    Symfony Documentation, Âûïóñê 2.0

    }

    public function setEventDispatcher(EventDispatcher $dispatcher)
    {
    $this->dispatcher = $dispatcher;
    }

    Choosing between the two is really a matter of taste. Many tend to prefer the constructor
    injection as the objects are fully initialized at construction time. But when you have a long
    list of dependencies, using setter injection can be the way to go, especially for optional
    dependencies.
    Ñîâåò: If you use dependency injection like we did in the two examples above, you can then
    use the Symfony2 Dependency Injection component to elegantly manage these objects.

    Using Event Subscribers
    The most common way to listen to an event is to register an event listener with the dispatcher.
    This listener can listen to one or more events and is notied each time those events are
    dispatched.
    Another way to listen to events is via an event subscriber. An event subscriber is a PHP class
    that's able to tell the dispatcher exactly which events it should subscribe to. It implements the
    Symfony\Component\EventDispatcher\EventSubscriberInterface interface, which requires a
    single static method called getSubscribedEvents. Take the following example of a subscriber
    that subscribes to the kernel.response and store.order events: