Book: Symfony 1.3 Web Application Development

November 4th, 2009

Few weeks ago Packt Publishing sent me their new book called Symfony 1.3 Web Application Development to review. Ironically (or not) I'm not doing any active symfony development in the last few months, but I'm trying to be up to date with the news around symfony.

As title says the book is based on the upcoming 1.3 version of the framework. While building an application called The Milkshake Shop readers are introduced to the features that the framework can offer including some of the latest (and may a little bit not so good documented) ones like forms, routes, creating and packaging plugins and so on. There is also a whole chapter dedicated to performance and optimization. Full table of contents can be found here.

It was a surprise (at lest for me) to see that the ORM choosen for The Milkshake Shop (and the book) was Propel instead of Doctrine which was choosen as the default ORM framework.

However this book is a good foundation for everyone that's new to symfony world and also gives some more information about the features introduced in both 1.2 and 1.3 to the developers which are still doing their job using 1.0 version.

At the end the one thing that I didn't like in this book -
"Test-driven development is the key to bug-free and well-written code. Symfony provides the ability for unit and functional testing. Unit tests enable the developer to test functions and methods for input and output. While functionality tests helps the developer to test for functional issues that would be executed in the browser, Symfony has its own testing framework called Lime. This testing framework is useful for both unit testing and functional testing. All test output can be saved in the xUnit format." -

Believe it or not, but that's the only place where testing is mentioned (correct me of I'm wrong)! Seriously, if "Test-driven development is the key to bug-free and well-written code" give it a chapter or two. Or even better, build the whole app in a test-driven way.


3 comments »



sfHoptoadNotifierPlugin - track your symfony project exceptions using Hoptoad

April 16th, 2009
sfHoptoadNotifier is symfony plugin that sends notifications to Hoptoad about your project exceptions. It's based on rich/php-hoptoad-notifier. You can check it's README for more info.

Requirements
rich/php-hoptoad-notifier uses the Horde_Yaml class. You can install this class using the commands below.

1
2
$ pear channel-discover pear.horde.org
$ pear install horde/yaml
It also uses Pear's HTTP_Request:

$ pear install HTTP_Request      
Install the plugin

$ git clone git://github.com/krasio/sfhoptoadnotifierplugin.git plugins/sfHoptoadNotifierPlugin
Add your Hoptoad API key in applications app.yml
1
2
3
all:
  sf_hoptoad_notifier_plugin:
    api_key: you_api_key_goes_here
Clear your cache

$ symfony cc

0 comments »



sfRestfulAuthenticationPlugin released

November 11th, 2008
I've just released my first symfony plugin named sfRestfulAuthenticationPlugin.

As you can see from it's name it's something like a port of technoweenie's restful_authentication plugin. It uses some cool new features from symfony 1.2 like the form framework and RESTful routes.

Tickets can be submitted on http://sfrestfulauthenticationplugin.lighthouseapp.com.
0 comments »



symfony 1.2 will be RESTful

September 12th, 2008
As we can see from this presentation, symfony 1.2 (expected 10/2008) will be RESTful.
0 comments »



Deploying symfony project with Capistrano

May 12th, 2008
As you may know Capistrano is a great tool "originally written to ease the pain of deploying Rails applications". If you are not familiar with it take a look at it's getting started guide. Even if Rails applications development is the primary way that people use Capistrano it can be used for a lot more than that.

Let me show you how you can use it for easy-and-with-no-pain deployment of symfony project.

First cd to your project and run (I assume that you have Capistrano installed allready)


$ capify .
to capify your project . This will create two files - Capify and config/deploy.rb. We'll use the second one for our symfony related tasks.
So this is how the modifed deploy.rb file that I use to deploy my symfony projects looks like :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
set :application, "mysfapp"
set :repository,  "http://path/to/the/version/control/repository"

# If you aren't deploying to /u/apps/#{application} on the target
# servers (which is the default), you can specify the actual location
# via the :deploy_to variable:
set :deploy_to, "/path/to/project/#{application}"

# If there's no access to the repository from the production server, deploy via uploading tarball to the server
#set :deploy_via, :copy

# If you aren't using Subversion to manage your source code, specify
# your SCM below:
# set :scm, :subversion

role :app, "codingspree.net"
role :web, "codingspree.net"
# role :db,  "your db-server here", :primary => true

set :user, "codingspree"

# path to php executable 
set :php, "/usr/local/php5/bin/php5"

# symfony application name (used for migrations)
set :sf_app, "frontend"

namespace (:deploy) do

  desc <<-DESC
    [internal] Overriding original task to fit to symfony project needs
  DESC
  task :finalize_update, :except => { :no_release => true } do
    run "chmod -R g+w #{latest_release}" if fetch(:group_writable, true)

    run <<-CMD
      rm -rf #{latest_release}/log &&
      ln -s #{shared_path}/log #{latest_release}/log
    CMD
    
    run <<-CMD
      rm -rf #{latest_release}/cache &&
      ln -s #{shared_path}/cache #{latest_release}/cache
    CMD
    
    stamp = Time.now.utc.strftime("%Y%m%d%H%M.%S")
    asset_paths = %w(images css js).map { |p| "#{latest_release}/web/#{p}" }.join(" ")
    run "find #{asset_paths} -exec touch -t #{stamp} {} ';'; true", :env => { "TZ" => "UTC" }
  end
  
  desc <<-DESC
    Overriding original task to exclude restart
  DESC
  task :default do
    update
  end
  
  desc <<-DESC
    Overriding original task to use symfoy migrations (via sfMigrationsLightPlugin)
  DESC
  task :migrations do
    update
    sf.migrate
  end  
  
  after "deploy:update", 'deploy:customize'
  
  desc <<-DESC
    All custom tasks will be here
  DESC
  task :customize do
    # custmize it here
    sf.symlinks
    sf.remove_dev_environments
    
    # clear cache
    sf.cc
  end
  
end


namespace (:sf) do
  
  desc <<-DESC
    Run the "symfony migrate" task
  DESC
  task :migrate do
    run "cd #{current_path} && #{php} symfony migrate #{sf_app}"    
  end

  desc <<-DESC
    Run the "symfony cc" task
  DESC
  task :cc do
    run "cd #{current_path} && rm -rf cache/*"
  end

  desc <<-DESC
    Create symlink to symfony specific targets
  DESC
  task :symlinks do
    # symlink to database.yml
    run "rm -rf #{current_path}/config/databases.yml"
    run "ln -s #{shared_path}/databases.yml #{current_path}/config/databases.yml"

    # symlink to config.php    
    run "rm -rf #{current_path}/config/config.php"
    run "ln -s #{shared_path}/config.php #{current_path}/config/config.php"

    # symlink to sf data dir    
    run "rm -rf #{current_path}/web/sf"
    run "ln -s /path/to/sf/data/dir #{current_path}/web/sf"
        
    # symlink to uploads    
    run "rm -rf #{current_path}/web/uploads"
    run "ln -s #{shared_path}/uploads #{current_path}/web/uploads"
    
  end 
  
  desc <<-DESC
    Remove DEV environments
  DESC
  task :remove_dev_environments do
    run "rm -rf #{current_path}/web/*_dev.php"    
  end 
  
  desc <<-DESC
    Disable symfony application
  DESC
  task :disable do
    run "cd #{current_path} && #{php} symfony disable #{sf_app} prod"    
  end 
  
  desc <<-DESC
    Enable symfony application
  DESC
  task :enable do
    run "cd #{current_path} && #{php} symfony enable #{sf_app} prod"    
  end   
end
As you can see most of it is usual Capistrano configuration (user, repository, application, etc.) so I'll explain only the symfony related stuff.
1
2
# path to php executable 
set :php, "/usr/local/php5/bin/php5"
This variable is used to specify where PHP5 cli is placed (you know not all hostings are using PHP5 as default).
1
2
# symfony application name (used for migrations)
set :sf_app, "frontend"
Here we specify the name of one of our symfony project applications. It will be needed to run migrations (you ARE using sfPropelMigrationsLightPlugin allready, right?).

In the next section of the deploy.rb file I overrided few of the original tasks from deploy namespace to fit to my needs - finalize_update to set symlinks to symfony cache and log directories, the default task to exclude restart because I don't need it, migrations to set it to use symfony migrations and at the end there is one hook task that will be executed after deploy:update -it's called customize and I'll place all custom tasks there.

The last part of the file is sf namespace that includes some symfony related tasks as migrate, cc, symlinks, remove_dev_environments, disable, enable and so on. As you see you can wrap any symfony task that you have to execute on the production server with Capistrano task.

All you need now is to set a little bit the production server (run cap deploy:config, symlink document root to /path/to/project/mysfapp/current, place relevant database.yml and config.php in /path/to/project/mysfapp/shared, etc) and you are ready to run

$ cap deploy
Thats it! Easy one command deployment! With migrations (cap deploy:migrate)! Hope it's explained clear enough (if not place your questions in the comments).
2 comments »



Adding callbacks for Propel models in symfony project

September 11th, 2007
This is a simple how-to that describes adding callbacks for Propel models in symfony project (like those usefull methods in ActiveRecord::Callbacks module).

Step 1 : Enable Propel behaviors in config/propel.ini


propel.builder.AddBehaviors = true     // Default value is false
Step 2 : Register the callback method and define it in model class - for example lib/model/Page.php
1
2
3
4
5
6
7
8
9
class PageTab extends BasePageTab
{
  function preSave(BaseObject $object, $con)
  {
    
  }
}

sfMixer::register('BasePageTab:save:pre', array('PageTab', 'preSave'));
Step 3 : Rebuild the model

$ symfony propel-build-model

There is much more detailed article on this topic by François Zaninotto called "Understanding Behaviors"
0 comments »



"Skinny Controller, Fat Model" approach in symfony project

September 10th, 2007
A very common error when you first start to write an application in the MVC way is bypassing some of the components or just not using it as it should.
This problem and an example how to do it in the "right way" are described in a one of Jamis Buck's posts called Skinny Controller, Fat Model.

Here is a cover-up version of the code from that post ported to symfony/PHP...

Before (or how-NOT-to-do-it example) :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!-- listSuccess.php -->
<?php
  foreach ($path as $breadcrumb=>$name)
  {
    ?>
      <a href="<?=url_for($breadcrumb)?>"><?=__($name)?></a> / 
    <?
  }
  
  foreach ($people as $p)
  {
    ?>
      <div id="person-<?=$p->getId()?>">
        <span class="name">
          <?=$p->getLastName()?>, <?=$p->getFirstName()?>
        </span>
        <span class="age">
          <?= (date('Y') - date('Y', strtotime($p->getBirthdate()))) ?>
        </span>
      </div>
    <?
  }
?>
1
2
3
4
5
6
7
8
9
10
11
12
# actions.php
class personsActions extends sfActions
{
  public function executeList()
  {
    $c = new Criteria;
    $c->add(PersonPeer::ADDED_AT, date('Y-m-d'), Criteria::GREATER_EQUAL);
    $c->add(PersonPeer::DELETED, false);
    
    $this->people = Person::doSelect($c);
  }
}
1
2
3
4
# PersonPeer.php
class PersonPeer extends BasePersonPeer
{
}
After (or do-it-in-this-way example) :
1
2
3
4
5
6
7
8
9
<!-- listSuccess.php -->
<?php echo breadcrumbs($path) ?>

<?php foreach ($people as $p): ?>
  <div id="person-<?php echo $p->getId()?>">
    <span class="name"><?php echo $p->getFullName()?></span>
    <span class="age"><?php echo $p->getAge()?></span>
  </div>
<? endforeach;?>
1
2
3
4
5
6
7
8
# actions.php
class personsActions extends sfActions
{
  public function executeList()
  {
    $this->people = PersonPeer::findRecent();
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# PersonPeer.php
class PersonPeer extends BasePersonPeer
{
  public function findRecent()
  {
    $c = new Criteria;
    $c->add(self::ADDED_AT, date('Y-m-d'), Criteria::GREATER_EQUAL);
    $c->add(self::DELETED, false);

    return self::doSelect($c);
  }
}

# Person.php
class Person extends BasePerson
{
  public function getFullName()
  {
    return $this->getLastName() . ', ' . $p->getFirstName();
  }

  public function getAge()
  {
    return (date('Y') - date('Y', strtotime($this->getBirthdate())));
  }
}

3 comments »