Showing posts with label symfony. Show all posts
Showing posts with label symfony. Show all posts

Wednesday, April 21, 2010

A Word About Caching: Memcached and APC

Sometimes when talking with developers I see that there are some misconceptions regarding this two caching systems. That's why I'd like to share some concepts I've learned along the way.

What does APC do?

APC has two main features, one is to cache the PHP opcodes, to speed up the page delivery. This means that our PHP code doesn't have to be parsed every time it gets executed. The other one is to cache user variables, so you can cache the results of expensive_function() in APC, using apc_store, for example, and apc_fetch to get the value back without needing to execute the expensive_function() function again.

A common technique employed by frameworks like symfony is to parse some configuration files, like YAML ones, and the var_export them into a PHP file. Something like:


<?php
$some_config = array('value_a', 'value_b');
?>


So later the framework just includes that file avoiding to parse the YAML file twice. This is technique is just file caching, but if you happen to have APC enabled, then you can benefit from the fact that the opcodes were cached by APC. So:

Misconception #1:

That APC caches the opcodes doesn't mean that loading whatever that file has won't be expensive performance wise. As an example, let's say that the framework caches a big array of configuration values. That array has to be loaded in memory again. AFAIK that can't be avoided. So, that APC caches the opcodes, doesn't mean that reloading it comes for free.

And what about Memcached?

Misconception #2:

Another misconception that I've heard is that Memcached is fast and it's speed is compared with hell's speed. That doesn't mean that is faster than accessing something from the PHP process memory, which is the case with APC. Every time you retrieve a value from Memcached, it has to perform a TCP roundtrip to get that value, besides opening a TCP connection to the server per request if you are not using persistent connections.

So while Memcached is fast, I won't recommend to use it for small values that are frequently accessed. APC can work just fine there –taking into account the size of the values of course–. So in the case of symfony, we use Memcached to cache view templates, results from queries, and things like that. But in the case of the routing generation calls, like the ones for url_for or link_to, I would prefer APC, which is what we use for the routing configuration.

Keep in mind that this is based on my experience, so take this for what it is and of course I'll love to read your comments about this topic here.

Thursday, January 21, 2010

Inspecting PHP sessions from Python

For one of our PHP projects we wanted to be able to inspect the PHP sessions from outside PHP. For example we want to know the users privileges at certain moment, i.e. if the user is logged in or not.

Why would you need that you may ask?

Well, let's say that our symfony application stores the result of a cached action in Memcache, having two versions of the result HTML, one for logged in users and one for logged out ones. In that case we want to avoid loading symfony at all and returning directly the HTML from Nginx. One of our devs wrote a Nginx module that does just that, it gets from the Memcache certain value, if it's found, then it returns the HTML immediately, else it calls symfony to handle the request. The problem with this approach is that the Nginx doesn't know if the user is authenticated or not, so it can't handle the case where we have two different versions of HTML output for one action. Well, until now...

Please welcome InspectorD a Python daemon that can inspect PHP sessions.

InspectorD is tcp server that understands a very simple text protocol: you ask it if certain session_id is authenticated and it replies 1 if it does, or 0 if it doesn't.

Here's an sample session:

telnet localhost 3002
isauth oglnp9phvn8ac04obdqjk6dko3
0
isauth bj6sc485t9s46o57qpngod5lm7
1
isauth bj6sc485t9s46o57qpngod5lm7 oglnp9phvn8ac04obdqjk6dko3 n63o4uk297c49131dcdg0h7g72
1
0
1
quit

The server is based on the Twisted framework and the PHPUnserialize module by Scott Hurring. From the later I fixed the session_decode method since it wasn't working for me.

For installation instructions and usage see the github project page.

Any comments and bugs reports are welcomed.

Monday, July 6, 2009

My Guess on Symfony 2

After I read this tweet from Fabien I was left thinking on how Symfony 2 will be. Then I
remembered this presentation from Fabien where he talks about the new framework. I took a look at it and then I decide to glue the pieces together to get something working out of the
code given in slide 28.

So to start with it, I needed an application to build. Since some days ago I'm playing with MongoDB, a document oriented database. To learn how to use it I built a centralized logger for symfony applications. The idea is that if we have in production twenty machines serving symfony and then we need to parse the logs to find possible errors, etc., it will be nice to have a tool that centralizes the logs in one place. Since this database is lightweight and fast, I wrote a simple logger to store the messages in a MongoDB database instead of using the normal file logger.

After I got the logger working I needed a way to display and search through the logs. Initially I built a symfony application that was able to filter the logs by priority and by some words in the log message. The feeling I got was that a full symfony 1.2 project was too much for such a
simple web app. This was the perfect excuse to experiment with Symfony 2.

The Logger

The MongoDB logger is just a simple symfony logger that stores for every symfony log an array with this structure:

 

$log = array( 'type' => $type, 'message' => $message,
'time' => time(), 'priority' => $priority) );

The idea is to provide a form to issue queries to the database to filter the logs by any of those fields. i. e.: I type sfRouting and I should see only those logs that contain that word in their messages.

Here's a screen shot of the final application:

So, how to build that using Symfony 2?

First we create the folder structure like this:

 
-/
--/ apps
--/ config
--/ lib
--/ web

Inside web we place the index.php file which has the following content:


define('ROOT_PATH', dirname(__FILE__).'/..');

require_once ROOT_PATH . '/config/sf_requires.php';
require_once ROOT_PATH . '/config/app_requires.php';

$app = new LogAnalyzer(); $app->run()->send();

There we define the root path, and then we include two configuration files, one which will take care of requiring the Symfony libraries and the other that will require the application files.

Then we instantiate the application class and we run it.

Now let's check what's inside the sf_requires.php file:

define('SF_LIB_PATH', ROOT_PATH . '/lib/vendor/symfony/lib');

require_once SF_LIB_PATH . '/utils/sfToolkit.class.php';
require_once SF_LIB_PATH . '/utils/sfParameterHolder.class.php';
require_once SF_LIB_PATH . '/event_dispatcher/sfEventDispatcher.php';
require_once SF_LIB_PATH . '/request/sfRequestHandler.class.php';
require_once SF_LIB_PATH . '/request/sfRequest.class.php';
require_once SF_LIB_PATH . '/request/sfWebRequest.class.php';
require_once SF_LIB_PATH . '/response/sfResponse.class.php';
require_once SF_LIB_PATH . '/response/sfWebResponse.class.php';

First we define the location of the Symfony libraries and then we proceed to include the required files.

The only new class here is the sfRequestHandler which Fabien describes in his presentations, the other ones I just took from a symfony 1.3 distrubution.

With those files included, we are done with what refers to symfony, then we have to include the application files. So the contents of app_requires.php will be:

require_once ROOT_PATH . '/lib/dba/MongoLogReader.class.php';
require_once ROOT_PATH . '/lib/dba/CollectionModel.class.php';
require_once ROOT_PATH . '/apps/LogAnalyzer.class.php';

Besides the LogAnalyzer class we include two classes that will take care of querying the log database. As we can see, the LogAnalyzer class will reside under the apps folder and then others under lib/dba.

So now let's check what's inside the LogAnalyzer class.

 
public function __construct()
{
$this->dispatcher =
new sfEventDispatcher();
$this->dispatcher->connect('application.load_controller', array($this, 'loadController'));
}

On instantiation we create a new instance of the sfEventDispatcher and we connect our application to the application.loadController event which will be fired by the sfRequestHandler::handleRaw() method. There we tell it that the loadController method of our application will process the request.

Then we have the run method:

public function run()
{
$request = new sfWebRequest($this->dispatcher);
$handler = new sfRequestHandler($this->dispatcher);
$response = $handler->handle($request);
return $response;
}

Here we initialize a sfWebRequest object to start parsing the request parameters. Then we instantiate our sfRequestHandler and we call the handle method. The handle method will return a response object, which is the one where we call send() in the index.php file to output the response to the browser.

When the sfRequestHandler start to do it's job it will fire the application.load_controller event for which we set up the following listener:

public function loadController(sfEvent $event)
{
$event->setReturnValue(array(array($this, 'execute'), array($this->dispatcher, $event['request'])));
return true;
}

There we say that the method execute of the LogAnalyzer class will take care of generatiing the response data out of the request.

And finally the code for the execute method:

public function execute($dispatcher, $request)
{
$response = new sfWebResponse($dispatcher);
$response->setContent($this->render($this->getTemplateValues($request)));
return $response;
}

There we instantiate a sfWebResponse. The content of this one will be the result of the proteced method render. –Here I must say that is possible to create our own View class to
handle this part, but for this example I preffer to build it like this–.

protected function render($values)
{
extract($values);
ob_start();
ob_implicit_flush(0);
require(ROOT_PATH . '/apps/template.php');
return ob_get_clean();
}

This method expects an array with all the variables that will be used in the template. This values are extracted from the array and inserted in the current scope by calling the extract function. As we can see, the template is a simple php file that is required from the apps folder. This file is plain PHP code embedded into HTML.

The getTemplateValues method take will get the data out of the database, plus interpreting the request:

protected function getTemplateValues($request)
{
$values = array();
$values['sf_request'] = $request;
$values['collections'] = $this->getCollections();
$values['priorities'] = $this->getPriorities();
$values['cursor'] = $this->getLogs($request);
$values['pageNumber'] = $request->getParameter('page', 1);
$values['cursor']->skip(($values['pageNumber'] - 1) * $this->maxPerPage)->limit($this->maxPerPage);
$values['hasMore'] = $values['cursor']->count() > ($this->maxPerPage * $values['pageNumber']);
$values['filterParams'] = $this->buildFilterParams($request);
return $values;
}

And that's it! Which such a simple structure we can leverage the power of the sfRequestHandler class which will be at the core of the new Symfony version. We know that symfony does very well for complex projects, but sometimes I felt like it was too big for a simple application like this one. With this new component I think that this distinction will be gone.

THE CODE:

The application code and the sfMongoDBLogger class can be found here.

You will need to setup a virtual host in order to run this application.

RESOURCES/REQUIREMENTS:

To learn more on MongoDB refer to this website: http://www.mongodb.org
If you setup MongoDB as explained here http://www.mongodb.org/display/DOCS/Getting+Started, you should be able to run this project without problems –Give it a try, MongoDB is pretty easy to setup and the documentation is very good–
For the installation instructions of the PHP native driver go here

IMPORTANT:

Even if this should be implicit, keep in mind that this are my personal views on the subjects. This is by no means an official statement from the Symfony project. Is just what I believe this new component will be based on Fabien presentation.

Thursday, June 4, 2009

New Firesymfony Release

I'm pleased to announce the release of version 1.1 of Firesymfony. This time it has a new design that we believe improves the user experience. The design was made by my colleague Jacqueline Wan and the logo by Olaf Horstmann.

Bellow you can see some screenshots and here you have the urls to update the symfony plugins and the Firebug extension:

Symfony Plugin

FB Extension

About Panel:

Configuration and Variables Panel:


Logs Panel:

Cache Panel:

Database Panel:

Timers Panel:

Information Panel:

Monday, March 30, 2009

How I would like to use Propel and Memcache

In this article I will like to share some ideas that are wandering in my mind but I haven't implemented yet. So beware!

I was thinking of a way to reduce database load by caching some results inside Memcache -idea which has nothing special or revolutionary this days-. This article gave me some ideas that I would like to see implemented in some of the projects I work for.

The code is for using mostly inside a Symfony/Propel project, but could be adapted to a different one with ease.

Propel Peer classes come packed with the following method.

BaseUserPeer::retrieveByPK($pk, $con=null);

I would like to override it in the following way:

public static function retrieveByPK($pk, $con = null)
{
  $cacheKey = sprintf('user:id:%d', $pk);
  $asArray = $memcache->get($cacheKey);
  
  if($asArray === null)
  {
    $obj = parent::retrieveByPK($pk, $con);
    if($obj !== null)
    {
      $memcache->set($cacheKey, $obj->toArray(BasePeer::TYPE_FIELDNAME));
    }
  }
  else
  {
    $obj = new User();
    $obj->fromArray($asArray, BasePeer::TYPE_FIELDNAME);
  }
  
  return $obj;
}

Note that I've avoided the memcache connection code. I assume that there is a memcache class that abstracts the process. I'm also avoided the details of this class instantiation.

As you can see from the code, I store the object in memcache as an associative array. I like to do so because I don't want to store a serialized version of the Propel object which will take more space. Also by using a native data type I can warm up the cache from -let's say- a batch script without the need of using Propel at all. Also, if a I have code that don't require symfony or Propel, I can still use the cached data. 

The fromArray and toArray methods are built inside propel objects as a convenient way of populating them, so there's no extra effort in our side to get their benefits. 

As key for the cache I'm using as prefix the name of the table, followed by a colon and then the primary key column name, a colon and the primary value. i. e.: table_name:primary_key:value

Then what is left for this to actually work is to override the User::save($con = null) method, so every time the database row is updated the changes will be reflected in the cache.

I came up with the following code:

 
public function save($con = null)
{
  $affectedRows = parent::save();
  $memcache->set(sprintf('user:id:%d', $this->getId()), $this->toArray(BasePeer::TYPE_FIELDNAME));
  return $affectedRows;
}

There by updating the entry after it's saved to the database I used a traditional pattern with memcache to warm up the cache.

So lets say that in a normal login form, the user will submit his nickname and password to be checked against the database. In this case we have to do a SELECT query
using the nickname and password as WHERE parameters. Instead of issuing a query, I would like to do the following inside UserPeer::retrieveByNickname($nickname).

public static function retrieveByNickname($nickname)
{
  $nicknameCacheKey = sprintf('user:nickname:%s', $nickname);
  $userId = $memcache->get($nicknameCacheKey);
  
  if($userId !== null)
  {
    return UserPeer::retrieveByPK($userId);
  }
  else
  {
    $c = new Criteria();
    $c->add(UserPeer::NICKNAME, $nickname);
    $user = UserPeer::doSelectOne($c);
    if($user !== null)
    {
      $memcache->set(sprintf('user:id:%d', $user->getId()), $user->toArray(BasePeer::TYPE_FIELDNAME));
      $memcache->set($nicknameCacheKey, $user->getId());
    }
    
    return $user;
  }
}

In the last example first I check if there is a memcache entry with the following key: user:nickname:somenickname. The value stored will be the user id which I assume
is the primary key of the table. If the user id is not null then I delegate the call to UserPeer::retrieveByPK to do the job. In the other case I fetch the user from
the database using the nickname as Criteria condition. If the record exists I store inside memcache the user data as an array and also I store the id using the
user:nickname:somenickname key. Now should be clear why in the previous example I used used:id:somenid as key.

Some improvements to do in the retrieveByPK method and in the save method will be to also store the respective values for the user:nickname:somenickname key once
the object has been populated. In this way we increase the chances that retrieveByNickname will successfully hit the memcache.

I hope this article results useful for you and thanks for reading.

NOTE: I know that the code is pretty ugly, some code needs to be refactored out to it's own methods and maybe Propel classes are not the best place for
this caching logic to reside, but I think is a nice example to build upon.

Tuesday, March 3, 2009

Symfony Speed and Hello World Benchmarks

After reading some posts showing that my blah blah framework is way more fast than symfony for a Hello World application I decided to explain why: because symfony is extensible and can adapt to your needs. That’s easy to say you may think, in fact, every framework out there claims that. So what makes symfony so special?


The following list names some of the features provided by symfony.

  • Factories
  • The Filter Chain 
  • The Configuration Cascade
  • The Plugins System
  • Controller adaptability
  • View adaptability


Factories


Since version 1.0 symfony provides a configuration file called factories.yml. This file affects the application core classes configuration. There you can override symfony default classes by your own ones. This means you can set up a custom Front Controller, Web Request, Cache classes, Session storage, etc. 


But this come with an extra price: when a symfony application bootstraps, it reads the configuration file from the filesystem. If the yml file was parsed before, then it loads a PHP file -which can also be cached with APC-, if not, then it parses the yml file, stores the parsed file on the cache folder and loads the configuration from there.


Why should I need that flexibility you may ask? In a project I’m involved with we needed Memcached. This means that we overrode all of the symfony default cache mechanism by our own custom classes. How? Setting up our classes in an easy to read yml file. So for sure when you benchmark your Hello World application symfony will be slower.


Filter Chain


One of the patterns from the Core J2EE Patterns book that impressed me the most is the Intercepting Filter. This patterns teach how to modify a request processing without the need to change Controllers or Model code. The idea is that in a configuration file you plug a class that will take care of pre or post processing the request. This classes are called filters. As an example, you can add a filter that checks if the user has the proper credentials to execute the action she wants. Another filter can cache the response, etc. 


Symfony has the filters.yml file which can be specified by application or by module. This means that we can set filters to be executed for the whole application, and then for specific modules -let’s say for Ajax actions-, we disable them. Does your framework provides this flexibility without resorting to some kind of monkey patching techniques? No? Well symfony does. Say hello to the Filter Chain. So for sure when you benchmark your Hello World application symfony will be slower. Because it adds flexibility to the process. You want to get rid of this behavior? Sure, set up a custom controller in the factories.yml file, and in your new class override the loadFilters method.


Configuration Cascade


As explained in the configuration chapter of the symfony book, symfony allows to modify it’s behavior through some yml files. So for example we have the view.yml that tells symfony which css and javascript files it should load for the current request. We can have an application view.yml configuration and then override the settings per module. When symfony process the request it checks all this files, that’s why in the Hello World benchmark it‘s slow.


Plugins System


Symfony has a very powerful and easy to use Plugin System. It’s more than 400 plugins with a set of 200+ developers speaks by itself of it success. 


The plugins can contain modules of their own and also a config.php file, similar to the project config.php file or the module config.php. When symfony process a request it checks for the settings in those files, this means that they will be read from disk. So in a plugin we can provide a custom logger that is fired up when bootstrapping the application. The Plugin user doesn’t need to care how the logger will be activated, she just now that it will work. 


The same applies for plugin modules. How do you think that symfony knows that certain module/action should be called from a plugin? If you enable the plugin module on the settings.yml file symfony will check inside the plugin module to see if the requested action should be executed there. 


Controller adaptability


For each action that the user call symfony will execute a page controller. Inside our modules symfony let us use a generic myModuleActions class that will extend sfActions or one specific to the action requested by the user, that as an example could be called indexAction and will extend the sfAction class. When a request is processed symfony first checks for the existence of the later. If it doesn’t exists then it tries to load the generic action for that module. Of course you don’t need this kind of flexibility for Hello World apps.


View Adaptability


For rendering the response symfony uses by default the sfPHPView class. If certain module in your application requires a different view, then there are at least three ways to accomplish this as explained here.


Conclusion


Symfony is a Professional Web Application Framework built to cope with real world needs. In a large project with more than a simple salutation feature sooner or later you will need the flexibility provided by the framework. This will save you time and will prevent headaches, because when you have built a whole system with a framework and the business needs start to push in a direction where you have to extend the framework you will thank yourself for having choose symfony at first.

In case that your client requires a Hello World! application, then you can use the following hyper fast framework code: die(“Hello World!”) ;-)

Wednesday, January 28, 2009

Integrating Facebook Hive with Symfony

A symfony feature that has been really helpful while working on projects with it is the debugging capabilities of the framework. Almost every day I see my self going to the command line an doing a tail -f log/frontend_dev.log in my symfony project. This helps me see what is going on behind the scenes, spot bugs, find places where I can improve the code, etc. If something goes wrong there is always a developer on the team that shouts “check the symfony logs”, showing that they had became an essential tool for development. 


But not everything shines under the sun. Sometimes it happens that we would like to have a tool that allows us to filter the logs according to specific criteria. A tool that goes beyond a simple cat logs/frontend_dev.log | grep SELECT. We wanted to perform some analysis on the website usage, basically to help us improve it performance.


A colleague talked about Facebook Scribe, that was not actually related with this dream tool but later lead me to learn about the existence of the Apache Hive project (which was started by Facebook).


The Hive project allows to load a logs file and then filter it with SQL like commands. -Hive is more than this raw description, you can read more about it here.


My idea was to adapt the file logging format of symfony to make easy to import those files inside a Hive database. Because Hive support table partitioning by date, it should be easier to load the data from the logs and then perform the analysis with the SQL like syntax provided by Hive.


After some fights with Ant and Java 1.6 It was possible for me to get Hive running in my Mac. Then I just created a shameless copy of the sfFileLogger and renamed it to sfHiveLogger. I did small changes here and there and got it ready to log in a format that it’s easy to load later into Hive. I browsed through my testing symfony project to generate some logs and then I moved to the command line to start the fun with Hive.


There I created a table to hold the logs with the following command:


CREATE TABLE sflogs(

  logTime STRING, 

  priorityName STRING,

  message STRING

  COMMENT 'This is the sflogs table' 

  PARTITIONED BY(dt STRING) 

  ROW FORMAT DELIMITED

    FIELDS TERMINATED BY '\011' 

    LINES TERMINATED BY '\012';


Everything was working smoothly. The next step was to load the data from the logs file into the sflogs table to start issuing queries to it. I did it with this command:


LOAD DATA LOCAL INPATH '/path/to/myproject/log/frontend_dev.log.2009-01-28' INTO TABLE sflogs PARTITION (dt='2009-01-28');


With the data loaded I started to issue some commands against the Hive console like:


SELECT * FROM sflogs WHERE message LIKE "{sfRequest}%";


SELECT DISTINCT priorityname FROM sflogs;


SELECT COUNT(1) FROM sflogs;


The results were similar as when we work on a mysql client which was awesome. What amazed me the most was how with some easy changes it's possible to adapt symfony to our needs. But which were this changes? Here they are:

First we need to enable the sfHiveLogger in the logging.yml file of your symfony project under the sf_file_debug entry


Then do the shameless copy of the symfony class to your lib folder and rename it to sfHiveLogger.


Then change the code of the initialize method to look like this:


    if (!isset($options['file']))

    {

      throw new sfConfigurationException('File option is mandatory for a file logger');

    }


    $dir = dirname($options['file']);


    if (!is_dir($dir))

    {

      mkdir($dir, 0777, 1);

    }

    

    $logFileName = $options['file'] . '.' . date('Y-m-d');


    $fileExists = file_exists($logFileName);

    if (!is_writable($dir) || ($fileExists && !is_writable($logFileName)))

    {

      throw new sfFileException(sprintf('Unable to open the log file "%s" for writing', $logFileName));

    }


    $this->fp = fopen($logFileName, 'a');

    if (!$fileExists)

    {

      chmod($logFileName, 0666);

    }


Basically the change there is to append the current date in “Y-m-d-” format at the end of the logs file name. This will make easier to import the logs into Hive -only if you want them partitioned by date-.


Then on the log method change the line with:


$line = sprintf("%s %s [%s] %s%s", strftime('%b %d %H:%M:%S'), 'symfony', $priorityName, $message, DIRECTORY_SEPARATOR == '\\' ? "\r\n" : "\n");


to:


$line = sprintf("%s\t%s\t%s%s", strftime('%b %d %H:%M:%S'), $priorityName, $message, "\n");


Here what we do is to apply a little formating there. The most important part is to have the tab and then new line characters as delimiters because this was what we specified in the CREATE TABLE command above.


With this easy steps we can have a Hive enabled log file. If you want to learn more about Hive and the supported commands and the theory behind it please refer to the wiki.


Conclusion:


Besides that I’m still comparing Hive with other solutions to parse and analyze the logs, I think that this tool has a lot of potential to help debugging and profiling symfony applications. If we polish the log format and refine the table structure, then it’s shouldn’t be hard to setup some cronjobs that generate reports of the website usage, improving the usability of the symfony logs.

Monday, January 26, 2009

Custom Views in Symfony 1.0

After seeing this feature request for symfony 1.3 I decided to write a tutorial explaining how to use custom views with symfony 1.0. Yes symfony 1.0 has this feature, a little bit hide inside, but is there since the ol’ good days.

So, there are three ways to have a custom View class for your actions. To show how this work, we will need a project with a module called example

First example:

After you have your module ready add an action called FirstExample which will have this code: 

public function executeFirstExample() {}

Then on the example module add a folder called views. Inside it we will add our custom view class. The name will be firstExampleSuccessView. Create a firstExampleSuccessView.class.php file with the following code inside:

class firstExampleSuccessView extends sfPHPView
{
  
}

As you can see, it extends the sfPHPView provided by symfony,  to get all the functionality inside. Please note that custom views in symfony need to extend sfView, which is the parent class of sfPHPView. 

At this point I’m going to tell you that we can customize a lot of features of the symfony views. For the tutorial I will just show you how to have new shortcut variables inside the templates like the $sf_request or $sf_user already provided by symfony. Feel free to comment about your experience extending the sfView.

To add a new shortcut to the template we need to extend the following method:
sfPHPView::getGlobalVars(). We will add a new shortcut smartly called my_view_data. with a dummy array inside. This will be our code:

protected function getGlobalVars()
{
  $context = $this->getContext();

   $shortcuts = array(
     'sf_context' => $context,
     'sf_params'  => $context->getRequest()->
getParameterHolder(),
     'sf_request' => $context->getRequest(),
     'sf_user'    => $context->getUser(),
     'sf_view'    => $this,
     'my_view_data' => array('foo' => 'this data came from firstExampleSuccessView')
  );

 if (sfConfig::get('sf_use_flash'))
 {
   $sf_flash = new sfParameterHolder();
   $sf_flash->add($context->getUser()->
  getAttributeHolder()->
  getAll('symfony/flash'));
     $shortcuts['sf_flash'] = $sf_flash;
   }

    return $shortcuts;
}

As you can see there is a new my_view_data key with a one element array.

To see this in action we add in the example/templates folder a file called firstExampleSuccess.php with the following content:

<h1>First Example View:</h1>
<?php var_dump($my_view_data); ?>

Clear the cache and point your browser to http://yourapp.com/example/firstExample

There you will see the output of our new shortcut.

As long as our action returns sfView::SUCCESS it will use our new custom view class. Who said that symfony had no custom views? :-)

Remember to follow the naming convention of: <actionName><viewName>View. In our example we had firstExampleSuccessView. firstExample is the action, Success is on of the possible values that our actions can return, View is the default suffix. 

Second Example

In this case we will create a class called secondExampleView inside a file secondExampleView.class.php located in the lib folder of our application. The contents of this class will be almost the same that four our first view.  We will change the class name to secondExampleView and the my_view_data entry will be declared like this:

'my_view_data' => array('foo' => 'this data came from secondExampleView')

Then we create a new action for our module with the following code:

public function executeSecondExample(){}

Then we add the template secondExampleSuccess.php with this code inside:

<h1>Second Example View:</h1>
<?php var_dump($my_view_data); ?>

Now, how do we use our new view with this action? In case we don’t have one, we add a module.yml file in the config folder of the example module. There we add the following content:

all:
  view_class: secondExample

This will tell symfony to use our custom view class for this module. As you can see here,  we can specify different views per environment :-) Yes,  symfony is configurable :-) 

If we point our browsers to the secondExample action we will see the contests of our shortcut.

Third Example:

For this example we will create a class called thirdExampleView inside the lib folder of our application. As we did with our secondExampleView we will have the same code, except for the class name and the my_view_data shortcut that will have inside the following: 

array('foo' => 'this data came from thirdExampleView')

We add a template called thirdExampleSuccess.php with this content:

<h1>First Example View:</h1>
<?php var_dump($my_view_data); ?>

and we create an action like this:

public function executeThirdExample()
{
    $module = $this->getRequestParameter('module');
    $action = $this->getRequestParameter('action');
    $this->getRequest()->setAttribute($module.'_'.$action.'_view_name', 'thirdExample', 'symfony/action/view');
}

What we do here is setting in the 'symfony/action/view' namespace of the sfRequest attribute holder a value with our view class without the View suffix. The key of the attribute should be composed by <module_name>_<action_name>_view_name  as you can see from the code above.

Then we can point our browsers to the thirdExample action to see the results.

With this three ways we can set up your own views and customize the rendering process of our applications. 

As final note I want to add how symfony knows which view class to pick.

  • It checks inside the module/views folder to see if it can find our custom class as explained on the first example.
  • If there is no user class there, it will check if we have set up one in the sfRequest as explained on example three.
  • Then it will try to see if we defined a view in the module.yml file.
  • As last resort it will load the default sfPHPView class.

Thanks for reading this far and I hope this will help you on your projects.

Tuesday, December 16, 2008

Symfony Design Patterns

Much has been said this days about how modern web frameworks interpret the MVC architectural pattern. I'm my case those articles made me rethink how I use a framework, specially Symfony.

This lead me to start a study on which patterns come into play while we develop a Symfony application. So while adding new features or refactoring existing ones I will know which class is in charge off doing the job.

Some of the patterns involved in Symfony are:
  • Front Controller
  • Command
  • Intercepting Filter
  • Context Object
  • Two Step View
  • Helper Object or View Helper
  • Table Data Gateway (i.e.: ArticlePeer.php)
  • Row Data Gateway (i.e.: Article.php)
  • Active Record
  • Single Table Inheritance
How do they interoperate? The Front Controller act as an entry point for our application. After it processes the request it will choose which Command should handle the request, in our case, one of our defined actions, that's why we have the execute prefix as a convention for the action
names. 

While the request is being processed Symfony filter chain will launch modifying the results accordingly. The idea of the Intercepting Filter pattern is to be possible to pre and post modify the request and the response without the need of change existing code. What's is a nice feature in Symfony is that we can add them directly on the filters.yml file making really convenient to use. Also we can specify filters that only apply for certain modules, making the process really flexible.

On the View side Symfony uses the Two Step View, so generally the result of our request will be decorated with a common layout to add consistency to our website. Here we can also use helpers, as they are explained in the Symfony documentation or add Helper classes. The Helpers classes can retrieve data from the Model, format data, or anything our application needs to work on the View side. With them we make our templates clear and free off script code, so it's easy to non developers to work on them.

Also Symfony allows to use specific view classes by module, adding extra flexibility in the way we can handle the response, let's say, to return JSON data.

As a way to keep a reference to the context, Symfony implement the -some guess please- Context Object pattern, which I won't talk about here.

Then we have the DAL, which in my case is powered by Propel. This ORM implements the Table Data Gateway and the Row Data Gateway patterns. As they name implies they act as gateways to our tables and rows, but because generally we add some domain logic inside them they start to act as Active Records.

A cool feature of Propel is that it implements the Single Table Inheritance pattern. This pattern is useful when we have a table for publications where we store Magazines and Books. If our table has a field acting as the type of the row, and in our application we treat them differently, whether if they are books or magazines, then this pattern let us work with Publication, Book and Magazine classes.

After this research I came with the following conclusion:
  • Use the controllers to handle user input data and choose which view to display.
  • If there, remove all business logic from the controllers.
  • Let the Model do it's job with the provided data. 
  • When handling data to the model, do it as an array of normalized data -or proper objects pertinent to our application-, the model shouldn't know about the request.
  • Let helpers or Helper Objects handle the data formating, avoiding script code inside templates.
  • Use Propel as a DAL and refactor towards a Model. This means to remove complex business logic from the Propel classes.
What helped my during this research where the following books, which I highly recommend.

Monday, October 13, 2008

New Version of FireSymfony - New Features & Improvements

The last week has been a good one for the project, along with new cool features arrives also the news that the project will be supported by the company I work for -TheNetCircle-. 
This means that they will provide hosting for the soon to be released  official project web site and blog and that I can pick from time to time some hours to write some docs for the project or work on a specific feature. That doesn't means that I won't keep working on the extension on my free time at home... It means that now I have more reasons to go on and keep improving this project.   

About what's new on this release...

Cache Panel

If you access your website on the dev environment with the new version of the extension you will notice that there is no cache information displayed on the page. That's because I removed all the information divs added by symfony and placed them inside a Cache Panel on FireSymfony. What does this means? In that panel you will see a table displaying on each row all the cache information of a given partial: lifetime, last modification time, and cache key. If you roll over a row then the cached partial will be highlighted on the page just the same way FireBug highlights elements when you are inspecting your web page.  For more information about it check this page: CacheTab.

Database Panel

When you access this panel you will see what looks like a normal symfony web debug toolbar database panel -wow that's a mouth full- but if you click on any of the displayed queries, then it will get copied to the clipboard, so you can go and paste it for debugging on your SQL client of choice.  

New Splash Page with Logo

Thanks to the work of Olaf Horstmann the splash page of the extension has a new and fresh look. You can check by yourself.



Small Improvements

I also added some small improvements to the user interface. Now the panels switching buttons show the state, so you wont get lost guessing in which panel are you. I refactored the code and fixed bugs. 
On the symfony plugin side I added a fsNoJsonException that will be thrown in case there is no json_encode support on the server. Also now the json object is called FireSymfonyDebugData to avoid javascript namespace clashing. So in this version you also need to upgrade the plugin.

As you can read here I was also playing on how to port the extension to the cool WebKit Web Inspector, so soon there will be a version for that browser family too -that includes Google Chrome and Safari-.

Friday, October 3, 2008

FireSymfony featured at Ajaxian.com

I'm more than glad to announce that the FireSymfony extension has been featured at Ajaxian.
A big thanks to them for supporting the project. 

Also I want to report that there is a new version of the extension for download from the mozilla add-ons web page. The reason of the update was to fix an incompatibility bug with Prototype.

The symfony plugin has been updated as well, fixing bugs as reported on the project web page. Also I started the project wiki adding detailed setup instructions for the plugin.


Wednesday, October 1, 2008

FireSymfony - First release is out

I'm happy to announce the release of both the FireSymfony extension for Firebug and the firesymfonyPlugin for symfony.

The extension can be downloaded from the mozilla add ons web site. The symfony plugin is available as a pear package from the framework plugins website.

So those that live on the edge can start testing this plugin.

I also created a google group where you can provide feedback and request new features, as well as have some discussions about it.

In a future post I will provide more details about upcoming features and roadmap.

Some screenshots:


Tuesday, September 30, 2008

FireSymfony - A Firebug extension for symfony

While developing websites is nice to have at hand the right tools to do the job effectively. One of the tools I like the most is Firebug for all it’s debugging capabilities. Also, because I develop websites using symfony, the web debug toolbar provided by the framework is very handy to know what was happening in the server while the request was processed.


But sometimes the toolbar position makes impossible to use some features of the layout of our website, like a link menu on the top right corner. It also happens that while we display a small popup with the resize functionality disabled it’s turns hard to access all the data displayed by the toolbar.


The solution I’ve came up with is to move all the data from the toolbar to Firebug, actually, to port the symfony web debug toolbar as a Firebug extension. This will remove the toolbar from the page html and will show it in a convenient place that almost every web developer is used to.


Taking advantage from the cool new features of symfony 1.2 I started a project to develop a symfony plugin to send the data to the Firebug extension. The later has been smartly called FireSymfony.


The roadmap I'm following is the next one:

  1. Extend the sfWebDebug class and it’s panels to send JSON data to the browser and not html markup. (Thanks to the neat code design of symfony this task has been really easy).
  2. Create the extension layout inside Firebug to present the same functionality provided by the web debug toolbar.
  3. Add some CSS styles to make it look pretty.


Currently I’m finishing the second step and the idea is to release both the symfony plugin and the Firebug extension during october, so stay tuned.


Below I added a screenshot of the current state of the project. Please provide feedback about the idea so I can try to make it better and useful for everyone. 






 


Tuesday, September 2, 2008

We started something!!

It seems that our benchmarking example has pushed people to do their useful benchmarks. 
You can check this framework benchmarks page and try to guess what are they actually benchmarking. If you can find the point of that benchmark, please drop some comments here, because I want to sleep with ease tonight.

So I want to left here a just a few remarks about this kind of stuff:
  1. Stop benchmarking your just created framework against symfony, Zend Framework, Cake, or whatever.
  2. When are we going to realize that the point of a framework is not to run as fast as assembly code, but to improve developer productivity and save money in developer time?
  3. I'm not pissed off, I just can't get the point of those benchmarks.  
If you have more examples of this kind of useless benchmarks, please add them to the comments.