Route Class - GoogleCode

gasownerData Management

Jan 31, 2013 (4 years and 7 months ago)

394 views







Symfony Framework

Download Symfony

framework

sompress file on symfony web site.

Create
lib

and
lib
\
vendor

folder under
projectFolder

Uncompress it unnder
projectFolder
\
lib
\
vendor

path.

Add PHP class path:


.go to "Enviroment Variable" section


.Add

to "path" variable the path of folder that contains php.exe

Do the same with "symfony.bat" file with the path of folder that contains "symfony.bat" file


Some symfony command
:

To configure on the project, we need to go to project folder




$s
ymfony generate
:project jobeet


: generate new project template




$ php symfony generate:app frontend
” create new application on specific project




$s
ymfony


: show all available options and tasks




$symfony
-
V
” : show symfony version



We need to tell symfony to use this
database

connection:

$ php symfony configure:database
"mysql:host=localhost;dbname=jobeet" userName password



this will generate data to
config/databases.yml



To generate data schema:

$php symfony doctrine:build
-
schema


this will generate data

from
existing

database

to


config/doctrine/schema.yml



The ORM also generates PHP classes that map table records to objects:


$ php symfony
doctrine:build

model




If you have created a module, you have probably used the
doctrine:generate
-
module

appName
moduleName modelName

(
it generates

_
form
, indexSuccess, newSuccess, showSuccess,
editSuccess.php file and action.class file
)
. That's fine but as we won't need 90% of the generated
code, I have used the
generate:module
(
it generate index.php and a
ction
.class

file
)

which creates
an empty module.



“$
php

symfony doctrine:build

sql”

task generates SQL statements in the
data/sql/

directory. It
will generate all ORM class to SQL statement in data/sql folder



php symfony doctrine
:insert
-
sql
:
insert schema structure to database and create table base on
schema.yml

file



$ php symfony doctrine:build
--
all
--
and
-
load
--
no
-
confirmation:
update the database tables,
and repopulate the database with our fixtures



$php symfony doctrine:data
-
load

task to load

data in
data/
fixtures

them into the database




Web Server Configuration
(Apache)
:


Locate and open the
httpd.conf

configuration file and add the following configuration at the end:


The following code will put our symfony project to the local
host with 8080 listen port

# This is the configuration for your project

Listen 127.0.0.1:8080


<VirtualHost 127.0.0.1:8080>


DocumentRoot "
ProjectFolder
\
web
"


DirectoryIndex index.php


<Directory "
ProjectFolder
\
web
">


AllowOverride All


Allow from All


</Directory>



Alias /sf
ProjectFolder
\
lib
\
vendor
\
symfony
\
data
\
web
\
sf


<Directory "
ProjectFolder
\
lib
\
vendor
\
symfony
\
data
\
web
\
sf
">


AllowOverride All


Allow from All


</Directory>

</VirtualHost>

Symfony Core Classes

The MVC
implementation in symfony uses several classes that you will meet quite often in this book:



sfController

is the controller class. It decodes the request and hands it to the action.



sfRequest

stores all the request elements (parameters, cookies, headers, an
d so on).



sfResponse

contains the response headers and contents. This is the object that will eventually be
converted to an HTML response and be sent to the user.



The context (retrieved by
sfContext::getInstance()
) stores a reference to all the core object
s and the
current configuration; it is accessible from everywhere.

Class Autoloading

Usually, when you use a class method or create an object in PHP, you need to include the class definition
first:

include_once

'classes/MyClass.php'
;

$myObject

=
new

MyClass
()
;

On large projects with many classes and a deep directory structure, keeping track of all the class files to
include and their paths can be time consuming. By providing an
spl_autoload_register()

function, symfony
makes
include_once

statements u
nnecessary, and you can write directly:

$myObject

=
new

MyClass
()
;

Symfony will then look for a
MyClass

definition in all files ending with
class.php

in one of the project's
lib/

directories. If the class definition is found, it will be included automatica
lly.

So if you store all your classes in
lib/

directories, you don't need to include classes anymore. That's why
symfony projects usually do not contain any
include_once

or
require_once

statements.

Constants

You will not find any constants in symfony becau
se by their very nature you can't change their value once
they are defined. Symfony uses its own configuration object, called
sfConfig
, which replaces constants. It
provides static methods to access parameters from everywhere

sfConfig c
an get constant in app.yml file. In practices, we stored constants in app.yml file


Data Model

Symfony is an Object
-
Oriented framework,

we like to manipulate objects whenever we can. The relational
database information must be mapped to an object model. T
his can be done with an
ORM tool

and
thankfully, symfony comes bundled with two of them:
Propel

and
Doctrine

The ORM needs a description of the tables and their relationships to create the related classes. There are
two ways to create this description schema: by introspecting an existing database or by creating it by hand.

config/doctrine/schema.yml

is the file for ORM configuration


config/databases.yml

uses for database configuration
. We can configure this file by hand or by command
tool
.
The schema is the direct translation of the entity relationship diagram in the YAML
format.

We need to tell symfony to use
the
database
configuration
:


$ php symfony configure:database "mysql:host=localhost;dbname=jobeet"
userName

password


This command will generate data to the
config/databases.yml


The YAML Format

According to the offic
ial
YAML

website, YAML is "a human friendly data serialization standard for all
programming languages"

Put another way, YAML is a simple language to describe data (strings, integers, dates, arrays, and hashes).

In YAML, str
ucture is shown through indentation, sequence items are denoted by a dash, and key/value
pairs within a map are separated by a colon. YAML also has a shorthand syntax to describe the same
structure with fewer lines, where arrays are explicitly shown with
[
]

and hashes with
{}
.

If you are not yet familiar with YAML, it is time to get started as the symfony framework uses it
extensively for its configuration files. A good starting point is the symfony YAML component
documentation
.

There is one important thing you need to remember when editing a YAML file:
indentation must be done
with one or more spaces, but never with tabulations
.

The
schema.yml

f
ile contains the description of all tables and their columns. Each column is described
with the following information:



type
: The column type (
boolean
,
integer
,
float
,
decimal
,
string
,
array
,
object
,
blob
,
clob
,
timestamp
,
time
,
date
,
enum
,
gzip
)



notnull
: S
et it to
true

if you want the column to be required



unique
: Set it to
true

if you want to create a unique index for the column.



To generate
schema.yml

file we need to use command: “
$ php symfony doctrine:build
-
schema



this
will generate data to

config/doctrine/schema.yml


The

$php symfony
doctrine:build

model


task generates PHP

class

files in the
lib/model/
doctrine

directory that can be used to interact with the database.

By browsing the generated files, you have probably noticed that Doctrine

generates three classes per table.
For the jobeet_job table:



JobeetJob: An object of this class represents a single record of the jobeet_job table. The class is
empty by default.



BaseJobeetJob: The parent class of JobeetJob. Each time you run doctrine:bui
ld
--
model, this class
is overwritten, so all customizations must be done in the JobeetJob class.



JobeetJobTable: The class defines methods that mostly return collections of JobeetJob objects. The
class is empty by default.

The

$
php

symfony
doctrine:build

sql


task generates SQL statements in the
data/sql/

directory
. It will
generate all ORM class to SQL statement

in data/sql folder

Each time symfony creates the tables in the database, all the data are lost. To populate the database with
som
e initial data, we could create a PHP script, or execute some SQL statements with the
mysql

program.
But as the need is quite common, there is a better way with symfony: create
YAML
files in the
data/fixtures/

directory and use the
doctrine:data
-
load

task
to load them into the database
.

doctrine:build
--
forms



build all forms base on model

Symfony is able to automatically generate a module for a given model that provides basic manipulation
features:

$ php symfony doctrine:generate
-
module
--
with
-
show
--
non
-
verbose
-
templates frontend job
JobeetJob

php symfony doctrine
:insert
-
sql
:
insert schema structure to database and create table base on
schema.yml

file

Use the doctrine
:build
--
all
--
and
-
load

task to update the database tables, and repopulate the database with
our fixtures:

$ php symfony doctrine
:build
--
all
--
and
-
load
--
no
-
confirmation


See it in Action in the Browser

Each application is further divided into
modules
. A module is a self
-
contai
ned set of PHP code that
represents a feature of the application (the API module for example), or a set of manipulations the user can
do on a model object

Symfony is able to automatically generate a module for a given model that provides basic manipulation

features:


$ php symfony doctrine:generate
-
module
--
with
-
show
--
non
-
verbose
-
templates frontend job
JobeetJob



The MVC Architecture

For web development, the most common solution for organizing your code nowadays is the
MVC design
pattern
. In short, the MVC design pattern defines a way to organize your code according to its nature.
This pattern separates the code into
three layers
:



The
Model

layer defines the business logic (the database belongs to this layer). You already know
that symfon
y stores all the classes and files related to the Model in the
lib/model/

directory.



The
View

is what the user interacts with (a template engine is part of this layer). In symfony, the
View layer is mainly made of PH
P templates. They are stored in
apps/app
N
ame
/
modules/
moduleName
/
templates
/

directory
.



The
Controller

is a piece of code that calls the Model to get some data that it passes to the View
for rendering to the client. When we installed symfony at the beginning of this book, we saw that
all requests are managed by front controllers (
index.php

and
frontend_dev
.php
). These front
controllers delegate the real work to
actions
. As we saw previously, these actions are logically
grouped into
modules
.




















The Layout

There must be a better way. Instead of reinventing the wheel, we will use another design pattern to solve
this problem: the
decorator design pattern
. The decorator design pattern resolves the problem the other
way around: the template is decorated after th
e content is rendered by a global template, called a
layout

in
symfony











The default layout of an application is called
layout.php

and can be found in the
apps/
appName
/templates/

directory

for each application
. This directory contains all the
global templates
for an application.


The Stylesheets, Images, and JavaScripts

By default, the
generate:project

task has created three directories for the project assets:
web/images/

for
images,
web/~css|CSS~/

for stylesheets, and

web/js/

for JavaScripts.
This is one of the many conventions
defined by symfony, but you can of course store them elsewhere under the
web/

directory.


The stylesheet file has been included by the
include_stylesheets
()

function call found within the layout
<head>

tag. The
include_stylesheets
()
,
include_javascripts
()
, include_metas(), include_title()

function
is called a
helper
. A helper is a function, defined by symfony, that can take parameters and returns HTML
code. Most of the time, helpers are time
-
savers, they package c
ode snippets frequently used in templates.
The
include_stylesheets()

helper generates
<link>

tags for stylesheets.

Both of helper function will be call
in
apps/appName/tenplates/layout.php

or other php file

The View layer can be configured which stylesheet
, javascript
, other files

can be included

in header
HTML section
(when we call helper)


by editing the
\
apps
\
appName
\
config
\
view.yml

configuration file of
the application.


The
view.yml

file configures the
default

settings for all the templates of the application. For instance, the
stylesheets

entry defines an array of stylesheet files to include for every page of the application (the
inclusion is done by the
include_stylesheets
()

helper).


In the
view.yml

file



the referenced file is
main.css
, and not
/css/main.css
. As a matter of fact, both definitions are equivalent as
symfony prefixes relative paths with
/~css|CSS~/
.

If many files are defined, symfony will include them in the same order as the definition:

stylesheets: [main.css, jobs.css, job.css]


This configuration will be rendered as:

<link rel=
"stylesheet"

type=
"text/css"

media=
"screen"


href=
"/css/main.css"

/>

<link rel=
"stylesheet"

type=
"text/css"

media=
"screen"


href=
"/css/jobs.css"

/>

<link rel
=
"stylesheet"

type=
"text/css"

media=
"screen"


href=
"/css/job.css"

/>

<link rel=
"stylesheet"

type=
"text/css"

media=
"print"


href=
"/css/print.css"

/>

The
view.yml

configuration file also defines the default layout used by the application. By default, the
name is
layout
, and so symfony decorates every page with the
layout.php

file. You can also disable the
decoration process altogether by switching the
has_layout

in

view.yml

file

entry to
false
.


To customize the view for the
job

module, create a
view.yml

file in the
apps/frontend/modules/job/config/

directory:

# apps/frontend/modules/job/config/view.yml

indexSuccess:


stylesheets: [jobs.css]



showSuccess:


styles
heets: [job.css]

Each module can have one
view.yml

file defining the settings of its views. This allows you to define view
settings for a whole module and per view in a single file. The first
-
level keys of the
view.yml

file are the
module view names

editSuccess:


metas:


title: Edit your profile


editError:


metas:


title: Error in the profile edition


all:


stylesheets: [my_style]


metas:


title: My website


The default settings for the module are defined under the
all:

key in the module
view.yml
. The default
settings for all the application views are defined in the application
view.yml
. Once again, you recognize
the configuration cascade principle:



In
apps/frontend/modules/mymodule/config/view.yml
, the per
-
view definiti
ons apply only to
one view and override the module
-
level definitions.



In
apps/frontend/modules/mymodule/config/view.yml
, the
all:

definitions apply to all the
actions of the module and override the application
-
level definitions.



In
apps/frontend/config/vie
w.yml
, the
default:

definitions apply to all modules and all
actions of the application.

Remember that the configuration cascade principle applies, so any file inclusion defined in the application
view.yml

makes it appear in every page of the application.
Listings 7
-
28, 7
-
29, and 7
-
30 demonstrate this
principle.

Listing 7
-
28
-

Sample Application
view.yml

default:


stylesheets: [main]

Listing 7
-
29
-

Sample Module
view.yml

indexSuccess:


stylesheets: [special]


all:


stylesheets: [additional]

Listing 7
-
30
-

Resulting
indexSuccess

View

<link rel=
"stylesheet"

type=
"text/css"

media=
"screen"

href=
"/css/main.css"

/>

<link rel=
"stylesheet"

type=
"text/css"

media=
"screen"

href=
"/css/additional.css"

/>

<link rel=
"stylesheet"

type=
"text/css"

media=
"screen"

href=
"/css/special.css"

/>

If you need to remove a file defined at a higher level, just add a minus sign (
-
) in front of the file name in
the lower
-
level definition, as shown in Listing 7
-
31.

Listing 7
-
31
-

Sample Module
view.yml

That Removes a Fi
le Defined at the Application Level

indexSuccess:


stylesheets: [
-
main, special]


all:


stylesheets: [additional]

To remove all style sheets or JavaScript files, use
-
*

as a file name, as shown in Listing 7
-
32.

Listing 7
-
32
-

Sample Module
view.yml

That
Removes all Files Defined at the Application Level

indexSuccess:


stylesheets: [
-
*]


javascripts: [
-
*]

You can be more accurate and define an additional parameter to force the position where to include the file
(first or last position), as shown in Listi
ng 7
-
33. This works for both style sheets and JavaScript files.

The best practice is to define the defaults stylesheets and javascripts file in the project view.yml file, and
include specifics stylesheets or javascripts files in your templates using the de
dicated
helper
(
include_stylesheets
()
,
include_javascripts
()
)
. This way, you do not have to remove or replace
already included assets, which can be a painfull in some cases.

According to the graphical charter of your website, you may have several layouts. Cl
assic websites have at
least two: the default layout and the pop
-
up layout.

You have already seen that the default layout is
myproject/apps/frontend/templates/layout.php
.
Additional layouts must be added in the same global
templates/

directory. If you want

a view to use a
frontend/templates/my_layout.php

file, use the syntax shown in Listing 7
-
36.

Listing 7
-
36
-

Layout Definition

// In view.yml

indexSuccess:


layout: my_layout

// In the action

$this
-
>
setLayout
(
'my_layout'
)
;



// In the template

<?php

decorate_with
(
'my_layout'
)

?>


Output Escaping

When you insert dynamic data in a template, you must be sure about the data integrity. For instance, if data
comes from forms filled in by anonymous users, there is a risk that it may include malicious script
s
intended to launch cross
-
site scripting (XSS) attacks. You must be able to escape the output data, so that
any HTML tag it contains becomes harmless.

You could escape your output manually by enclosing every unsure value in a call to
htmlspecialchars()
, b
ut
that approach would be very repetitive and error
-
prone. Instead, symfony provides a special system, called
output escaping, which automatically escapes every variable output in a template. It is activated by default
in the application
settings.yml
.

Outp
ut escaping is configured globally for an application in the
settings.yml

file. Two parameters control
the way that output escaping works: the strategy determines how the variables are made available to the
view, and the method is the default escaping func
tion applied to the data.

Basically, all you need to do to activate output escaping is to set the
escaping_strategy

parameter to
true

This will add
htmlspecialchars()

to all variable output by default. For instance, suppose that you define a
test

variable
in an action as follows:

$this
-
>
test

=
'<script>alert(document.cookie)</script>'
;

With output escaping turned on, echoing this variable in the template will output the escaped data:

echo

$test
;


=> &lt;script&gt;alert
(
document.cookie
)
&lt;/script&gt;

Escaping helpers are functions returning an escaped version of their input. They can be provided as a
default
escaping_method

in the
settings.yml

file or to specify an escaping method for a specific value in
the view. The following escaping helpers are ava
ilable:



ESC_RAW
: Doesn't escape the value.



ESC_SPECIALCHARS
: Applies the PHP function
htmlspecialchars()

to the input.



ESC_ENTITIES
: Applies the PHP function
htmlentities()

to the input with
ENT_QUOTES

as the
quote style.



ESC_JS
: Escapes a value to be put
into a JavaScript string that is going to be used as HTML. This
is useful for escaping things where HTML is going to be dynamically changed using JavaScript.



ESC_JS_NO_ENTITIES
: Escapes a value to be put into a JavaScript string but does not add
entities.
This is useful if the value is going to be displayed using a dialog box (for example, for a
myString

variable used in
javascript:alert(myString);
).



Configuration Principles in symfony


For many symfony configuration files, the same setting can be defined

at different levels:



The default configuration is located in the framework



The global configuration for the project (in
config/
)



The local configuration for an application (in
apps/APP/config/
)



The local configuration restricted to a module (in
apps/APP/m
odules/MODULE/config/
)

At runtime, the configuration system merges all the values from the different files if they exist and caches
the result for better performance.

As a rule of thumb, when something is configurable via a configuration file, the same can

be accomplished
with PHP code. Instead of creating a
view.yml

file for the
job

module for instance, you can also use the
use_stylesheet
()

helper to include a stylesheet from a template:

<!
--

apps/frontend/modules/job/templates/indexSuccess.php
--
>

<?php

use_stylesheet
(
'jobs.css'
)

?>



<!
--

apps/frontend/modules/job/templates/showSuccess.php
--
>

<?php

use_stylesheet
(
'job.css'
)

?>

The JavaScript configuration is done via the
javascripts

entry of the
view.yml

configuration file and the
use_javascript
()

helper defines JavaScript files to include for a template



The Job Homepage

As seen in day 3, the job homepage is generated by the
index

action of the
job

module. The
index

action
is the Controller part of the page and the associated template,
indexSucce
ss.php
, is the View part:








The Action

Each action is represented by a method of a class. For the job homepage, the class is
jobActions

(the name
of the module suffixed by
Actions
) and the method is
executeIndex()

(
execute

suffixed by the name of the
action)


// apps/frontend/modules/job/actions/actions.class.php

class

jobActions
extends

sfActions

{


public

function

executeIndex
(
sfWebRequest
$request
)


{


$this
-
>
jobeet_jobs

= Doctrine::
getTable
(
'JobeetJob'
)


-
>
createQuery
(
'a'
)


-
>
execute
()
;


}




// ...

}

Let's have a closer look at the code: the
executeIndex
()

method (the Controller) calls the Table
JobeetJob

to create a query to retrieve all the jobs. It returns a
Doctrine_Collection

of
JobeetJob

objects that are
assigned to the
jobeet_jobs

object property. All such object properties are then automatically passed to the
template (the View). To pass data from the Controller to the View, just create a new property


public

function

executeFooBar
(
sfWe
bRequest
$request
)

{


$this
-
>
foo

=
'bar'
;


$this
-
>
bar

=
array
(
'bar'
,
'baz'
)
;

}

This code will make
$foo

and
$bar

variables accessible in the template.


The Template

By default, the template name associated with an action is deduced by symfony thanks to a convention (the
action name suffixed by
Success
).

The
indexSuccess.php

template generates an HTML table for all the jobs.

In template, we can call variables that defi
ne in action method and use it to generate data for template


The job description uses the
simple_format_text
()

helper to format it as HTML, by replacing carriage
returns with
<br />

for instance. As this helper belongs to the
Text

helper group, which is not loaded by
default, we have loaded it manually by using the
use_helper()

helper.


The Request and the Response

We have already seen that symfony encapsulates the request in a
sfWebRequest

object. the response is
also an object, o
f class
sfWebResponse
.
You can access the response object in an action by calling

$this
-
>getResponse()

The Request

The
sfWebRequest

class wraps the
$_SERVER
,
$_COOKIE
,
$_GET
,
$_POST
, and
$_FILES

PHP global arrays:


The Response

The
sfWebResponse

class wraps the
header()

and
setrawcookie()

PHP methods:











The Routing


URL


In a web context, a URL is the unique identifier of a web resource. When you go to a URL, you ask the
browser to fetch a resource identified by that URL. So, as the URL
is the interface between the website and
the user, it must convey some meaningful information about the resource it references. But "traditional"
URLs do not really describe the resource, they expose the internal structure of the application. The user
does

not care that your website is developed with the PHP language or that the job has a certain identifier
in the database. Exposing the internal workings of your application is also quite bad as far as security is
concerned: What if the user tries to guess t
he URL for resources he does not have access to? Sure, the
developer must secure them the proper way, but you'd better hide sensitive information.

URLs are so important in symfony that it has an entire framework dedicated to their management: the
routing

f
ramework. The routing manages internal URIs and external URLs. When a request comes in, the
routing parses the URL and converts it to an internal URI.

Routing Configuration

The mapping between internal URIs and external URLs is done in the
routing.yml

conf
iguration file:


# apps/frontend/config/routing.yml

homepage:


url: /


param: { module: default, action: index }



default_index:


url: /:module


param: { action: index }



default:


url: /:module/:action/*

The
routing.yml

file describes routes. A route has a name (
homepage
), a pattern (
/:module/:action/*
), and
some parameters (under the
param

key).

When a request comes in, the routing tries to match a pattern for the given URL. The first route that
matches wins, so the ord
er in
routing.yml

is important. Let's take a look at some examples to better
understand how this works.

When you request the Jobeet homepage, which has the
/job

URL, the first route that matches is the
default_index

one. In a pattern, a word prefixed with
a colon (
:
) is a variable, so the
/:module

pattern
means: Match a
/

followed by something. In our example, the
module

variable will have
job

as a value.
This value can then be retrieved with
$request
-
>getParameter('module')

in the action. This route also
d
efines a default value for the
action

variable. So, for all URLs matching this route, the request will also
have an
action

parameter with
index

as a value.

If you request the
/job/show/id/1

page, symfony will match the last pattern:
/:module/:action/*
. In
a
pattern, a star (
*
) matches a collection of variable/value pairs separated by slashes (
/
):


The
/job/show/id/1

URL can be created from a template by using the following call to the
url_for()

helper:


url_for
(
'job/show?id='
.
$job
-
>
getId
())

You can also use the route name by prefixing it by
@
:

url_for
(
'@default?module=job&action=show&id='
.
$job
-
>
getId
())


The
url_for()

helper transforms an internal URI into an external URL


Speeding Up Routing by Using the Rule Name

The link helpers accept a
rule label instead of a module/action pair if the rule label is preceded by an 'at'
sign (@), as shown in Listing 9
-
21.

Listing 9
-
21
-

Using the Rule Label Instead of the Module/Action

<?php

echo

link_to
(
'my article'
,
'article/read?id='
.
$article
-
>
getId
())

?>



// can also be written as

<?php

echo

link_to
(
'my article'
,
'@article_by_id?id='
.
$article
-
>
getId
())

?>

// or the fastest one (does not need extra parsing):

<?php

echo

link_to
(
'my article'
,
'article_by_id'
,
array
(
'id'

=>
$article
-
>
getId
()))

?>

There are

pros and cons to this trick. The advantages are as follows:



The formatting of internal URIs is done faster, since symfony doesn't have to browse all the rules to
find the one that matches the link. In a page with a great number of routed hyperlinks, the b
oost
will be noticeable if you use rule labels instead of module/action pairs.



Using the rule label helps to abstract the logic behind an action. If you decide to change an action
name but keep the URL, a simple change in the
routing.yml

file will suffice.

All of the
link_to()

calls will still work without further change.



The logic of the call is more apparent with a rule name. Even if your modules and actions have
explicit names, it is often better to call
@display_article_by_slug

than
article/display
.



You

know exactly which actions are enabled by reading the routing.yml file

On the other hand, a disadvantage is that adding new hyperlinks becomes less self
-
evident, since you
always need to refer to the
routing.yml

file to find out which label is to be used
for an action. And in a
big project, you will certainly end with a lot a routings rules, making a bit hard to maintain them. In this
case, you should package your application in several plugins, each one defining a limited set of features.

However, the exp
erience states that using routing rules is the best choice in the long run.

During your tests (in the
dev

environment), if you want to check which rule was matched for a given
request in your browser, develop the "logs" section of the web debug toolbar and

look for a line specifying
"matched route XXX". You will find more information about the web debug mode in Chapter 16

Route Customizations

From now, when you request the
/
URL in a browser, you have the default congratulations page of
symfony. That's becau
se this URL matches the
homepage

route. But it makes sense to change it to be the
Jobeet homepage. To make the change, modify the
module

variable of the
homepage

route to
job
:

# apps/frontend/config/routing.yml

homepage:


url: /


param:
{

module: job, action: index
}

We can now change the link of the Jobeet logo in the layout to use the
homepage

route:

<!
--

apps/frontend/templates/layout.php
--
>

<h1>


<a href=
"<?php echo url_for('homepage') ?>"
>


<img src=
"/images/logo.jpg"

alt=
"Jobeet

Job Board"

/>


</a>

</h1>

That was easy!

For something a bit more involved, let's change the job page URL to something more meaningful:

/job/sensio
-
labs/paris
-
france/1/web
-
developer

Without knowing anything about Jobeet, and without looking at the page,
you can understand from the
URL that Sensio Labs is looking for a Web developer to work in Paris, France.

Pretty URLs are important because they convey information for the user. It is also useful when you copy
and paste the URL in an email or to optimize y
our website for search engines.

The following pattern matches such a URL:

/job/:company/:location/:id/:position

Edit the
routing.yml

file and add the
job_show_user

route at the beginning of the file:

job_show_user:


url: /job/:company/:location/:id/:pos
ition


param: { module: job, action: show }

If you refresh the Jobeet homepage, the links to jobs have not changed. That's because to generate a route,
you need to pass all the required variables. So, you need to change the
url_for()

call in
indexSuccess.
php

to:

url_for('job/show?id='.$job
-
>getId().'&company='.$job
-
>getCompany().


'&location='.$job
-
>getLocation().'&position='.$job
-
>getPosition())

An internal URI can also be expressed as an array:

url_for(array(


'module' => 'job',


'action' =>

'show',


'id' => $job
-
>getId(),


'company' => $job
-
>getCompany(),


'location' => $job
-
>getLocation(),


'position' => $job
-
>getPosition(),

))


URL Rewriting

Before getting deeper into the routing system, one matter needs to be clarified. In the
examples given in
the previous section, there is no mention of the front controller (
index.php

or
frontend_dev.php
) in the
internal URIs. The front controller, not the elements of the application, decides the environment. So all the
links must be environme
nt
-
independent, and the front controller name can never appear in internal URIs.

There is no script name in the examples of generated URLs either. This is because generated URLs don't
contain any script name in the production environment by default. The
no
_script_name

parameter of the
settings.yml

file precisely controls the appearance of the front controller name in generated URLs. Set it
to
false
,

prod:


.settings


no_script_name: false

Now, the generated URLs will look like this:


http://www.example.com/index.php/articles/finance/2010/activity
-
breakdown.html

In all environments except the production one, the
no_script_name

parameter is set to
false

by default.
So when you browse your application in the development environment, for

instance, the front controller
name always appears in the URLs.


http://www.example.com/frontend_dev.php/articles/finance/2010/activity
-
breakdown.html

In production, the
no_script_name

is set to
true
, so the URLs show only the routing information and are

more user
-
friendly. No technical information appears.

http://www.example.com/articles/finance/2010/activity
-
breakdown.html

But how does the application know which front controller script to call? This is where URL rewriting
comes in. The web server can be

configured to call a given script when there is none in the URL.

In Apache, this is possible once you have the
mod_rewrite

extension activated. Every symfony project
comes with an
.htaccess

file, which adds
mod_rewrite

settings to your server configuration for the
web/

directory. The default content of this file is shown in Listing 9
-
6.

Listing 9
-
6
-

Default Rewriting Rules for Apache, in
myproject/web/.htaccess

<IfModule mod_rewrite.c>


RewriteEngine On



# uncomment

the following line, if you are having trouble


# getting no_script_name to work


#RewriteBase /



# we skip all files with .something


#RewriteCond %{REQUEST_URI}
\
..+$


#RewriteCond %{REQUEST_URI} !
\
.html$


#RewriteRule .*
-

[L]



# we check if th
e .html version is here (caching)


RewriteRule ^$ index.html [QSA]


RewriteRule ^([^.]+)$ $1.html [QSA]


RewriteCond %{REQUEST_FILENAME} !
-
f



# no, so we redirect to our front web controller


RewriteRule ^(.*)$ index.php [QSA,L]

</IfModule>

The web s
erver inspects the shape of the URLs it receives. If the URL does not contain a suffix
(commented out by default) and if there is no cached version of the page available (Chapter 12 covers
caching), then the request is handed to
index.php
.

However, the
web
/

directory of a symfony project is shared among all the applications and environments
of the project. It means that there is usually more than one front controller in the web directory. For
instance, a project having a
frontend

and a
backend

application,
and a
dev

and
prod

environment,
contains four front controller scripts in the
web/

directory:

index.php // frontend in prod

frontend_dev.php // frontend in dev

backend.php // backend in prod

backend_dev.php // backend in dev

The
mod_rewrite

settings can specify only one default script name. If you set
no_script_name

to
true

for all the applications and environments, all URLs will be interpreted as requests to the
frontend

application in the
prod

environment. This is why you can ha
ve only one application with one environment
taking advantage of the URL rewriting for a given project.

There is a way to have more than one application with no script name. Just create subdirectories in the web
root, and move the front controllers inside
them. Change the path to the
ProjectConfiguration

file
accordingly, and create the
.htaccess

URL rewriting configuration that you need for each application.


Requirements

At the beginning of the book, we talked about validation and error handling for good
reasons. The routing
system has a built
-
in validation feature. Each pattern variable can be validated by a regular expression
defined using the
requirements

entry of a route definition:

job_show_user:


url: /job/:company/:location/:id/:position


param: { module: job, action: show }


requirements:


id:
\
d+

The above
requirements

entry forces the
id

to be a numeric value. If not, the route won't match.


Route Class

Each route defined in routing.yml is internally converted to an object of class

sfRoute
. This class can be
changed by defining a class entry in the route definition. If you are familiar with the HTTP protocol, you
know that it defines several "methods", like GET, POST, HEAD, DELETE, and PUT. The first three are
supported by all brows
ers, while the other two are not.

To restrict a route to only match for certain request methods, you can change the route class to
sfRequestRoute

and add a requirement for the virtual sf_method variable:

job_show_user:


url: /job/:company/:location/:id/:position


class: sfRequestRoute


param: { module: job, action: show }


requirements:


id:
\
d+


sf_method: [get]


Object Route Class

The new internal URI for a job is quite long and tedious to write (
url_for('j
ob/show?id='.$job
-
>getId().'&company='.$job
-
>getCompany().'&location='.$job
-
>getLocation().'&position='.$job
-
>getPosition())
)


For the
job_show_user

route, it is better to use
sfDoctrineRoute

as the class is optimized for routes that
represent Doctrine objects or collections of Doctrine objects


job_show_user:


url: /job/:company/:location/:id/:position


class: sfDoctrineRoute


options: { model: JobeetJob, type: object
}


param: { module: job, action: show }


requirements:


id:
\
d+


sf_method: [get]

The
options

entry customizes the behavior of the route. Here, the
model

option defines the Doctrine model
class (
JobeetJob
) related to the route, and the
type

option defines that this route is tied to one object (you
can also use
list

if a route represents a collection of objects).


The
job_show_user

route is now aware of its relation with
JobeetJob

and so we can simplify the
url_for()
|
url_for()

helper call to:

url_for
(
array
(
'sf_route'

=>
'job_show_user'
,
'sf_subject'

=>
$job
))

or just:

url_for
(
'job_show_user'
,
$job
)



Routing in Actions and Templates

In a template, the
url_for
()

helper converts an internal URI to an external URL. Some other symfony
helpers also take an internal URI as an argument, like the
link_to
()

helper which generates an
<a>

tag


<?php

echo

link_to
(
$job
-
>
getPosition
()
,
'job_show_user'
,
$job
)

?>

It generates t
he following HTML code:

<a href=
"/job/sensio
-
labs/paris
-
france/1/web
-
developer"
>Web Developer</a>

The "redirect" Methods Family

Yesterday, we talked about the "forward" methods. These methods forward the current request to another
action without a round
-
tr
ip with the browser.

The "redirect" methods redirect the user to another URL. As with forward, you can use the
redirect()

method, or the
redirectIf()

and
redirectUnless()

shortcut methods.


List Pagination

To paginate a list of Doctrine objects, symfony provides a dedicated class:
sfDoctrinePager
.


Most of this code deals with the links to other pages. Here are the list of sfDoctrinePager

methods used in
this template:



getResults(): Returns an array of Doctrine objects for the current page



getNbResults(): Returns the total number of results



haveToPaginate(): Returns true if there is more than one page



getLinks(): Returns a list of page lin
ks to display



getPage(): Returns the current page number



getPreviousPage(): Returns the previous page number



getNextPage(): Returns the next page number



getLastPage(): Returns the last page number

As sfDoctrinePager also implements the Iterator and Countab
le interfaces, you can use count() function to
get the number of results instead of the getNbResults() method.

The Form

The form framework is made of three parts:



validation
: The validation sub
-
framework provides classes to validate inputs (integer, string
, email
address, ...)



widgets
: The widget sub
-
framework provides classes to output HTML fields (input, textarea,
select, ...)



forms
: The form classes represent forms made of widgets and validators and provide methods to
help manage the form. Each form fiel
d has its own validator and widget.

Form fields are configured in the
configure()

method

of
sfFormDoctrine

class
, by using the
setValidators()

and
setWidgets()

methods
.



Doctrine Forms

Most of the time, a form has to be serialized to the database. As
symfony already knows everything about
your database model, it can automatically generate forms based on this information

symfony automatically called the
doctrine:build
--
forms

task:

$ php symfony doctrine:build
--
forms

The
doctrine:build
--
forms

task generates form classes in the
lib/form/

directory. The organization of these
generated files is similar to that of
lib/model/
.


Customizing the Job Form


We can customize form property
, validation

and other thing

by using
CLASSNAMEForm

that extends
from
BaseCLASSNAME
Form

on
configure
() method

By default, a Doctrine form displays fields for all the table columns. But for the job form, some of them
must not be editable by the end user. Removing fields from a form is as simple as unsetting

them


// lib/form/doctrine/JobeetJobForm.class.php

class

JobeetJobForm
extends

BaseJobeetJobForm

{


public

function

configure
()


{


unset
(


$this
[
'created_at'
]
,
$this
[
'updated_at'
]
,


$this
[
'expires_at'
]
,
$this
[
'is_activated'
]


)
;


}

}

Unsetting a field means that both the field widget and validator are removed.

Instead of unsetting the fields you don't want to display, you can also explicitly list the fields you want by
using the
useFields
()

method

// lib/form/doctrine/JobeetJobForm.cla
ss.php

class

JobeetJobForm
extends

BaseJobeetJobForm

{


public

function

configure
()


{


$this
-
>
useFields
(
array
(
'category_id'
,
'type'
,
'company'
,
'logo'
,
'url'
,
'position'
,
'location'
,
'description'
,
'how_to_apply'
,
'token'
,
'is_public'
,
'email'
))
;


}

}

It is almost always better to add the new validator to the existing ones by using the special
sfValidatorAnd

validator

$this
-
>
validatorSchema
[
'email'
]

=
new

sfValidatorAnd
(
array
(


$this
-
>
validatorSchema
[
'email'
]
,


new

sfValidatorEmail
()
,


))
;

We
can use $this
-
>validatorSchema[‘ID’] to access specific validation on unique input on the form

Even if the
type

column is also a
varchar

in the schema, we want its value to be restricted to a list of
choices: full time, part time, or freelance.

First, let'
s define the possible values in
JobeetJobTable
:

// lib/model/doctrine/JobeetJobTable.class.php

class

JobeetJobTable
extends

Doctrine_Table

{


static

public

$types

=
array
(


'full
-
time'

=>
'Full time'
,


'part
-
time'

=>
'Part time'
,


'freelance'

=>
'Freelance'
,


)
;




public

function

getTypes
()


{


return

self::
$types
;


}




// ...

}

Then, use
sfWidgetFormChoice

for the
type

widget:

$this
-
>
widgetSchema
[
'type'
]

=
new

sfWidgetFormChoice
(
array
(


'choices'

=> Doctrine_Core::
getTable
(
'JobeetJob'
)
-
>
getTypes
()
,


'expanded'

=>
true
,

))
;


We can use $this
-
>widgetSchema[‘ID’] to access
output HTML fields

on the form

sfWidgetFormChoice

represents a choice widget which can be rendered by a different widget according
to some configuration options (
expanded

and
multiple
):



Dropdown list (
<select>
):
array('multiple' => false, 'expanded' => false)



Dropdown box (
<select multiple="multiple">
):
array('multiple' => true, 'expanded' => false)



List of radio buttons:
array('multiple' => false, 'expanded' => tr
ue)



List of checkboxes:
array('multiple' => true, 'expanded' => true)



// Dropdown list (select)

$form
-
>
setWidget
(
'country'
,
new

sfWidgetFormChoice
(
array
(


'choices'

=>
array
(
''

=>
'Select from the list'
,
'us'

=>
'USA'
,
'ca'

=>
'Canada'
,
'uk'

=>
'UK'
,
'other'
)
,


'default'

=>
'uk'

)))
;

// symfony renders the widget in HTML as

<label
for
=
"country"
>Country</label>

<select id=
"country"

name=
"country"
>


<option value=
""
>Select from the list</option>


<option value=
"us"
>USA</option>


<option value=
"ca"
>Canada</option>


<option value=
"uk"

selected=
"selected"
>UK</option>


<option value=
"0"
>other</option>

</select>



// Dropdown list with multiple choices

$form
-
>
setWidget
(
'languages'
,
new

sfWidgetFormChoice
(
array
(


'multiple'

=>
'true'
,


'choices'


=>
array
(
'en'

=>
'English'
,
'fr'

=>
'French'
,
'other'
)
,


'default'

=>
array
(
'en'
,
0
)

)))
;

// symfony renders the widget in HTML as

<label
for
=
"languages"
>Language</label>

<select id=
"languages"

multiple=
"multiple"

name=
"languages[]"
>


<option value=
"en"

selected=
"selected"
>English</option>


<option value=
"fr"
>French</option>


<option value=
"0"

selected=
"selected"
>other</option>

</select>



// List of Radio buttons

$form
-
>
setWidget
(
'gender'
,
new

sfWidgetFormChoice
(
array
(


'expanded'

=>
true
,


'choices'

=>
array
(
'm'

=>
'Male'
,
'f'

=>
'Female'
)
,


'class'

=>
'gender_list'

)))
;

// symfony renders the widget in HTML as

<label
for
=
"gender"
>Gender</label>

<ul
class
=
"gender_list"
>


<li><input type=
"radio"

name=
"gender"

id=
"gender_m"

value=
"m"
><
label
for
=
"gender_m"
>Male</label></li>


<li><input type=
"radio"

name=
"gender"

id=
"gender_f"

value=
"f"
><label
for
=
"gender_f"
>Female</label></li>

</ul>



// List of checkboxes

$form
-
>
setWidget
(
'interests'
,
new

sfWidgetFormChoice
(
array
(


'multiple'

=>
'true
'
,


'expanded'

=>
true
,


'choices'

=>
array
(
'Programming'
,
'Other'
)

)))
;

// symfony renders the widget in HTML as

<label
for
=
"interests"
>Interests</label>

<ul
class
=
"interests_list"
>


<li><input type=
"checkbox"

name=
"interests[]"

id=
"interests_0"

value=
"0"
><label
for
=
"interests_0"
>Programming</label></li>


<li><input type=
"checkbox"

name=
"interests[]"

id=
"interests_1"

value=
"1"
><label
for
=
"interests_1"
>Other</label></li>

</ul>


sfValidatorChoice

class use to validate from a range of input

sfWidgetFormInputFile

class used for input file tag

sfValidatorFil
e class use to validate for file upload

$this
-
>
widgetSchema
-
>
setLabels
(array(…))

sets lablel for each input

in the form


The
use_javascripts_for_form
()

and
use_stylesheets_for_form
()

helpers include JavaScript and
stylesheet dependencies needed for the form widgets
.


The
setTemplate
()

method changes the template used for a given action. If the submitted form is not valid,
the
create

and
update

methods use the same template as the
new

and
edit

action respectively to re
-
display
the form with error messages
.


Some note in Form:

$form
[
'name'
]: render HTML input

$form
[
'name'
]
-
>
renderRow
()
: render HTML row for a row
(
returns a set of
<tr>
,
<th>

and
<td>

tags
)

$form
[
'name'
]
-
>
renderRow
(
array
(
'size'

=>
25
,
'class'

=>
'foo'
)
,
'Your Name'
)

: render row with specific HTML attribute


<?php

foreach

(
$form

as

$field
)
:
?>


<li>


<?php

echo

$field
-
>
renderLabel
()

?>


<?php

echo

$field
-
>
render
()

?>


</li>


<?php

endforeach
;
?>

: render

label and input for each widget.


$form[‘name’]
-
> renderError(): rend
er error message in HTML

for validation.

$form[‘name']
-
>getError(): get pure text for validation
.


By default, the <?php echo $form ?> renders the form widgets as table rows.

Most of the

time, you will need to customize the layout of your forms. The form object provides many
useful methods for this customization:

Method

Description

render()

Renders the form (equivalent to the output of


echo $form)
.Render pure HTML tag

renderHiddenFields()

Renders the hidden fields

hasErrors()

Returns true if the form has some errors

hasGlobalErrors()

Returns true if the form has global errors

getGlobalErrors()

Returns an array of global errors

renderGlobalErrors()

Renders the global

errors

The form also behaves like an array of fields. You can access the company field with $form['company'].
The returned object provides methods to render each element of the field:

Method

Description

renderRow()

Renders the field row

render()

Renders the field widget

renderLabel()

Renders the field label

renderError()

Renders the field error messages if any

renderHelp()

Renders the field help message

The echo $form statement is equivalent to:

<?php foreach ($form as $widget): ?>


<
?php echo $widget
-
>renderRow() ?>

<?php endforeach ?>


$frmTargetText
-
>offsetGet('category_id')== $frmTargetText[‘category_id’]


Form Handling With Data Validation

In practice, there is much more to form submission handling than just getting the values
entered by the
user. For most form submissions, the application controller needs to:

1.

Check that the data is conform to a set of predefined rules (required fields, format of the email, etc.)

2.

Optionally transform some of the input data to make it understanda
ble (trim whitespaces, convert
dates to PHP format, etc)

3.

If the data is not valid, display the form again, with error messages where applicable

4.

If the data is correct, do some stuff and then redirect to another action




Install
Plugin

1. Download manually

source code sfJqueryReloadedPlugin, sfJQueryUIPlugin and sfAdminDashPlugin.


2. Extract it into plugin directory like this:


plugins/sfJqueryReloadedPlugin/config


/lib


/web


plugins
/sfJQueryUIPlugin/ (similar like above)


plugins/sfAdminDashPlugin/



3. configuring in ProjectConfiguration.class.php


public function setup()


{


$this
-
>enablePlugins(array(



'sfDoctrinePlugin',



'sfDoctrineGuardPlugin',



'sfJqueryReloadedPlugin',



'sfJQueryUIPlugin',



'sfAdminDashPlugin'


));

4. php symfony plugin:publish
-
assets


5. php symfony cc



User Flashes

A flash is an ephemeral message stored in the user session that will be automatically deleted after t
he very
next request. It is very useful when you need to display a message to the user after a redirect. The admin
generator uses flashes a lot to display feedback to the user whenever a job is saved, deleted, or extended.

$this
-
>
getUser
()
-
>
setFlash
(
'notice'
,
sprintf
(
‘message Text’
)
)
;

The first argument is the identifier of the flash and the second one is the message to display


To check if the page or layout has flash or not. We use
$sf_user
-
>
hasFlash
(
'flashName'
)
;


Some symfony objects are always accessible in the templates, without the need to explicitly pass them
from the action:
$sf_request
,
$sf_user
, and
$sf_response
.

A user session can be identified using a cookie. In symfony, the developer does not need to manipulate the
session directly, but rather uses the
sfUser

object, which represents the application end user

Every template can call methods of the
$sf_request
,
$s
f_params
,
$sf_response
, and
$sf_user

objects.
They contain data related to the current request, request parameters, response, and session. You will soon
learn how to use them efficiently.


User Attributes

sfUser::
getAttribute()
,
sfUser::
setAttribute()

Given an identifier, the
sfUser::getAttribute()

method fetches values from the user session. Conversely,
the
setAttribute()

method stores any PHP variable in the session for a given identifier.

The
getAttribute()

method also takes an optional default value

to return if the identifier is not yet defined.

The
myUser

class

To better respect the separation of concerns, let's move the code to the
myUser

class. The
myUser

class
overrides the default symfony base
sfUser

class with application specific
behaviors

This class is extended with
sfBasicSecurityUser
, and is exten
de
d with
sfGuardSecurityUser

for
sfGuardPlugin
.
When we call
$this

来瑕獥r⠠)

in sfAct
ion class or
$sf_user

in php view page, it will
return the object of
myUser

class
.

sfParameterHolder

User's attributes are managed by an object of class
sfParameterHolder
. The
getAttribute()

and
setAttribute()

methods are proxy methods for
getParameterHolder()
-
>
get()

and
getParameterHolder()
-
>set()
. As the
remove()

method has no proxy method in
sfUser
, you need to use the parameter holder
object directly

The
sfParameter
Holder

class is also used by
sfRequest

to store its parameters.

Web service

For this route(routing.yml), the special
sf_format

variable ends the URL and the valid values are
xml
,
json
, or
yaml
.


The view file format like: actionSuccess.formatFile.php

formatFile: html, xml, yaml, …By default is html


We base on routing URL format to decide what file will be render to client.

For example:

api_jobs:


url: /api/:token/jobs.:sf_format


class: sfPropelRoute


param: { module: api, action: list }


options: { model: JobeetJob, type: list, method: getForToken }


requirements:


sf_format: (?:xml|json|yaml)


/api/12343/jobs.xml : will redender to XML file

Plugins

A symfony plugin offers a way to package and distribute a subset of your project fil
es. Like a project, a
plugin can contain classes, helpers, configuration, tasks, modules, schemas, and even web assets.

Private Plugins

The first usage of plugins is to ease sharing code between your applications, or even between different
projects. Recall

that symfony applications only share the model. Plugins provide a way to share more
components between applications.

Public Plugins

Public plugins are available for the community to download and install.

T
hey are exactly the same as
private plugins. The
only difference is that anybody can install them for their projects.

Plugin File Structure

sfJobeetPlugin/


config/


sfJobeetPluginConfiguration.class.php // Plugin initialization


schema.yml // Database schema


routing
.yml // Routing


lib/


Jobeet.class.php // Classes


helper/ // Helpers


filter/ // Filter classes


form/ // Form classes


model/ // Model classes


task/ // Tasks


modules/


job/ // Modules


actions/


con
fig/


templates/


web/ // Assets like JS, CSS, and images


The Cache

By defaul
t, the template cache

feature of symfony is enabled in the
settings.yml

configuration file for the
prod

environment, but not for the
tes
t

and
dev

ones

After you configures on
setting.yml

file, we must clear cache for applying new setting

Cache Configuration

The symfony template cache can be configured with the
cache.yml

configuration file. The default
configuration for the application is to be found in
apps/frontend/config/cache.yml

default:


enabled: false


with_layout: false


lifetime: 86400

By default, as all pages can contain dynamic information, the cache
is globally disabled (
enabled: false
).
We don't need to change this setting, because we will enable the cache on a page by page basis.

The
lifetime

setting defines the server side life time of the cache in seconds (
86400

seconds equals one
day).

Page Cache

As the Jobeet homepage will probably be the most visited page of the website, instead of requesting data
from the database each time a user accesses it, it can be cached.

In

cache.yml

file
add following code
:

//cache for
index action with
indexSuccess.php

file

index:


enabled: true


with_layout: true

If you refresh your browser, you will see that symfony has decorated the page with a box indicating that
the content has been cached

The box gives some precious information about the cache key for debugg
ing, like the lifetime of the cache,
and the age of it.

If you refresh the page again, the color of the box changed from green to yellow, indicating that the page
has been retrieved from the cache:

When a page is cacheable, and if the cache does not exist

yet, symfony stores the response object in the
cache at the end of the request. For all other future requests, symfony will send the cached response
without calling the controller:



Action Cache


Sometimes, you cannot cache the whole page in the cache, b
ut the action template itself can be
cached. Put another way, you can cache everything but the layout.

We will setup with_layout is
false

index:


enabled: true


with_layout: false




Partial and Component Cache

For highly dynamic websites, it is
sometimes even impossible to cache the whole action template. For
those cases, you need a way to configure the cache at the finer
-
grained level. Thankfully, partials and
components can also be cached.

Configuring the cache for a partial or a component is a
s simple as adding an entry with its name. The
with_layout

option is not taken into account for this type of cache as it does not make any sense:

_list:


enabled: true


Removing the Cache

Each time a user posts and activates a job, the homepage must be r
efreshed to list the new job.

As we don't need the job to appear in real
-
time on the homepage, the best strategy is to lower the cache life
time to something acceptable:

# plugins/sfJobeetPlugin/modules/sfJobeetJob/config/cache.yml

index:


enabled: true


lifetime: 600

But if you want to update the homepage as soon as a user activates a new job, edit the
executePublish()

method of the
sfJobeetJob

module to add manual cache cleaning:

// plugins/sfJobeetPlugin/modules/sfJobeetJob/actions/actions.class.php

p
ublic

function

executePublish
(
sfWebRequest
$request
)

{


$request
-
>
checkCSRFProtection
()
;




$job

=
$this
-
>
getRoute
()
-
>
getObject
()
;


$job
-
>
publish
()
;




if

(
$cache

=
$this
-
>
getContext
()
-
>
getViewCacheManager
())


{


$cache
-
>
remove
(
'sfJobeetJob/index?sf_culture=*'
)
;


$cache
-
>
remove
(
'sfJobeetCategory/show?id='
.
$job
-
>
getJobeetCategory
()
-
>
getId
())
;


}




$this
-
>
getUser
()
-
>
setFlash
(
'notice'
,
sprintf
(
'Your job is now online for %s days.'
,
sfConfig::
get
(
'app_active_days'
)))
;




$this
-
>
redirect
(
$this
-
>
generateUrl
(
'job_show_user'
,
$job
))
;

The cache is managed by the
sfViewCacheManager

class. The
remove()

method removes the cache
associated with an internal URI. To remove cache for all possible parameters of a variable, use the
*

as the
value. The
sf_culture=*

we have used in the code above means that symfony will remove the cache for
the English and the French homepage.

As the cache manager is
null

when the cache is disabled, we have wrapped the cache removing in an
if

block.

Per
mission in sfGuardPlugin

create a default user in sfGuardPlugin:

$ symfony guard:create
-
user tdat tdat

When a user is authenticated, the access to some actions can be even more restricted by defining
credentials

in
security.yml

file
. When credentials are d
efined, a user must have the required credentials to
access the action:

all:


is_secure: true


credentials: admin

The credential system of symfony is simple and powerful. A credential is a string that can represent
anything you need to describe the app
lication security model (like groups or permissions).

The
credentials

key supports Boolean operations to describe complex credential requirements by using
the notation array.

If a user must have the credential A
and

the credential B, wrap the credentials with square brackets:

index:


credentials: [A, B]

If a user must have credential the A
or

the credential B, wrap them with two pairs of square brackets:

index:


credentials: [[A, B]]

You can also mix and match brac
kets to describe any kind of Boolean expression with any number of
credentials.

sfGuardSecurityUser

class


This class inherits from the
sfBasicSecurityUser

class from
Symfony

and is used for the
user

object
(typically of class
myUser
) in your
Symfony

application (because you configured it in
factories.yml

already).

So, to access it, you can use the standard
$t
his
-
>getUser()

in your actions or
$sf_user

in your templates.

sfGuardSecurityUser

adds some methods:



signIn()

and
signOut()

methods



getGuardUser()

that returns the
sfGuardUser

object



a bunch of proxy methods to access the
sfGuardUser

object directly

For example, to get the current username:


$this
-
>getUser()
-
>getGuardUser()
-
>getUsername()



// or via the proxy method


$this
-
>getUser()
-
>getUsername()

It is important to understand the permissions model provided by
sfGuardPlugin
.
Symfony

action
s are
restricted according to each user's
credentials
, as described above. But
sfGuardPlugin

associates users with
permissions. So how do these two marry up?

The answer is provided by
sfGuardSecurityUser

and
sfGuardUser

(via its parent class
PluginsfGuard
User
).
When a user signs in, the
sfGuardSecurityUser::signIn()

method calls
sfGuardUser::getAllPermissionNames()
. All the permission names are granted as
credentials

for the user.
All the hard work is done by
PluginsfGuardUser
, which gets all the permissio
ns granted to the user (from
the
sf_guard_user_permission

table). Then, in addition, it gets all the groups to which the user belongs and
adds all the permissions for each of them (from the
sf_guard_user_group

and
sf_guard_group_permission

tables). Therefo
re, the user has a set of
credentials

consisting of the permission names directly granted to
the user, set
-
unioned with the permission names of all the permission sets of all the groups to which the
user belongs (phew!).

In summary, when you edit a user with the
sfGuardUser

module, you can allocate
credentials

by



ticking the permissions boxes on the form, and also by



allocating the user to groups and granting permissions to those groups.

Ajax

To check a request is
an AJ
AX call,
we use

$request
-
>
isXmlHttpRequest()

method of the request object
.It
returns
true
, if request is

ajax call, otherwise it

will return false.

The
sfAction::
renderPartial()

method used in the action returns the partial as the response instead of the
full template.

$this
-
>
renderPartial
(
'job/list'
,
array
(
'jobs'

=>
$this
-
>
jobs
))
;

We will use the
sfAction::
renderText()

method to render a simple test string

YAML File

For its configuration, symfony uses the YAML format by default, instead of more traditional INI or XML
formats. YAML shows structure through indentation and is fast to write. However, you need to keep a few
conventions in mind when writing YAML files. This

section introduces several of the most prominent
conventions. First of all, never use tabs in YAML files; use spaces instead.

# Never use tabs

all:

-
> mail:

-
>
-
> webmaster: webmaster@example.com


# Use blanks instead

all:


mail:


webmaster: webmast
er@example.com

If your parameters are strings starting or ending with spaces, contain special characters (such as the
"octothorpe" (#) or comma), or key words such as "true, false" (where a string is intended) you must
enclose the value in single quotes

er
ror1: This field is compulsory

error2: ' This field is compulsory '

error3: 'Don''t leave this field blank' # Single quotes must be doubled

error4: 'Enter a # symbol to define an extension number'

i18n: 'false' # if we left off the quotes here, a boo
lean false would be returned


You can define long strings in multiple lines, and also multiple
-
line strings, with the special string headers
(
>

and
|
) plus an additional indentation.


# Folded style, introduced by >

# Each line break is folded to a space

#

Makes YAML more readable

accomplishment: >


Mark set a major league


home run record in 1998.


# Literal style, introduced by |

# All line breaks count

# Indentation doesn't appear in the resulting string

stats: |


65 Home Runs


0.278 Batting Average

To define a value as an array, enclose the elements in square brackets or use the expanded syntax with
dashes,

# Shorthand syntax for arrays

players: [ Mark McGwire, Sammy Sosa, Ken Griffey ]


# Expanded syntax for arrays

players:


-

Mark McGwire


-

Samm
y Sosa


-

Ken Griffey

To define a value as an associative array, or hash, enclose the elements in curly brackets and always insert
a spaces between the key and the value in the
key: value

pair, and any list items separated by commas

# Incorrect syntax, bl
anks are missing after the colons and comma

mail: {webmaster:webmaster@example.com,contact:contact@example.com}


# Correct shorthand syntax for associative arrays

mail: { webmaster: webmaster@example.com, contact: contact@example.com }


# Expanded syntax f
or associative arrays

mail:


webmaster: webmaster@example.com


contact: contact@example.com

To give a Boolean value, you can use the
false

and
true

values, provided they are not enclosed in quotes

Don't hesitate to add comments (starting with the hash mark,
#
) and extra spaces to values to make your
YAML files more readable

This means that a property can be defined several times, and the actual value results from a definition
cascade. A parameter
definition in a named environment has precedence over the same parameter
definition for all environments, which has precedence over a definition in the default configuration. A
parameter definition at the module level has precedence over the same parameter

definition at the
application level, which has precedence over a definition at the project level. This can be wrapped up in
the following priority list:

1.

Module

2.

Application

3.

Project

4.

Specific environment

5.

All environments

6.

Default


The Configuration Cache

Pars
ing YAML and dealing with the configuration cascade at runtime represent a significant overhead for
each request. Symfony has a built
-
in configuration cache mechanism designed to speed up requests.

The configuration files, whatever their format, are proces
sed by some special classes, called handlers, that
transform them into fast
-
processing PHP code. In the development environment, the handlers check the
configuration for changes at each request, to promote interactivity. They parse the recently modified fi
les
so that you can see a change in a YAML file immediately. But in the production environment, the
processing occurs once during the first request, and then the processed PHP code is stored in the cache for
subsequent requests. The performance is guarante
ed, since every request in production will just execute
some well
-
optimized PHP code.

For instance, if the
app.yml

file contains this:

all: # Setting for all environments


mail:


webmaster: webmaster@example.com

then the file
config_app.yml.php
, located in the
cache/

folder of your project, will contain this:

<?php



sfConfig::
add
(
array
(


'app_mail_webmaster'

=>
'webmaster@example.com'
,

))
;

As a consequence, most of the time, the YAML files aren't even parsed by the framework,

which relies on
the configuration cache instead. However, in the development environment, symfony will systematically
compare the dates of modification of the YAML files and the cached files, and reprocess only the ones that
have changed since the previou
s request.

This presents a major advantage over many PHP frameworks, where configuration files are compiled at
every request, even in production. Unlike Java, PHP doesn't share an execution context between requests.
For other PHP frameworks, keeping the
flexibility of XML configuration files requires a major
performance hit to process all the configuration at every request. This is not the case in symfony. Thanks
to the cache system, the overhead caused by configuration is very low.

There is an important
consequence of this mechanism. If you change the configuration in the production
environment, you need to force the reparsing of all the configuration files for your modification to be taken
into account. For that, you just need to clear the cache, either
by deleting the content of the
cache/

directory or, more easily, by calling the
cache:clear

task:

$ php symfony cache:clear

Accessing the Configuration from Code

All the configuration files are eventually transformed into PHP, and many of the settings they

contain are
automatically used by the framework, without further intervention. However, you sometimes need to
access some of the settings defined in the configuration files from your code (in actions, templates, custom
classes, and so on). The settings de
fined in
settings.yml
,
app.yml
, and
module.yml

are available
through a special class called
sfConfig
.

The
sfConfig

Class

You can access settings from within the application code through the
sfConfig

class. It is a registry for
configuration parameters, with a simple getter class method, accessible from every part of the code:

// Retrieve a setting

$parameter

= sfConfig::
get
(
'param_name'
,
$default_value
)
;

Note that you can also define, or override, a
setting from within PHP code:

// Define a setting

sfConfig::
set
(
'param_name'
,
$value
)
;

The parameter name is the concatenation of several elements, separated by underscores, in this order:



A prefix related to the configuration file name (
sf_

for
settings.y
ml
,
app_

for
app.yml
,
mod_

for
module.yml
)



The parent keys (if defined), in lowercase



The name of the key, in lowercase

The environment is not included, since your PHP code will have access only to the values defined for the
environment in which it's execu
ted.

For instance, if you need to access the values defined in the
app.yml

file shown in Listing 5
-
15, you will
need the code shown in Listing 5
-
16.

Listing 5
-
15
-

Sample
app.yml

Configuration

all:


.general:


tax: 19.6


default_user:


name: John Doe


mail:


webmaster: webmaster@example.com


contact: contact@example.com

dev:


mail:


webmaster: dummy@example.com


contact: dummy@example.com

Listing 5
-
16
-

Accessing Configuration Settings in PHP in
the
dev

Environment

echo

sfConfig::
get
(
'app_tax'
)
;
// Remember that category headers are ignored


=>
'19.6'

echo

sfConfig::
get
(
'app_default_user_name'
)
;


=>
'John Doe'

echo

sfConfig::
get
(
'app_mail_webmaster'
)
;


=>
'dummy@example.com'

echo

sfConfig::
get
(
'app_mail_contact'
)
;


=>
'dummy@example.com'

So symfony configuration settings have all the advantages of PHP constants, but without the
disadvantages, since the value can be changed.

On that account, the
settings.yml

file, where you can set the framework
settings for an application, is the
equivalent to a list of
sfConfig::set()

calls. Listing 5
-
17 is interpreted as shown in Listing 5
-
18.

Listing 5
-
17
-

Extract of
settings.yml

all:


.settings:


csrf_secret: FooBar


escaping_strategy: true


escaping_method: ESC_SPECIALCHARS

Listing 5
-
18
-

What Symfony Does When Parsing
settings.yml

sfConfig::
add
(
array
(


'sf_csrf_secret'

=>
'FooBar'
,


'sf_escaping_strategy'

=>
true
,


'sf_escaping_method'

=>
'ESC_SPECIALCHARS'
,

))
;


Action Termination

Various behaviors are possible at the conclusion of an action's execution. The value returned by the action
method determines how the view will be rendered. Constants of the
sfView

class are used to specify which
template is to be used to display the resul
t of the action.

If there is a default view to call (this is the most common case), the action should end as follows:

return

sfView::
SUCCESS
;

Symfony will then look for a template called
actionNameSuccess.php
. This is defined as the default
action behavior, so if you omit the
return

statement in an action method, symfony will also look for an
actionNameSuccess.php

template. Empty actions will also trigger that behavior. See Listing 6
-
8 for
examples of successf
ul action termination.

Listing 6
-
8
-

Actions That Will Call the
indexSuccess.php

and
listSuccess.php

Templates

public

function

executeIndex
()

{


return

sfView::
SUCCESS
;

}



public

function

executeList
()

{

}

If there is an error view to call, the action sh
ould end like this:

return

sfView::
ERROR
;

Symfony will then look for a template called
actionNameError.php
.

To call a custom view, use this ending:

return

'MyResult'
;

Symfony will then look for a template called
actionNameMyResult.php
.

If there is no view
to call
--
for instance, in the case of an action executed in a batch process
--
the action
should end as follows:

return

sfView::
NONE
;

No template will be executed in that case. It means that you can bypass completely the view layer and set
the response HTML code directly from an action. As shown in Listing 6
-
9, symfony provides a specific
renderText()

method for this case. This can be us
eful when you need extreme responsiveness of the
action, such as for Ajax interactions, which will be discussed in Chapter 11.

Listing 6
-
9
-

Bypassing the View by Echoing the Response and Returning
sfView::NONE

public

function

executeIndex
()

{


$this
-
>
get
Response
()
-
>
setContent
(
"<html><body>Hello, World!</body></html>"
)
;




return

sfView::
NONE
;

}



// Is equivalent to

public

function

executeIndex
()

{


return

$this
-
>
renderText
(
"<html><body>Hello, World!</body></html>"
)
;

}

In some cases, you need to send an empty response but with some headers defined in it (especially the
X
-
JSON

header). Define the headers via the
sfResponse

object, discussed in the next chapter, and return the
sfView::HEADER_ONLY

constant, as shown in List
ing 6
-
10.

Listing 6
-
10
-

Escaping View Rendering and Sending Only Headers

public

function

executeRefresh
()

{


$output

=
'<"title","My basic letter"],["name","Mr Brown">'
;


$this
-
>
getResponse
()
-
>
setHttpHeader
(
"X
-
JSON"
,
'('
.
$output
.
')'
)
;




return

sfView:
:
HEADER_ONLY
;

}

If the action must be rendered by a specific template, ignore the
return

statement and use the
setTemplate()

method instead.

public

function

executeIndex
()

{


$this
-
>
setTemplate
(
'myCustomTemplate'
)
;

}

With this code, symfony will look for a
myCustomTemplateSuccess.php

file, instead of
indexSuccess.php
.

Skipping to Another Action

In some cases, the action execution ends by requesting a new action execution. For instance, an action
handling a form submiss
ion in a POST request usually redirects to another action after updating the
database.

The action class provides two methods to execute another action:



If the action forwards the call to another action:

$this
-
>
forward
(
'otherModule'
,
'index'
)
;



If the action

results in a web redirection:

$this
-
>
redirect
(
'otherModule/index'
)
;

$this
-
>
redirect
(
'http://www.google.com/'
)
;

The code located after a forward or a redirect in an action is never executed. You can consider that these
calls are equivalent to a
return

stat
ement. They throw an
sfStopException

to stop the execution of the
action; this exception is later caught by symfony and simply ignored.

The choice between a redirect or a forward is sometimes tricky. To choose the best solution, keep in
mind that a forward

is internal to the application and transparent to the user. As far as the user is
concerned, the displayed URL is the same as the one requested. In contrast, a redirect is a message
to the user's browser, involving a new request from it and a change in th
e final resulting URL.

If the action is called from a submitted form with
method="post"
, you should
always

do a redirect.
The main advantage is that if the user refreshes the resulting page, the form will not be submitted
again; in addition, the back button works as expected by displaying the form and not an alert asking
the user if he wants to resubmit a POST

request
.

There is a special kind of forward that is used very commonly. The
forward404()

method forwards to a
"page not found" action. This method is often called when a parameter necessary to the action execution is
not present in the request (thus detec
ting a wrongly typed URL). Listing 6
-
11 shows an example of a
show

action expecting an
id

parameter.

public

function

executeShow
(
sfWebRequest
$request
)

{


// Doctrine


$article

= Doctrine::
getTable
(
'Article'
)
-
>
find
(
$request
-
>
getParameter
(
'id'
))
;




// P
ropel


$article

= ArticlePeer::
retrieveByPK
(
$request
-
>
getParameter
(
'id'
))
;




if

(
!
$article
)


{


$this
-
>
forward404
()
;


}

}

If you are looking for the error 404 action and template, you will find them in the
$sf_symfony_
lib_dir/controller/default/

directory. You can customize this page by adding a new
default

module to your
application, overriding the one located in the framework, and by defining an
error404

action and an
error404Success template inside. Alternatively, you can set the
error_404_mod
ule

and
error_404_action

constants in the
settings.yml

file to use an existing action.

OK, so basically a submitted form should be redirected by the action (as opposed to doing a forward). In
other situations where you’re calling part of your own applicati
on, just use a forward().


Session Management

Symfony's session
-
handling feature completely masks the client and server storage of the session IDs to
the developer. However, if you want to modify the default behaviors of the session
-
management
mechanisms,
it is still possible. This is mostly for advanced users.

On the client side, sessions are handled by cookies. The symfony session cookie is called
symfony
, but you
can change its name by editing the
factories.yml

configuration file, as shown in Listing 6
-
1
7.

Listing 6
-
17
-

Changing the Session Cookie Name, in
apps/frontend/config/factories.yml

all:


storage:


class: sfSessionStorage


param:


session_name: my_cookie_name

The session is started (with the PHP function
session_start()
) only if the
a
uto_start

parameter is set
to true in
factories.yml

(which is the case by default). If you want to start the user session manually,
disable this setting of the storage factory.

Symfony's session handling is based on PHP sessions. This means that if you wan
t the client
-
side
management of sessions to be handled by URL parameters instead of cookies, you just need to change the
use_trans_sid

setting in your php.ini. Be aware that this is not recommended.

session.use_trans_sid = 1

On the server side, symfony sto
res user sessions in files by default. You can store them in your database by
changing the value of the
class

parameter in
factories.yml
, as shown in Listing 6
-
18.

Listing 6
-
18
-

Changing the Server Session Storage, in
apps/frontend/config/factories.yml

al
l:


storage:


class: sfMySQLSessionStorage


param:


db_table: session # Name of the table storing the sessions


database: propel # Name of the database connection to use


# Optional parameters


db_id_col: sess_id # Name of the column storing the session id


db_data_col: sess_data # Name of the column storing the session data


db_time_col: sess_time # Name of the column storing the session
times
tamp

The
database

setting defines the database connection to be used. Symfony will then use
databases.yml

(see Chapter 8) to determine the connection settings (host, database name, user, and password) for this
connection.

The available session storage classes are
sfCacheSessionStorage
,
sfMySQLSessionStorage
,
sfMySQLiSessionStorage
,
sfPostgreSQLSessionStorage
, and
sfPDOSessionStorage
; the latter is
preferred. To disable session storage completely, you can use the
sfNoStorage

class.

Session expiration occurs automatically after 30 minutes. This default setting can be modified for each
environment in the same
factories.yml

configuration file, but this time in the
user

factory, as shown in
Listing 6
-
19.

Listing 6
-
19
-

Changing S
ession Lifetime, in
apps/frontend/config/factories.yml

all:


user:


class: myUser


param:


timeout: 1800 # Session lifetime in seconds




Helpers

Helpers are PHP functions that return HTML code and can be used in templates.

He
lpers facilitate the
process of writing templates and produce the best possible HTML code in terms of performance and
accessibility. You can always use plain HTML, but helpers are usually faster to write.

Declaring Helpers

The symfony files containing help
er definitions are not autoloaded (since they contain functions, not
classes). Helpers are grouped by purpose. For instance, all the helper functions dealing with text are
defined in a file called
TextHelper.php
, called the
Text

helper group. So if you need to use a helper in a
template, you must load the related helper group earlier in the template by declaring it with the
use_helper()

function

A few helpers are available by default in every template, without need for declaratio
n. These are helpers of
the following helper groups:



Helper: Required for helper inclusion (the use_helper() function is, in fact, a helper itself)



Tag: Basic tag helper, used by almost every helper



Url: Links and URL management helpers



Asset: Helpers
populating the HTML <head> section, and providing easy links to external assets
(images, JavaScript, and style sheet files)



Partial: Helpers allowing for inclusion of template fragments



Cache: Manipulation of cached code fragments

The list of the standard
helpers, loaded by default for every template, is configurable in the
settings.yml

file. So if you know that you will not use the helpers of the
Cache

group, or that you will always use the
ones of the Text group, modify the
standard_helpers

setting accord
ingly. This will speed up your
application a bit. You cannot remove the first four helper groups in the preceding list (
Helper
,
Tag
,
Url
,
and
Asset
), because they are compulsory for the templating engine to work properly. Consequently, they
don't even appe
ar in the list of standard helpers.

Adding Your Own Helpers

Symfony ships with a lot of helpers for various purposes, but if you don't find what you need in the API
documentation, you will probably want to create a new helper. This is very easy to do.

Help
er functions (regular PHP functions returning HTML code) should be saved in a file called
FooBarHelper.php
, where
FooBar

is the name of the helper group. Store the file in the
apps/frontend/lib/helper/

directory (or in any
helper/

directory created under o
ne of the
lib/

folders of your
project) so it can be found automatically by the
use_helper('FooBar')

helper for inclusion.

This system even allows you to override the existing symfony helpers. For instance, to redefine all the
helpers of the
Text

helper gr
oup, just create a
TextHelper.php

file in your
apps/frontend/lib/helper/

directory. Whenever you call
use_helper('Text')
, symfony will use your helper group rather than its own.
But be careful: as the original file is not even loaded, you must redefine all

the functions of a helper group
to override it; otherwise, some of the original helpers will not be available at all.



Code Fragments

Symfony provides three alternative types of intelligent code fragments to replace
include
s:



If the logic is lightweight,

you will just want to include a template file having access to some data
you pass to it. For that, you will use a partial.



If the logic is heavier (for instance, if you need to access the data model and/or modify the content
according to the session), you

will prefer to separate the presentation from the logic. For that, you
will use a component.



If the fragment is meant to replace a specific part of the layout, for which default content may
already exist, you will use a slot

Partials

Notice that we have copied and pasted the
<table>

tag that create a list of jobs from the job
indexSuccess.php

template. That's bad. Time to learn a new trick. When you need to reuse some portion of
a template, you need to create a
partial
. A partial is a
snippet of template code that can be shared among
several templates. A partial is just another template that starts with an underscore (
_
).


You can include a partial by using the
include_partial
()

helper:

<?php

include_partial
(
'job/list'
,
array
(
'jobs'

=>
$jobs
))

?>

The first argument of
include_partial
()

is the partial name (made of the module name, a
/
, and the partial
name without the leading
_
). The second argument is an array of variables to pass to the partial
.


Instead of resulting in a template, an action can return a partial or a component. The
renderPartial()

and
renderComponent()

methods of the action class promote reusability of code.

Besides, they take advantage of the caching abilities of the partials. The

variables defined in the action will
be automatically passed to the partial/component, unless you define an associative array of variables as a
second parameter of the method.


public

function

executeFoo
()

{


// do things


$this
-
>
foo

=
1234
;


$this
-
>
ba
r

=
4567
;




return

$this
-
>
renderPartial
(
'mymodule/mypartial'
)
;

}


Slots
:

In symfony, when a zone of the layout depends on the template to be displayed, you need to define a slot










Add a slot to the layout to allow the title to be dynamic:

//
apps/frontend/templates/layout.php

<title><?php include_slot
(
'title'
)

?></title>

Each slot is defined by a name (
title
) and can be displayed by using the
include_slot()

helper. Now,
at the beginning of the
showSuccess.php

template, use the
slot()

helper to define the content of the slot
for the job page:

// apps/frontend/modules/job/templates/showSuccess.php

<?php

slot
(

'title'
,


sprintf
(
'%s is looking for a %s'
,
$job
-
>
getCompany
()
,
$job
-
>
getPosition
()))

?>

If the title is complex to generate, the
slot()

helper can also be used with a block of code:

// apps/frontend/modules/job/templates/showSuccess.php

<?php

slot
(
'title'
)

?>


<?php

echo

sprintf
(
'%s is looking for a %s'
,
$job
-
>
getCompany
()
,
$job
-
>
getPosition
())

?>

<?php

end_slot
()

?>

For some pages, like the homepage, we just need a generic title. Instead of repeating the same title over
and over again in templates, we can define a default title in the layout:


// apps/frontend/templates/layout.php

<title>


<?php

include_slot
(
'title'
,
'Jobeet
-

Your best job board'
)

?>

</title>

The second argument of the
include_slot(
)

method is the default value for the slot if it has not been
defined. If the default value is longer or has some HTML tags, you can also define
d it like in the following
code:

// apps/frontend/templates/layout.php

<title>


<?php

if

(
!include_slot
(
'title'
))
:
?>


Jobeet
-

Your best job board


<?php

endif

?>

</title>

The
include_slot
()

helper returns
true

if the slot has been defined. So, when you define the
title

slot
content in a template, it is used; if not, the default title is used.

Components

A component is like an action, except it's much faster. The logic of a component is kept in a class
inheritin
g from
sfComponents
, located in an
actions/components.class.php

file. Its presentation is
kept in a partial. Methods of the
sfComponents

class start with the word
execute
, just like actions, and
they can pass variables to their presentation counterpart in the same way that actions can pass variables.
Partials that serve as presentation for components are named by the component (without the leading
execute
, but with an unde
rscore instead). Table 7
-
1 compares the naming conventions for actions and
components.

Table 7
-
1
-

Action and Component Naming Conventions

Convention

Actions

Components

Logic file

actions.class.php

components.class.php

Logic class extends

sfActions

sfComponents

Method naming

executeMyAction()

executeMyComponent()

Presentation file naming

myActionSuccess.php

_myComponent.php

Now, every time you need the component in a template, just call this:

<?php

include_component
(
'news'
,
'headlines'
)

?>

// Call

to the component


// Call to the component

<?php

include_component
(
'news'
,
'headlines'
,
array
(
'foo'

=>
'bar'
))

?>



// In the component itself

echo

$this
-
>
foo
;


=>
'bar'



// In the _headlines.php partial

echo

$foo
;


=>
'bar'

You can include components in components, or in the global layout, as in any regular template. Like
actions, components'
execute

methods can pass variables to the related partial and have access to the same
shortcuts. But the similarities stop there. A com
ponent doesn't handle security or validation, cannot be
called from the Internet (only from the application itself), and doesn't have various return possibilities.
That's why a component is faster to execute than an action.

Doctrine Modal

The
Doctrine_Quer
y

class
:
Doctrine_Query object represents a DQL query. It is used to query databases
for data in an object
-
oriented fashion. A DQL query understands relations and inheritance and is dbms
independant.

The
Doctrine_Core

class

: The base core class of Doctrine

public

function

executeIndex
(
sfWebRequest
$request
)

{


$q

= Doctrine_Query::
create
()


-
>
from
(
'JobeetJob j'
)


-
>
where
(
'j.created_at > ?'
,
date
(
'Y
-
m
-
d H:i:s'
,
time
()

-

86400

*
30
))
;




$this
-
>
jobeet_jobs

=
$q
-
>
execu
te
()
;

}


As you don't write the SQL statements by hand, Doctrine will take care of the differences between
database engines and will generate SQL statements optimized for the database engine you choose during
day 3. But sometimes, it is of great help to se
e the SQL generated by Doctrine; for instance, to debug a
query that does not work as expected. In the
dev

environment, symfony logs these queries (along with
much more) in the
log/

directory

The
app.yml

configuration file is a great way to centralize global settings for your application.

Last, if you need project
-
wide settings, just create a new
app.yml

file in the
config

folder at the root of
your symfony project
.

Why keep two versions of the data objec
t model in two different directories?

You will probably need to add custom methods and properties to the model objects (think about the
getName()

method in Listing 8
-
1). But as your project develops, you will also add tables or columns.
Whenever you change

the
schema.yml

file, you need to regenerate the object model classes by making a
new call to doctrine:build
-
model. If your custom methods were written in the classes actually generated,
they would be erased after each generation.

The
Base

classes kept in
the
lib/model/doctrine/base/

directory are the ones directly generated from
the schema. You should never modify them, since every new build of the model will completely erase
these files.

On the other hand, the custom object classes, kept in the
lib/model/
doctrine

directory, actually inherit
from the
Base

ones. When the
doctrine:build
-
model

task is called on an existing model, these classes
are not modified. So this is where you can add custom methods.





Refactoring

In the MVC model, the Model defines all

the business logic, and the Controller only calls the Model to
retrieve data from it.

In Model layer is belong to CLASSNAME
Table
(ex: JobeetJobTable)
, we can write business logic and
access database in this class. Later we can call it from C
ontroller

layer


The process to add a new feature to a symfony website is always the same:
think about the URLs, create
some actions, update the model, and write some templates
. And, if you can apply some good
development practices to the mix, you will become a symfony
master very fast.


Relational and object data model use similar concepts, but they each have their own nomenclature:
















Retrieving Records with Doctrine_Query

When you want to retrieve more than one record, you need to call the
createQuery()

method of the table
class corresponding to the objects you want to retrieve. For instance, to retrieve objects of class
Article
,
call
Doctrine_Core::getTable('Article')
-
>createQuery()
-
>execute()
.

The first parameter of the
execute()

method is an array of
parameters, which is the array of values to
replace any placeholders found in your query.

An empty
Doctrine_Query

returns all the objects of the class

q

= Doctrine_Core::
getTable
(
'Article'
)
-
>
createQuery
()
;

$articles

=
$q
-
>
execute
()
;



// Will result in the

following SQL query

SELECT b.id
AS

b__id, b.title
AS

b__title, b.content
AS

b__content, b.created_at
AS

b__created_at, b.updated_at
AS

b__updated_at FROM blog_article b

The call to

execute()

is actually much more powerful than a simple SQL query. First, the SQL is
optimized for the DBMS you choose. Second, any value passed to the
Doctrine_Query

is escaped before
being integrated into the SQL code, which prevents SQL injection risks. Third, th
e method returns an array
of objects, rather than a result set. The ORM automatically creates and populates objects based on the
database result set. This process is called hydrating.


For a more complex object selection, you need an equivalent of the WHER
E, ORDER BY, GROUP BY,
and other SQL statements. The
Doctrine_Query

object has methods and parameters for all these
conditions. For example, to get all comments written by Steve, ordered by date, build a
Doctrine_Query

as shown in Listing 8
-
13.

Listing 8
-
1
3
-

Retrieving Records by a
Doctrine_Query

with
createQuery()
--
Doctrine_Query with
Conditions

$q

= Doctrine_Core::
getTable
(
'Comment'
)


-
>
createQuery
(
'c'
)


-
>
where
(
'c.author = ?'
,
'Steve'
)


-
>
orderBy
(
'c.created_at ASC'
)
;

$comments

=
$q
-
>
execute
()
;



// W
ill result in the following SQL query

SELECT b.id
AS

b__id, b.article_id
AS

b__article_id, b.author
AS

b__author, b.content
AS

b__content, b.created_at
AS

b__created_at, b.updated_at
AS

b__updated_at FROM
blog_comment b WHERE
(
b.author = ?
)

ORDER BY b.created_at ASC


For a more complex object selection, you need an equivalent of the WHERE, ORDER BY, GROUP BY,
and other SQL statements. The
Doctrine_Query

object has methods and parameters for all these
conditions.

Finally, if you just want th
e first object returned, replace
execute()

with a
fetchOne()

call. This may be the
case when you know that a
Doctrine_Query

will return only one result, and the advantage is that this
method returns an object rather than an array of objects


Using Raw SQL
Queries

Sometimes, you don't want to retrieve objects, but want to get only synthetic results calculated by the
database. For instance, to get the latest creation date of all articles, it doesn't make sense to retrieve all the
articles and to loop on the a
rray. You will prefer to ask the database to return only the result, because it
will skip the object hydrating process.

On the other hand, you don't want to call the PHP commands for database management directly, because
then you would lose the benefit of
database abstraction. This means that you need to bypass the ORM
(Doctrine) but not the database abstraction (PDO).

Querying the database with PHP Data Objects requires that you do the following:

1.

Get a database connection.

2.

Build a query string.

3.

Create a st
atement out of it.

4.

Iterate on the result set that results from the statement execution.

$connection

= Doctrine_Manager::
connection
()
;

$query

=
'SELECT MAX(created_at) AS max FROM blog_article'
;

$statement

=
$connection
-
>
execute
(
$query
)
;

$statement
-
>
execute
()
;

$resultset

=
$statement
-
>
fetch
(
PDO::
FETCH_OBJ
)
;

$max

=
$resultset
-
>
max
;

Using Special Date Columns

Usually, when a table has a column called
created_at
, it is used to store a timestamp of the date when the
record was created. The same applies to update
d_at columns, which are to be updated each time the record
itself is updated, to the value of the current time.

The good news is that Doctrine has a
Timestampable

behavior that will handle their updates for you. You
don't need to manually set the
created_a
t

and
updated_at

columns; they will automatically be updated

Extending the Model


The generated model methods are great but often not sufficient. As soon as you implement your own
business logic, you need to extend it, either by adding new methods or by
overriding existing ones.

Adding New Methods

You can add new methods to the empty model classes generated in the
lib/model/doctrine

directory. Use
$this

to call methods of the current object, and use
self::

to call static methods of the current class.
Remember that the custom classes inherit methods from the
Base

classes located in the
lib/model/doctrine/base

directory.

you can add a magic
__toString()

method so that echoing an object of class

class

Article
extends

BaseArticle

{


public

function

__toString
()


{


return

$this
-
>
getTitle
()
;
// getTitle() is inherited from BaseArticle


}

}

You can also extend the table classes
--
for instance, to add a method to retrieve all articles ordered by
creation date

class

ArticleTable
extends

BaseArticleTable

{


public

function

getAllOrderedByDate
()


{


$q

=
$this
-
>
createQuery
(
'a'
)


-
>
orderBy
(
'a.created_at ASC'
)
;




return

$q
-
>
execute
()
;


}

}




The new methods are available in the same way as the
generated ones

$articles

= Doctrine_Core::
getTable
(
'Article'
)
-
>
getAllOrderedByDate
()
;

foreach

(
$articles

as

$article
)

{


echo

$article
;
// Will call the magic __toString() method

}

Overriding Existing Methods

If some of the generated methods in the
Base

classes don't fit your requirements, you can still override
them in the custom classes. Just make sure that you use the same method signature (that is, the same
number of arguments).

Using Model Behaviors

Some model modifications are generic and can b
e reused. For instance, methods to make a model object
sortable and an optimistic lock to prevent conflicts between concurrent object saving are generic
extensions that can be added to many classes.

Symfony packages these extensions into behaviors. Behavio
rs are external classes that provide additional
methods to model classes. The model classes already contain hooks, and symfony knows how to extend
them.

To enable behaviors in your model classes, you must modify your schema and use the
actAs

option:

Articl
e:


actAs: [Timestampable, Sluggable]


tableName: blog_article


columns:


id:


type: integer


primary: true


autoincrement: true


title: string(255)


content: clob

After rebuilding the model, the
Article

model have a slug colum
n that is automatically set to a url
friendly string based on the title.

Some behaviors that come with Doctrine are:



Timestampable



Sluggable



SoftDelete



Searchable



I18n



Versionable



NestedSet



Dealing with multiple Schemas

You can have more than one schema per application. Symfony will take into account every file ending
with
.yml

in the
config/doctrine

folder. If your application has many models, or if some models don't share
the same connection, you will find this approach

very useful.

Consider these two schemas:

// In config/doctrine/business
-
schema.yml

Article:


id:


type: integer


primary: true


autoincrement: true


title: string(50)



// In config/doctrine/stats
-
schema.yml

Hit:


actAs: [Timestampable]


colu
mns:


id:


type: integer


primary: true


autoincrement: true


resource: string(100)

Both schemas share the same connection (
doctrine
), and the
Article

and
Hit

classes will be generated under
the same
lib/model/doctrine

directory. Everything happens as if you had written only one schema.

You can also have different schemas use different connections (for instance,
doctrine

and
doctrine_bis
, to
be defined in
databases.yml
) and associate it with that connection:

// In confi
g/doctrine/business
-
schema.yml

Article:


connection: doctrine


id:


type: integer


primary: true


autoincrement: true


title: string(50)



// In config/doctrine/stats
-
schema.yml

Hit:


connection: doctrine_bis


actAs: [Timestampable]


columns
:


id:


type: integer


primary: true


autoincrement: true


resource: string(100)

Many applications use more than one schema. In particular, some plug
-
ins have their own schema to avoid
messing with your own classes

T
he column
parameters are as follows:



type
: Column type. The choices are
boolean
,
integer
,
double
,
float
,
decimal
,
string(size)
,
date
,
time
,
timestamp
,
blob
, and
clob
.



notnull
: Boolean. Set it to
true

if you want the column to be required.



length
: The size or length
of the field for types that support it



scale
: Number of decimal places for use with decimal data type (size must also be specified)



default
: Default value.



primary
: Boolean. Set it to
true

for primary keys.



autoincrement
: Boolean. Set it to
true

for column
s of type
integer

that need to take an auto
-
incremented value.



sequence
: Sequence name for databases using sequences for
autoIncrement

columns (for
example, PostgreSQL and Oracle).



unique
: Boolean. Set it to
true

if you want the column to be unique.

Defaul
t Modules and Actions

Symfony provides default pages for special situations. In the case of a routing error, symfony executes an
action of the
default

module, which is stored in the
sfConfig::get('sf_symfony_lib_dir')/controller/default/

directory. The
set
tings.yml

file
defines which action is executed depending on the error:



error_404_module

and
error_404_action
: Action called when the URL entered by the user
doesn't match any route or when an
sfError404Exception

occurs. The default value is
default/error4
04
.



login_module

and
login_action
: Action called when a nonauthenticated user tries to access a
page defined as
secure

in
security.yml

(see Chapter 6 for details). The default value is
default/login
.



secure_module

and
secure_action
: Action called when a user doesn't have the credentials
required for an action. The default value is
default/secure
.



module_disabled_module

and
module_disabled_action
: Action called when a user requests a
module declared as disabled in
module.yml
. The def
ault value is
default/disabled
.

You can override the default pages in two ways:



You can create your own default module in the application's
modules/

directory, override all the
actions defined in the
settings.yml

file (
index
,
error404
,
login
,
secure
,
disab
led
) and all the
related templates (
indexSuccess.php
,
error404Success.php
,
loginSuccess.php
,
secureSuccess.php
,
disabledSuccess.php
).



You can change the default module and action settings of the
settings.yml

file to use pages of
your application.