symfony: an open-source framework for professionals (dutch php conference 2008)
TRANSCRIPT
Before we begin
How many have already used symfony for a project,
even a very small personal project?
Who are we? • Fabien Potencier
– Founder of Sensio • Web Agency • Since 1998 • 45 people • Open-Source Specialists • Big corporate customers
– Creator and lead developer of symfony
• Stefan Koopmanschap – Consultant at Ibuildings – Initiator of symfony-framework.nl and symfonyCamp – symfony developer for 2 years
symfony • PHP Web framework • Based on
– 10 years of Sensio experience – Existing Open-Source projects
• Built for : – Professional websites – Complex needs – Demanding environments
Whatever the application, a framework is build to ease
development by providing tools for recurrent and boring tasks.
Write less code
More time for edge cases, business rules, …
less code
less complexity
less bugs
more productivity
more time
Each line of code has an initial cost
… and there is a cost to maintain the line
Kent Beck (based on Yourdon and Constantine)
Costinitial = Costdeveloppement + Costtests
Costmaintenance >> Costinitial
Costmaintenance = Costunderstanding + Costchange + Costtests + Costdeployment
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 software. »
• Open-Source documentation – The book (450 pages - GFDL) – Askeet Tutorial (250 pages)
• Translation in 12 langages – 中文 (Chinese)
– Deutsch – Español – Français – Italiano – 日本語 (Japanese)
– Polski – Português – Russian – Ukrainian – Čeština – Nederlands
Mailing-list support / forums / IRC
240 available plugins
300k unique visitors per month on the official website www.symfony-project.org
Version 1.0 released early 2007
– Maintained for 3 years (early 2010)
– ~1 release a month (1.0.16 now) • Bug and security fixes, compatibility with
new PHP versions fixes
• No new features (even small ones)
• Upgrading is simple and safe
Version 1.1 to be released this month – Maintained for 1 year
– Same release cycle as 1.0
Roadmap – Version 1.2 Q4 2008
– Version 1.3 Q1 2009
sfEventDispatcher // sfUser!$event = new sfEvent($this, ‘user.change_culture’,
array(‘culture’ => $culture));!$dispatcher->notify($event);!
// sfI18N!$callback = array($this, ‘listenToChangeCultureEvent’);!$dispatcher->connect(‘user.change_culture’, $callback);!
• sfI18N and sfUser are decoupled • « Anybody » can listen to any event • You can notify existing events or create new ones
The Project • A simple blog system
– Posts – Categories – Authors – Comments
• Requirements – Maintainable – Customizable – Secure
• Use symfony 1.1
Bootstrap a symfony Project
1. Install symfony
2. Initialize a new project
3. Configure the Web Server
4. Start coding
Installing symfony
Sandbox: Ready-to-run symfony application
PEAR: Install symfony globally on your machine
Subversion: Be free to have several versions around
Create a new secure Application
./symfony generate:app frontend \!
--escaping-strategy=on \!
--csrf-secret=A$ecret!
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>
Web root directory is web/
symfony Assets Used by the default pages and the Web Debug Toolbar
Configure the Web Server to serve symfony assets
Or, create a symlink
<VirtualHost *:80> … Alias /sf /$sf_symfony_data_dir/web/sf <Directory "/$sf_symfony_data_dir/web/sf"> AllowOverride All Allow from All </Directory> </VirtualHost>
$ cd web/
$ ln -sf ../lib/vendor/symfony/data/web/sf sf
cache cache cache
debug debug debug
logs logs
stats stats stats
logs
development environment
staging environment
production environment
Create a Module for Posts Create a new ‘post’ module in the ‘frontend’
application
$ php symfony generate:module frontend post!
Action and Template Naming /frontend_dev.php/blog/index
// in apps/frontend/modules/blog/actions/actions.class.php <?php
class blogActions extends sfActions { public function executeIndex() { // do things } }
// in apps/frontend/modules/blog/templates/indexSuccess.php <!–- do things -->
module action
Create the Blog Homepage
• Copy homepage.html into indexSuccess.php • Copy the images/ and css/ under web/ • Add the base.css CSS in view.yml • Fix images and css paths
apps/frontend/modules/post/templates/indexSuccess.php!
/frontend_dev.php/post/index!
Create an Action to show a Post apps/frontend/modules/post/actions/actions.class.php!
/frontend_dev.php/post/show!
• Create an empy executeShow() action • Copy post.html into showSuccess.php • Fix images and css paths
The Layout
header.php
page content
footer.php
page content
layout.php
include
include
decoration
A layout wraps the template content
The Layout Move the common code from homepage and post to
the layout
apps/frontend/templates/layout.php!
Layout with Several "holes"
Main content
Main content
+ =
Layout Template with slots
Rendered Page
Slot1
Slot
2
Slot 1
Slot 2
A slot content depends on the template context
Passing Data from Action to Template
apps/frontend/modules/blog/actions/actions.class.php!
apps/frontend/modules/blog/templates/indexSuccess.php!
Propel : The symfony ORM ORM = Object-Relational Mapping Mapping a relational database to an object-oriented
model Database Abstraction
table row, record field, column
class object proterty
Object-Oriented Relational
Schema Conventions post: id: # primary key, autoincrement integer author_id: # foreign key to Author created_at: # timestamp, set to current time on creation updated_at: # timestamp, set to current time on update
# column types published_at: timestamp title: varchar(255) content: longvarchar is_spam: boolean
# complex column definitions last_name: { type: varchar(100), index: true, required: true } category_id: { type: integer, foreignTable: category,
foreignReference: id, required: false, onDelete: setnull }
From Schema to Object Model
$ ./symfony propel:build-model!
lib/ model/ om/ BasePost.php BasePostPeer.php Post.php PostPeer.php
propel: post: id: ~ name: varchar(255)
1 table > 4 classes?
Base and Custom Classes Base classes
Under model/om/, prefixed by Base Generated by Propel Overwritten each time the schema
changes and the model is generated
Never edit these files!
lib/ model/ om/ BasePost.php BasePostPeer.php Post.php PostPeer.php
lib/ model/ om/ BasePost.php BasePostPeer.php Post.php PostPeer.php
Custom classes Under model/, no prefix Inherit from Base classes Never overwritten Put custom methods here Override base methods here
Peer and Object Classes Peer classes
Suffixed by Peer Useful to retrieve a collection of objects Methods return objects Only static methods (::, self)
lib/ model/ om/ BasePost.php BasePostPeer.php Post.php
PostPeer.php
Object classes No suffix Useful to create / inspect / update records Methods return column values Only object methods (->, $this)
lib/ model/ om/ BasePost.php BasePostPeer.php Post.php PostPeer.php
Database Initialization
mysqladmin create dutchconference!
./symfony configure:database mysql://localhost/dutchconference!
Initial Data data/fixtures/01-data.yml!
Define PKs with names
Use names instead of Pks
Dynamic values
Summary of Code Generation
schema.yml
Object model Base, Custom, Peer and object classes
Relational database Tables, columns, keys, indexes
propel:build-model!
propel:build-sql!propel:insert-sql!
1
2
3
If the Database preexists the Project
schema.yml
Object model Base, Custom, Peer and object classes
Relational database Tables, columns, keys, indexes
propel:build-model!
propel:build-schema!
2
3
1
Generated Methods of Object Classes Getter for columns
$title = $post->getTitle(); $content = $post->getContent(); $createdAt = $post->getCreatedAt();
Some getters have special options $date = $post->getCreatedAt($dateFormat);
Getter by name $title = $post->getByName('title');
CamelCase version of the column name
Generated Methods of Object Classes Manipulate primary keys
$commentId = $comment->getId(); // for composite keys, prefer $commentId = $comment->getPrimaryKey();
Manipulate foreign keys $postId = $comment->getPostId(); // in practice, these methods are not used much // use getter for foreign objects instead $post = $comment->getPost(); // Post object // as the result is an object, you can chain method calls $content = $comment->getPost()->getContent();
One-to-Many smart getters $comments = $post->getCommments(); // Array of Comments $nb = $post->countCommments(); // Integer
What the Model Layer does Action Model Database
PostPeer::doSelect(new Criteria())!
SELECT * FROM post!
Criteria to SQL translation
resultset!Query execution
Array of Post objects!Object hydrating
What the Model Layer does Template Model Database
$post->getTitle()!
String!Looking up internal attribute
Change the Date Format getPublishedAt() first argument accepts the date()
format or the strftime() format
symfony format_date() helper is i18n aware
Helper Groups • Tag • URLs • Assets (images, JavaScript, CSS, …) • Subtemplate inclusion (slot, partial, component) • Links • Form • Javascript and Ajax • Text, number, date manipulation • I18N • …
Permalinks • Many applications provide an alternative to
functional URLs • Permalinks look like links to permanent content
while the resource they reference is dynamically generated
• Primarily focused at search engines, permalink often carry more readable data for end users
http://www.symfony-project.org/blog/2008/05/21/new-symfony-security-policy
Links to the Post Page
apps/frontend/modules/post/templates/indexSuccess.php!
apps/frontend/modules/post/actions/actions.class.php!
What the Model Layer does Template Model Database
$post->getComments()!
SELECT * FROM comment!WHERE comment.post_id= ?!
resultset!Query execution!
Array of Comment objects! Object hydrating!
Base and Custom Classes Base classes
Under form/base/, prefixed by Base Generated by symfony Overwritten when the schema
changes and the forms are generated
Never edit these files!
lib/ form/ base/ BasePostForm.class.php PostForm.class.php
lib/ form/ base/ BasePost.Form.class.php PostForm.class.php
Custom classes Under form/, no prefix Inherit from Base classes Never overwritten Put custom methods here Override base methods here
Create a Comment Form
apps/frontend/modules/post/actions/actions.class.php!
apps/frontend/modules/post/templates/showSuccess.php!
Propel Forms • Generated by propel:build-forms • 1 table = 1 form • Model introspection to determine
– The widget – The validation rules
• Automatically converts a form to a Propel object and save it to the database
• Extensible
Create the Category Page lib/modełPostPeer.class.php!
apps/frontend/modules/blog/actions/actions.class.php!
Create a Partial for the List apps/frontend/modules/blog/templates/_list.php!
apps/frontend/modules/blog/templates/listByCategorySuccess.php!
Create a Component
apps/frontend/templates/layout.php!
apps/frontend/modules/post/actions/components.class.php!
apps/frontend/modules/post/templates/_categories.php!
Create a Web Service for Posts
apps/frontend/config/routing.yml!
apps/frontend/modules/post/templates/indexSuccess.xml.php!
Sensio S.A. 26, rue Salomon de Rothschild
92 286 Suresnes Cedex FRANCE
Tél. : +33 1 40 99 80 80
Contact Fabien Potencier
http://www.sensiolabs.com/ http://www.symfony-project.com/