symfony workshop www.symfony-project.com www.sensiolabs.com
Workshop - Day 1
symfony workshop www.symfony-project.com www.sensiolabs.com
Sensio
✓
Web agency
✓
70 people dedicated to webdevelopment
✓
Open-Source specialists
✓
Customers : big corporate companies
Webmarketing TechnologiesInternet
Sensio
Web Agency
Creator of the
symfony
framework
lundi 15 mars 2010
Symfony
Open-Source PHP 5 Framework
Based on :
➡
11 years of experience
➡
Open-Source projects
Created for :
➡
Professionals
➡
Complex needs
➡
Demanding environments
symfony workshop www.symfony-project.com www.sensiolabs.com
What is a Framework ?
«
Whatever the application, a framework is build
to ease development by providing tools for
recurrent and boring tasks.
»
✓
Generic components
➡
Bundled by default
➡
Well integrated
➡
Optimized for the web
✓
Professional Web Developments
What is a CMS ?
✓
Content Management System
➡
Standard features
➡
Pre-made application
symfony workshop www.symfony-project.com www.sensiolabs.com
Don’t reinvent the wheel and use best practices
Develop faster and better
Develop evolutive and maintenable applications
Become the framework for professionals
Main goals of symfony
Develop better
•
Kent Beck (based on Yourdon and Constantine)
Each line of code has an initial cost
… and a cost to maintain the line of code
Cost
initial
= Cost
development
+ Cost
tests
Cost
maintenance
>>
Cost
initial
Develop faster
✓
Write less code
✓
More time for business rules, edge cases, …
Less code
Less complexity
Less bugs
More productivity
More time
symfony workshop www.symfony-project.com www.sensiolabs.com
Become the framework
for professionals
Main Advantages
✓
Symfony is about code, but also…
➡
An Open-Source Framework
➡
An Open-Source Documentation
➡
An international community
➡
An “Entreprise” version
symfony workshop www.symfony-project.com www.sensiolabs.com
An Open-Source Framework
✓
Released under the MIT Licence
«
It is a permissive license, meaning that it
permits reuse within proprietary software on the
condition that the license is distributed with that
An Open-Source Documentation
✓
Several O
ffi
cial Books
➡
The Definitive Guide (450 pages)
➡
Practical symfony (350 pages)
➡
The Reference Guide (150 pages)
➡
More With symfony (320 pages - 5 translations)
✓
Lots of available translations
✓
Free online
symfony workshop www.symfony-project.com www.sensiolabs.com
A large community
Dedicated Event : the symfony live
✓
February, 2010, the 15th, 16th and 17th
✓
2 days of conferences
✓
+1 Trainings Day with symfony experts
➡
Symfony and Zend Framework together
➡
Symfony and Webservices
➡
What’s new in symfony 1.3 / 1.4
➡
Hosting practices with symfony
➡
Doctrine 1.2
symfony workshop www.symfony-project.com www.sensiolabs.com
An «
Entreprise
» Version
✓
Version 1.4 released in December 2009
➡
~1 release per month
•
Bugs fixes, security and compatibility issues with newer
versions of PHP
•
No new feature added
•
Updating is simple and safe
•
Migrating is safe
•
Supported for 3 years
➡
Commercial support
Entreprise Features
✓
Security
✓
Environments and Deployment support
✓
Unit and functional testing frameworks
✓
Configurable and extensible
✓
Model / View / Controller architecture
✓
Admin Generator
✓
Tools for the developer (debugging, log, queries logging…)
✓
Cache management
✓
Clean and smart URLs
✓
Internationalization (I18N) and localization (L10N)
symfony workshop www.symfony-project.com www.sensiolabs.com
Security
✓
Protected by
default
against most web attacks
➡
Cross Site Scripting (XSS)
➡
Cross Site Request Forgery (CSRF - « Sea Surf »)
➡
SQL Injection
✓
Why ?
➡
Because XSS attacks are really easy to exploit
Environments and deployment
✓
Symfony recognized the need for di
ff
erent
environments:
➡
development,
➡
testing,
➡
production
✓
Easy deployments
symfony project:deploy production --go
symfony workshop www.symfony-project.com www.sensiolabs.com
Unit and Functional Testing
✓
Symfony
automates
functional tests by
simulating a browser
✓
Why ?
➡
Manual tests are not reproducible… Customers
never test their applications
Configurability and Extensibility
✓
Symfony is
configurable
and easily
extensible
✓
Easy to use plugins system
✓
Why ?
➡
The web evolves quickly
➡
Some customers have specific needs
➡
The framework cannot and must not do everything
➡
Ease external contributions
symfony workshop www.symfony-project.com www.sensiolabs.com
MVC Architecture
✓
Better concerns separation:
➡
Business rules (Model)
➡
Templates (View)
✓
File structure
✓
Conventions (naming, formats …)
✓
Why ?
The MVC architecture
✓
Web applications design
✓
3 di
ff
erent layers:
➡
Model: Business logic
➡
View : Presentation
➡
Controller
: Application logic
✓
Advantages:
➡
Better encapsulation
➡
Code is reusable
➡
Robust
controller
view
model
response
client
internet
server
request
lundi 15 mars 2010
Back-O
ffi
ce
✓
A
utomatic
creation of a backend interface
➡
Records lists
➡
Pagination
➡
Sorting
✓
Why ?
➡
All websites have a backend
➡
Boring to develop
➡
Filters
➡
Validation
Tools for the developer
✓
Symfony comes with tools to help debugging
✓
Why ?
➡
To improve the developer's productivity
➡
To ease debugging
symfony workshop www.symfony-project.com www.sensiolabs.com
Cache management
✓
The Symfony cache system is fined-grained
✓
Why ?
symfony workshop www.symfony-project.com www.sensiolabs.com
Clean URLs
✓
symfony decouples URLs et code
✓
Why?
➡
SEO optimization
➡
They are indexed by search engine, copy/pasted in
emails, bookmarked, …
➡
Masks technical implementation
/blog.php?section=symfony&article_id=18475
/blog/2008-01-30/symfony-happy-new-year
i18n
✓
Symfony has built-in support
for i18n and l10n
✓
Why?
symfony workshop www.symfony-project.com www.sensiolabs.com
A professional framework
✓
Based on experience
✓
Stable versions
✓
Maintained with commercial support
✓
Large community
✓
Extensible
✓
Stable API
✓
Open-Source Documentation
✓
Focused on best practices and security
symfony workshop www.symfony-project.com www.sensiolabs.com
Before we begin
✓
Copy the
exercises/
folder in your web root folder
✓
Check your configuration
/exercises/check.php
The exercises/ directory
✓
All exercises for this workshop with solutions
✓
You must do the exercises to learn
symfony workshop www.symfony-project.com www.sensiolabs.com
The Application
✓
A pure and simple PHP application
✓
Attendees management system
➡
List
➡
Create
➡
Delete
➡
Display
Install the application
symfony workshop www.symfony-project.com www.sensiolabs.com
Play with the application
/exercises/attendee/version_0/welcome.php
Play with the application
symfony workshop www.symfony-project.com www.sensiolabs.com
Main features of the application
✓
Flat programming
✓
Typical PHP4 application
✓
No class
✓
No abstraction
… and a lot of problems
Problem n°1: No structure
✓
The structure is not reproducible
➡
Flat structure
➡
Librairies under the main root directory?
•
An empty index.html file must be created to ensure
that the files are not browseable
➡
Librairies?
•
inc/
•
includes/
•
lib/
symfony workshop www.symfony-project.com www.sensiolabs.com
Problem n°2: Duplication
✓
Code duplication / lots of includes
/exercises/attendee/version_0/welcome.php
/exercises/attendee/version_0/attendee.php
Problem n°3: Security
✓
Security is not taken care of:
➡
SQL injection
➡
XSS
➡
CSRF
symfony workshop www.symfony-project.com www.sensiolabs.com
Problem n°3: Security
✓
SQL injection
Problem n°3: Security
✓
SQL injection solution
➡
Variable escaping within SQL statements
symfony workshop www.symfony-project.com www.sensiolabs.com
Problem n°3: Security
✓
XSS - Cross-Site Scripting
Probleme n°3: Security
✓
XSS solution - Cross-Site Scripting
➡
Escape variables when they are used in a template
symfony workshop www.symfony-project.com www.sensiolabs.com
Problem n°4: Edge cases
✓
Edge cases are not taken care of:
➡
the application is not robust
➡
What if a user enter an empty name ou a long one?
•
No data validation
➡
And if the database does not exist?
•
Errors are displayed right in the browser
Problem n°5: Not maintainable
✓
HTML and PHP code are mixed up together
➡
Modifying the display is complicated
symfony workshop www.symfony-project.com www.sensiolabs.com
Probleme n°5: Not maintainable
✓
Lots of hardcoded paths
Problem n°6: Wheel is reinvented
✓
File structure
✓
Coding standards
✓
Database access
symfony workshop www.symfony-project.com www.sensiolabs.com
Problem n°7: No documentation
✓
No class
✓
No functions
✓
No PHPDoc
lundi 15 mars 2010
Problem n°8: No tests
✓
The application is not tested
➡
Testing is about automated tests of course
✓
No class and no function to test
➡
Unit testing is not possible
✓
The application is not testable
➡
How to test the SQL statement that creates an
symfony workshop www.symfony-project.com www.sensiolabs.com
Common PHP application problems
✓
No structure
✓
Duplication
✓
Security
✓
Edge cases
✓
Maintainance
✓
The wheel is reinvented
✓
No documentation
✓
No test
Conclusion
✓
The application is:
➡
hard to maintain
➡
hard to make evolve
➡
a pain to develop
symfony workshop www.symfony-project.com www.sensiolabs.com
How to resolve those problems ?
Use a framework
…
Use symfony
symfony workshop www.symfony-project.com www.sensiolabs.com
4 installation ways
✓
Using SVN
[recommended]
➡
http://svn.symfony-project.com/branches/1.4
✓
Download
sf_sandbox1.4.1.zip
✓
Using PEAR channel
✓
Download symfony1.4.1.zip
$ pear channel-discover pear.symfony-project.org
$ pear install symfony/symfony-1.4.1
Go fast: the sandbox
✓
A ready-to-use symfony application
➡
A simple archive to uncompress at the root of the server
➡
An application bundled with all the symfony librairies
➡
No need to configure a web server
✓
Project is already bootstrapped
symfony workshop www.symfony-project.com www.sensiolabs.com
PHP Best Practices
✓
The web root directory only contains static files and
the front controllers
✓
Source control (Subversion / CVS / Git / Mercurial…)
➡
Several developers can work on the same application
➡
Change your code with confidence
➡
Dependency management with
svn:externals
✓
PEAR
➡
Eases the installation and the migration
➡
Knows about dependencies, versions, and stability…
Symfony project initialization
✓
Just follow the four steps below:
➡
Install symfony
➡
Initialize a new project
➡
Configure the Web server
symfony workshop www.symfony-project.com www.sensiolabs.com
Step 1: Install symfony
✓
Install symfony with PEAR
✓
For previous versions
$ pear channel-discover pear.symfony-project.com
$ pear install symfony/symfony-1.4
$ pear install symfony/symfony-1.2
Step 2 : Initialize a new project
apps/
batch/
cache/
config/
ProjectConfiguration.php
data/
doc/
lib/
log/
plugins/
test/
$ mkdir ~/myproject
$ cd ~/myproject
symfony workshop www.symfony-project.com www.sensiolabs.com
Step 3: Create a new application
✓
Create a new application with the CLI
✓
Get help for a task
$ php symfony generate:app frontend
$ php symfony help generate:app
Result : initialize an application
apps/
myapp/
config/
i18n/
lib/
modules/
templates/
batch/
cache/
config/
data/
doc/
lib/
log/
plugins/
test/
web/
symfony workshop www.symfony-project.com www.sensiolabs.com
Step 4: Configure the web server
<VirtualHost *:80>
ServerName myapp.example.com
DocumentRoot "
/path/to/blog/web
"
DirectoryIndex index.php
<Directory "
/path/to/blog/web
">
AllowOverride All
Allow from All
</Directory>
</VirtualHost>
The web root is web/
Which symfony version?
✓
The symfony version can be found in
config/
ProjectConfiguration.class.php
✓
… or directly from the command line:
config/
ProjectConfiguration.class
.php
<?php
require_once
'/path/to/symfony/lib/autoload/sfCoreAutoload.class.php';
sfCoreAutoload::register();
symfony workshop www.symfony-project.com www.sensiolabs.com
Refactor a pure PHP4
application
Let’s begin the refactoring
✓
Switch to version_1/
➡
With a symfony sandbox
➡
It is a pre-packaged project
symfony workshop www.symfony-project.com www.sensiolabs.com
Scalability of structure
Application
apps/
[application name]
/
config/
i18n/
lib/
modules/
templates/
layout.php
error.php
error.txt
Module
apps/
[application name]
/
modules/
[module name]
/
actions/
actions.class.php
config/
lib/
templates/
indexSuccess.php
lundi 15 mars 2010
The command line interface
✓
Needed in production when the configuration change
✓
Files under
cache/
folder can be removed manually
$ php symfony cache:clear
$ php symfony cc
The CLI is your best friend
✓
Automates several redundant operations:
➡
Default directory structure and default files
➡
Code generation (model files, backend, …)
➡
Repetitive and complex tasks
✓
Useful to manage the project lifecycle
➡
Initialization, creation
➡
Test
symfony workshop www.symfony-project.com www.sensiolabs.com
Exercise 1: Module Creation
✓
Create a new
attendee
module in the
frontend
application
$ php symfony generate:module frontend attendee
Command line tasks
symfony workshop www.symfony-project.com www.sensiolabs.com
Exercise 2: Copy PHP files
✓
Copy from version_0
–
in web/css/
•
main.css
–
in web/images/
•
attendee.jpg
–
in data/
•
data.db
–
in apps/frontend/templates/
•
config.php
•
header.php
•
footer.php
–
Content of welcome.php in indexSuccess.php
/exercises/attendee/version_1/web/frontend_dev.php/attendee/index
Exercise 3: Navigation & Routing
// in apps/frontend/modules/attendee/actions/
actions.class.php
<?php
class
attendeeActions
extends
sfActions
{
public function
executeIndex
( sfWebRequest $request )
{
// do things
}
}
symfony workshop www.symfony-project.com www.sensiolabs.com
Action and template names
/exercises/attendee/version_1/web/frontend_dev.php/attendee/index
Module
Action
// in apps/frontend/modules/attendee/templates/
indexSuccess.php
<!–- do things -->
lundi 15 mars 2010
Exercise 4: The action
•
Edit
actions.class.php
and
remove the code with
forward in
executeIndex()
•
See your
attendee/index
•
We want a list of
symfony workshop www.symfony-project.com www.sensiolabs.com
Constants vs. variables
✓
Constants are fast but only allow scalar values
✓
Symfony replaces constants with sfConfig
With constants (PHP)
With sfConfig (Symfony)
define('FOO', 'bar');
sfConfig::set('foo', 'bar');
echo FOO;
echo sfConfig::get('foo');
$bool = defined(FOO);
$bool = sfConfig::has('foo');
if (defined(FOO)) {
echo FOO;
} else {
echo 'bar';
}
echo sfConfig::get('foo', 'bar');
No equiv.
sfConfig::set('foo', new Bar());
No equiv.
sfConfig::clear();
Exercise 5: Fix links
<?php echo image_tag(
'attendee.jpg‘
)?>
•
Fix the link to the image in
header.php
•
Use
sfConfig
to read some config variables
•
Some constants are available
•
Fix the link to the sqlite database in
config.php
symfony workshop www.symfony-project.com www.sensiolabs.com
•
Move some template code to the
action
•
Content of
config.php
in the
executeIndex()
method
•
DB closing from
footer.php
to
executeIndex()
•
Remove the
config.php
file
•
Remove the
config.php
inclusion
in
indexSuccess.php
Exercise 6: Move the code
✓
We want to list attendees, we need a DB connection
✓
We re-use the original code
Pass a variable to a template
✓
Pass the
$userName
variable to the template
✓
Setting a virtual property allows to pass the
variable to the corresponding template
symfony workshop www.symfony-project.com www.sensiolabs.com
Pass a variable to a template
class
attendeeActions
extends
sfActions
{
public function
executeIndex
(
sfWebRequest
$request)
{
$this
->
count
=
10
;
}
}
<p>
Attendees: <?php
echo
$count ?>
</p>
actions.class.php
indexSuccess.php
Easy to read ?
<h1>My article</h1>
<?php
if
(
$user
->
isAdministrator())
{
echo "
<h2>Administrator links</h2>";
echo link_to('
edit',
'
article/edit?id='.
$
article-
>
getId());
}
symfony workshop www.symfony-project.com www.sensiolabs.com
Easy to read ?
<h1>My article</h1>
<?php
if
(
$user
->
isAdministrator()): ?>
<h2>Administrator links</h2>
<?php
echo
link_to(
'edit'
,
'article/edit?id='
.
$article
->
getId()) ?>
<?php
endif
; ?>
Alternative PHP syntax
foreach (
$vars
as
$var
)
{
echo
"
This is ".
$
var.
"<br />";
}
<?php foreach (
$vars
as
$var
): ?>
This is <?php
echo
$var
?><br />
<?php endforeach; ?>
symfony workshop www.symfony-project.com www.sensiolabs.com
Readability in templates
if (
$condition
) {
echo
'foo'
;
} else {
echo
'bar'
;
}
<?php if (
$condition
): ?>
foo
<?php else: ?>
bar
<?php endif; ?>
lundi 15 mars 2010
Templating Best Practices
✓
Use the PHP alternative syntax
✓
No business logic must be in actions
✓
KISS (Keep It Simple Stupid)
✓
No more than 1 line of PHP code at a time
✓
Meaningful variable names
symfony workshop www.symfony-project.com www.sensiolabs.com
The layout solution
header.php
welcome.php
footer.php
welcome.php
layout.php
include
include
decoration
lundi 15 mars 2010
Reusable template code
Here is the typical way to include a snippet of code:
Not D.R.Y
Not flexible
<?php
$title
=
"My page"
; ?>
<?php
include
'header.php'
; ?>
<?php
include
'footer.php'
; ?>
symfony workshop www.symfony-project.com www.sensiolabs.com
Layout and Templates
•
Layout is the main “template”
•
Layout is optional
•
AJAX
•
RSS
•
Template is the rendering of each action
•
no code duplication
•
Local representation of the page
•
Templates are reusable in di
ff
erent layouts
Exercise 7: Move the DB code
•
Move the DB code from the list to the action
query
=
sqlite_query(
$db
,
'
SELECT
count(*)
FROM
attendee'
);
$this
->
count
=
sqlite_fetch_single(
$query
);
$this
->
attendees
=
array();
$query
=
sqlite_query(
$db
,
'
SELECT
*
FROM
attendee'
);
while
(
$attendee
=
sqlite_fetch_array(
$query
, SQLITE_ASSOC))
{
symfony workshop www.symfony-project.com www.sensiolabs.com
Exercise 7: Move the DB code
<p>
There are <?php
echo
$count
?> attendee(s).
</p>
<table id="list" cellspacing="0">
<tr>
<th>Id</th>
<th>Name</th>
<th> </th>
</tr>
<?php
foreach
(
$attendees
as
$attendee
): ?>
<tr>
<!-- ... -->
</tr>
<?php
endforeach
?>
</table>
lundi 15 mars 2010
Exercise 8 : Alternative PHP syntax
•
Refactor the attendees list by using the PHP alternative syntax
•
A template should contain a lot of HTML, with some PHP
<?php
foreach
(
$attendees
as
$attendee
): ?>
<tr>
<td>
<?php
echo
link_to(
$attendee
['id'], 'attendee/show?id='
.
$attendee
['id']) ?>
</td>
<td><?php
echo
$attendee
['name'] ?></td>
<td>
<?php
echo
link_to('delete', 'attendee/delete?id='
.
$attendee
['id']) ?>
</td>
symfony workshop www.symfony-project.com www.sensiolabs.com
Exercise 9: Layout
✓
Move the
header
and
footer
code in
layout.php
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"
http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd
">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<?php include_http_metas() ?>
<?php include_metas() ?>
<?php include_title() ?>
</head>
<body>
<div id="container">
<?php
echo
image_tag(
'attendee.jpg'
,
'height=120 align=right'
) ?>
<h1>
<?php
echo
link_to(
'Attendee Application'
,
'attendee/index'
) ?>
</h1>
<?php
echo
$sf_content
?>
<p class="footer">© Not Sensio</p>
</div>
symfony workshop www.symfony-project.com www.sensiolabs.com
<h2>Create a new Attendee</h2>
<form action="welcome.php" method="POST">
<label for="name">Name: </label><input size=25 type="text" name="name" />
<input type="hidden" name="action" value="create" />
<input type="submit" value="go" />
</form>
<h2>All Attendees</h2>
<p>
There are <?php
echo
$count
?> attendee(s).
</p>
<table id="list" cellspacing="0">
<tr>
<th>Id</th>
<th>Name</th>
<th> </th>
</tr>
<?php
foreach
(
$attendees
as
$attendee
): ?>
<tr>
<td><?php
echo
link_to(
$attendee
[
'id'
],
'attendee/show?id='
.
$attendee
[
'id'
]) ?></td>
<td><?php
echo
$attendee
[
'name'
] ?></td>
<th><?php
echo
link_to(
'delete'
,
'attendee/delete?id='
.
$attendee
[
'id'
]) ?></th>
</tr>
<?php
endforeach
; ?>
</table>
Adding features
✓
We need some extra features:
➡
Creating new attendees
➡
Deleting attendees
➡
Showing an attendee
✓
A module contains several actions:
Adding new features
class
attendeeActions
extends
sfActions
{
public function
executeIndex
(
sfWebRequest
$request
)
{
// ...
}
public function
executeCreate
(
sfWebRequest
$request
) {
}
public function
executeShow
(
sfWebRequest
$request
) {
}
public function
executeDelete
(
sfWebRequest
$request
) {
}
}
✓
Just create new methods in the actions class
Exercise 10: The create action
✓
Opens a connection on the database
✓
Receives the form submitted parameters
✓
Inserts the submitted values into the DB
✓
Closes the database connection
symfony workshop www.symfony-project.com www.sensiolabs.com
public function
executeCreate
(
sfWebRequest
$request
)
{
if
(
!
$db
=
sqlite_open
(
sfConfig
::
get(
'sf_data_dir'
)
.
'/data.db'
),
0666
,
$error
)) {
die
(
$error
);
}
if
(
$request
->
isMethod(
'post'
))
{
$name
=
$request
>
getParameter(
'name'
);
$query
=
sqlite_exec
(
$db
,
'
INSERT INTO
attendee (name)
VALUES
(
"'
.
$name
.
'")'
,
$error
);
if
(
!
$query
) {
die
(
"Error in query: '
$error
'"
);
}
}
sqlite_close
(
$db
);
$this
->
redirect(
'attendee/index'
);
}
Exercise 10: The create action
The $request object allows to
retrieve GET or POST data and check
the HTTP method
Exercise 10: The create action
✓
Finally, change the form action with the
url_for()
helper
<h2>Create a new Attendee</h2>
<form action="<?php
echo
url_for(
'attendee/create'
) ?>" method="POST">
<label for="name">Name: </label><input size=25 type="text" name="name" />
<input type="hidden" name="action" value="create" />
<input type="submit" value="go" />
</form>
symfony workshop www.symfony-project.com www.sensiolabs.com
✓
Open a connection on the database
✓
Retrieve a record by its id given in URI
✓
Pass the record array to the template
✓
Close the DB connection
✓
Forward the user to a 404 page if no record
✓
Display data in the view
Exercise 11: The show action
public function
executeShow
(
sfWebRequest
$request
)
{
if
(
!
$db
=
sqlite_open
(
sfConfig
::
get(
'sf_data_dir'
)
.
'/data.db'
),
0666
,
$error
))
{
die
(
$error
);
}
$query
=
sqlite_query
(
$db
,
'
SELECT
*
FROM
attendee
WHERE
id =
'
.
$request
->
getParameter(
'id'
)
);
$this
->
attendee
=
sqlite_fetch_array
(
$query
,
SQLITE_ASSOC
);
sqlite_close
(
$db
);
$this
->
forward404Unless(
$this
->
attendee
);
}
symfony workshop www.symfony-project.com www.sensiolabs.com
<h2>Attendee #<?php
echo
$attendee
[
'id'
] ?></h2>
<p>
Name: <?php
echo
$attendee
[
'name'
] ?>
</p>
Exercise 11: The show action
URLs in actions
✓
Forward to another action
$this
->
forward(
$moduleName
,
$actionName
);
$this
->
forward404Unless(
$condition
);
✓
Redirect to another page
symfony workshop www.symfony-project.com www.sensiolabs.com
✓
Access the request parameters
$param
=
$request
->
getParameter(
'id'
);
$module
=
$request
->
getParameter(
'module'
);
$action
=
$request
->
getParameter(
'action'
);
✓
Create a URL
$url
=
$this
->
generateUrl(
$uri
);
URLs in actions
Redirect or Forward?
✓
A forward does not change the user URL
✓
A redirect involves a round-trip with the browser (302)
✓
A forward can be used to create the same page with 2 URLs
✓
After a post, you must always redirect
symfony workshop www.symfony-project.com www.sensiolabs.com
✓
The DB connection opening code is duplicated
✓
The DB connection closing code is duplicated
✓
It’s a good practice to often refactor the code
✓
Use the
preExecute()
and
postExecute()
methods
✓
The goal is to simplify actions and make them thinner
Exercise 12: Clean the code
Exercise 12: Clean the code
public function
preExecute
()
{
define
(
'DB_FILE'
,
sfConfig
::
get(
'sf_data_dir'
)
.
'/data.db'
);
if
(
!
$this
->
db
=
sqlite_open
(
DB_FILE
,
0666
,
$error
))
{
die
(
$error
);
}
}
public function
postExecute
()
{
symfony workshop www.symfony-project.com www.sensiolabs.com
Exercise 12: Clean the code
public function
executeIndex
(
sfWebRequest
$request)
{
query
=
sqlite_query
($this
->
db,
'
SELECT
count(*)
FROM
attendee
'
);
$this
->
count
=
sqlite_fetch_single
($query);
$this
->
attendees
=
array
();
$query
=
sqlite_query
($this
->
db,
'
SELECT
*
FROM
attendee
'
);
while
($attendee
=
sqlite_fetch_array
($query,
SQLITE_ASSOC
))
{
$this
->
attendees[]
=
$attendee;
}
}
Exercise 12: Clean the code
public function
executeShow
(sfWebRequest
$request
)
{
$query
=
sqlite_query(
$this
->
db
,
'
SELECT
*
FROM
attendee
WHERE
id = '
.
$request
->
getParameter('id')
);
$this
->
attendee
=
sqlite_fetch_array(
$query
, SQLITE_ASSOC);
$this
->
forward404Unless(
$this
->
attendee
);
symfony workshop www.symfony-project.com www.sensiolabs.com
Exercise 12: Clean the code
public function
executeCreate
(
sfWebRequest
$request
)
{
if
(
$request
->
isMethod(
'post'
))
{
$name
=
$request
>
getParameter(
'name'
);
$query
=
sqlite_exec
(
$this
->
db
,
'
INSERT INTO
attendee (name)
VALUES
(
"'
.
$name
.
'")'
,
$error
);
if
(
!
$query
) {
die
(
"Error in query: '
$error
'"
);
}
}
$this
->
redirect(
'attendee/index'
);
}
Template names
✓
Templates are stored under
templates/
✓
The template name is the action name + a su
ffi
x
✓
The su
ffi
x is returned by the action
class
myModule
extends
sfActions
{
public function
executeMyAction()
{
return
sfView
::
SUCCESS
;
}
symfony workshop www.symfony-project.com www.sensiolabs.com
Template names
class
myModuleActions
extends
sfActions
{
// myModule/templates/myActionSuccess.php
public function
executeMyAction
()
{
}
}
class
myModule
extends
sfActions
{
// myModule/templates/myActionError.php
public function
executeMyAction
()
{
return
sfView
::
ERROR
;
}
}
Template names
class
myModule
extends
sfActions
{
// myModule/templates/myActionFoobar.php
public function
executeMyAction
()
{
return
'Foobar'
;
}
symfony workshop www.symfony-project.com www.sensiolabs.com
Symfony templating system: PHP
✓
Just works
✓
Easy to understand and learn (plenty of docs)
✓
Powerful (lots of built-in functions)
✓
Extensible
✓
Easy to integrate with the other objects
✓
Readable
✓
Opened to other templating systems
Exercise 13: Links between pages
•
Replace links in templates
•
Use the symfony
link_to()
and
url_for()
helpers
<h1>
<?php
echo
link_to('Attendee Application', 'attendee/index') ?>
</h1>
// …
<?php
foreach
(
$attendees
as
$attendee
): ?>
<tr>
<td>
<?php
echo
link_to(
$attendee
['id'], 'attendee/show?id='
.
$attendee
['id']) ?>
</td>
<td><?php
echo
$attendee
['name'] ?></td>
<th>
<?php
echo
link_to('delete', 'attendee/delete?id='
.
$attendee
['id']) ?>
</th>
symfony workshop www.symfony-project.com www.sensiolabs.com
Link to another action
Routed URL
<a
href="
/attendee/show/id/12
"
>
Click Me!
</a>
<?php echo link_to(
'Click Me!'
,
'attendee/show?id='
.
$id
) ?>
Template helpers
✓
PHP functions
✓
Wrap complex templating code
✓
Shortcuts for common usages
symfony workshop www.symfony-project.com www.sensiolabs.com
Link helpers
The
url_for()
helper
Outputs in HTML as:
/articles/2007/02/19/symfony-rocks
<?php
echo
url_for(
'article/permalink?slug=symfony-rocks&day=19&month=02&year=2007'
) ?>
Link helpers
The
link_to()
helper
Outputs in HTML as
<a
href="
/articles/2007/02/19/symfony-rocks
"
>
symfony rocks!
</a>
<?php
echo
link_to(
'symfony rocks!'
,
'article/permalink?slug=symfony-rocks&day=19&month=02&year=2007'
) ?>
symfony workshop www.symfony-project.com www.sensiolabs.com
Exercise 14: The delete action
•
Change the delete link
<th>
<?php
echo
link_to(
'delete'
,
'attendee/delete?id='
.
$attendee
[
'id'
]) ?>
</th>
public function
executeDelete(sfWebRequest
$request
)
{
$query
=
sqlite_exec
(
$this
->
db
,
'
DELETE
FROM
attendee
WHERE
id
=
'
.
sqlite_escape_string
(
$request
->
getParameter(
'id'
)
),
$error
);
if
(
!
$query
)
{
die
(
"Error in query: '
$error
'"
);
}
$this
->
redirect(
'attendee/index'
);
symfony workshop www.symfony-project.com www.sensiolabs.com
Exercise 15: Fix images directoy
✓
The link to create a new attendee or to come back to the
homepage is broken
✓
image_tag()
takes the root directory of the project
installation into account
<?php
echo
image_tag(
'attendee.jpg'
) ?>
✓
Contains data to manipulate
✓
Contains all the business logic
✓
Often associated with the database
✓
Most important layer in MVC architecture
symfony workshop www.symfony-project.com www.sensiolabs.com
✓
Create a
lib/AttendeeModel.class.php
file
✓
Move the
preExecute()
code in
__construct()
method
✓
Move the
postExecute()
code in
__destruct()
method
✓
Store the DB connection in a private
$db
property
Exercise 16: The Model layer
Exercise 16: The Model layer
class
AttendeeModel
{
private
$db
;
public function
__construct
()
{
if
(
!
$this
->
db
=
sqlite_open
(
sfConfig
::
get(
'sf_data_dir'
)
.
'/data.db'
,
0666
,
$error
))
{
throw
new
Exception
(
'Unable to open DB connection: '
.
$error
);
}
}
public function
__destruct
()
{
if
(
$this
->
db
)
{
sqlite_close
(
$this
->
db
);
}
symfony workshop www.symfony-project.com www.sensiolabs.com
✓
Remove
preExecute()
and
postExecute()
methods
✓
Implement a
count()
method to count all attendees
✓
Implement a
findAll()
method to find all attendees
✓
Refactor the
executeIndex()
method consequently
Exercise 16: The Model layer
Exercise 16: The Model layer
class
AttendeeModel
{
// ...
public function
count
()
{
$query
=
sqlite_query
(
$this
->
db
,
'SELECT
COUNT(*)
FROM
attendee
'
);
return
(
int
)
sqlite_fetch_single
();
}
}
symfony workshop www.symfony-project.com www.sensiolabs.com
Exercise 16: The Model layer
class
AttendeeModel
{
// ...
public function
findAll
()
{
$query
=
sqlite_query
(
$this
->
db
,
'SELECT
*
FROM
attendee
'
);
$attendees
=
array
();
while
(
$attendee
=
sqlite_fetch_assoc(
$query
,
SQLITE_ASSOC
))
{
$attendees
[]
=
$attendee
;
}
return
$attendees
;
}
}
lundi 15 mars 2010
Exercise 16: The Model layer
public function
executeIndex
(
sfWebRequest
$request
)
{
$attendeeTable
=
new
AttendeeModel
();
$this
->
count
=
$attendeeTable
->
count();
$this
->
attendees
=
$attendeeTable
->
findAll();
}
symfony workshop www.symfony-project.com www.sensiolabs.com
Exercise 16: The Model layer
✓
Add a
create()
method that creates a new attendee
✓
Add a
find()
method that finds an attendee by its id
✓
Add a
delete()
method that deletes an attendee by its id
✓
Refactor action methods consequently
Exercise 16: The Model layer
class
AttendeeModel
{
// ...
public function
find
($id)
{
$sql
=
sprintf
(
'
SELECT
*
FROM
attendee
WHERE
id = %u
'
, $id);
$query
=
sqlite_query
($this
->
db, $sql);
return
sqlite_fetch_array
($query,
SQLITE_ASSOC
);
}
symfony workshop www.symfony-project.com www.sensiolabs.com
Exercise 16: The Model layer
class
AttendeeModel
{
// ...
public function
create
(
$name
)
{
$sql
=
sprintf
(
'INSERT INTO
attendee (name)
VALUES
(
"%s"
)
'
,
$name
);
$query
=
sqlite_query
(
$this
->
db
,
$sql
,
$error
);
if
(
!
$query
)
{
throw
new
Exception
(
'Unable to create a new attendee: '.
$error
);
}
}
}
Exercise 16: The Model layer
class
AttendeeModel
{
// ...
public function
delete
(
$id
)
{
$sql
=
sprintf
(
'DELETE
FROM
attendee
WHERE
id = %u
'
,
$id
);
$query
=
sqlite_query
(
$this
->
db
,
$sql
,
$error
);
if
(
!
$query
)
{
throw
new
Exception
(
'Unable to delete #'.
$id
.': '.
$error
);
}
symfony workshop www.symfony-project.com www.sensiolabs.com