Symfony World blog is not maintained anymore. Check new sys.exit() programming blog.

symfony sfAdminDashPlugin icons

sfAdminDashPlugin is one of the most useful symfony plugins (it's the 8th most popular plugin at the moment). I want to show few ways how to easily extend your backend application with custom icons: where to get them from and how to make your project use them. It seems not very complicated - and it's not :)! But the way your website looks like is still very important.



sfAdminDashPlugin built-in login screen

sfAdminDashPlugin is provided with some standard icons, but probably you'll need more of them. Especially that it's really easy to add new ones - just follow these steps:

  • find icon(s) that have the folowing sizes: 16x16 and 48x48, should be in .png format and be transparent in the background
  • copy those files to the plugin directories: PROJECT/plugins/sfAdminDashPlugin/web/images/icons - for the 48x48 file and PROJECT/plugins/sfAdminDashPlugin/web/images/icons/small for the 16x16 file
  • use new icon(s) in your config/app.yml configuration file:
    items:
      Articles:
        url:          article
        image:        CustomIcon.png
    



set of icons provided with sfAdminDashPlugin

desktop environment

The easiest place to look for your icons is your desktop environment resources directory. For example, I use KDE which stores its icons in

/usr/share/icons/
/usr/share/icons/default.kde4/16x16
/usr/share/icons/default.kde4/48x48


external resources

KDE has lots of icons added by users, which you can find here. I have downloaded several packages and whenever I need a specific icon, I look through them. Good thing about icon themes is that large amount of icons are created in the same style, which would make your backend look even better!



nuvola KDE icon theme

Of course, you may also find nice icons at special websites, such as

and many more (these two I use the most - to see the rest, ask google)...



icon usage example


drop-down menu example


icon usage example


drop-down menu example


icon usage example

simple mailing system with symfony - part I

This article is the beginning of a tutorial presenting an easy way to implement database-templated-mailing system for symfony projects.


the basics


The basic version uses 2 tables: mail_queue and mail_template. For each type of mails, one template object is created.


mail_template

name, description - name & description of the template
template_code - (HTML, including variables marked as {variable})
template_data - what variables are defined, just a description, no calculations performed


For each single mail to be sent, one queue object is created.


mail_queue

template_id - which template to use
mail_data - serialized array: variables=>values
mail_recipent - formatted/serialized recipent information
mail_author - as above
mail_subject - string
send_at - time that this email should be sent
sent - boolean, already sent


Table structure for mail_template and mail_queue tables

E-mail is sent when sent=0 (and marked 1 afterwards, not to be sent again; in case of errors - mark as 2) and when actual time has gone beyond send_at. send_at column is especially useful, as you may create emails in a long time advance. For example, you want to send a reminder E-mail: create a new MailQueue object with its datetime value equal to current timestamp + two weeks. This would make the E-mail to be sent in two weeks time from now.


example usage


What kind of mails can we define? It depends on your project functionalities. For example, if your project is a stock managing software that stores information about products and their states, you can send important E-mails when one of the following situations take place:

  • status of a product is changing - other employees shall be informed about that,
  • quantity of a product reached alarming level - supply needed immediately,
  • inform customers when product becomes available again,
  • and so on...
A template object is created for each of the situations defined above. There's a MailTools class with static methods processing mail sending. Just like notifyProductStatusHasChanged method, taking two parameters: Product and status change. This method creates a mail_queue instance, defining specific data for this type of E-mail, using Article object passed as the parameter.


some details on implementation


Symfony Swift Mailer integration is provided with a ready to use mail queueing system. However, I decided to create a special task, defined to send E-mails that are marked with sent=0. The code is pretty obvious:

$mails = Doctrine::getTable('MailQueue')
  ->getMails2bSentQuery()
  ->execute();
 
foreach($mails as $m)
{
  $m->send();
}

Above task is run by a cron job (every few minutes). getMails2bSentQuery method looks for MailQueue objects that have sent=0 and send_at <= now. For each of those objects, the template code (HTML) is filled with variable data and sent afterwards. The mail data is unserialized first and each variable is injected into the HTML code using str_replace function:



protected function generateContent()
{
  $code = $this->getTemplate()->getTemplateCode();
  $data = unserialize($this->getMailData());
  foreach($data as $key => $value)
    $code = str_replace('{'.$key.'}', $value, $code);
  return $code;
}

Each mail template is defined using HTML/CSS code. It works similar to smarty template engine. Just define a list of variables of and all occurences of {VARIABLE} will be replaced with the parameter value you pass.


to be continued

This was just a brief overview. In the near future, precise code examples will be provided in tutorial's part 2.

Doctrine SoftDelete behavior usage

Scene from "The Neverending Story" by Wolfgang Petersen (1984)

SoftDelete

Recently, I created a detailed list of Doctrine behaviors ready to use in symfony projects. Among them, there is the SoftDelete behavior I want to focus on this time. For a chosen model, it adds a deleted_at column, which defines if a record has been marked as deleted (and if so, when).


where to use it?

Suppose your system needs to store all the detailed data history, every modification has to be marked and stay there forever. And even if the application allows users to delete objects, they are not really deleted - it is just an abstraction layer - in fact, objects are marked as deleted, but they stay in the database (and the application treats them as if they were deleted). This is where SoftDelete comes handy.


the code

All you gotta do is just to state that a given model is SoftDelete:

GivenModel:
  actAs:
    SoftDelete: ~
After this, the deleted_at column is added (of course, both to SQL and PHP class). But that's not all the job. Unfortunately, you have to tell the application how to use this column manually. Fortunately, this is really easy. For example, if you have an admin generated module, soft-deleted objects shall never appear in the list. Add/modify the buildQuery action of the admin module:
protected function buildQuery()
  {
    return parent::buildQuery()
      ->andWhere('deleted_at IS NULL');
  }
Additionally, if you want to have a full protection, you should update your admin module edit action to disable executing it for a soft-deleted object. So this was for the backend. For frontend, you shall modify your eventual data retrieving table class methods, like the following:
public function getObjectByIdQuery($id)
  {
    return Doctrine_Query::create()
      ->from('GivenModel gm')
      ->where('gm.id = ?', $id)
      ->andWhere('gm.deleted_at IS NULL');
  }
Of course, soft-deleted objects can NEVER be accessible from any type of frontend applications. There is no way to give a detailed list of modifications you need to provide 100% data protection, because all applications have different structure - you have to go through your functionalities on your own. These code lines above are just examples you will probably use.


enable DQL callbacks

Alternatively, you may enable DQL callbacks for your models in each of your config/ProjectConfiguration.class.php files:

public function configureDoctrine(Doctrine_Manager $manager)
  {
    // Enable callbacks so that softDelete behavior can be used
    $manager->setAttribute(Doctrine_Core::ATTR_USE_DQL_CALLBACKS, true);
  }
this will dynamically add an andWhere DQL clause to all SELECT queries for SoftDelete models (Doctrine_Template_Listener_SoftDelete::preDqlDelete()). Thanks to it, you don't have to manually update all possible SELECT queries in your project (such as buildQuery mentioned above).


be careful!

As the official behavior docs say, SoftDelete overrides the delete() method. When delete() is called, instead of deleting the record from the database, a deleted_at date is set. This means, if you try to delete records without using Doctrine_Record::delete() method, SoftDelete won't work! To provide 100% data protection covering SoftDelete you should replace all code like

Doctrine_Query::create()
  ->delete()
  ->from('GivenModel gm')
  ->where('gm.id = ?', $id)
  ->execute();
with
$objects = Doctrine_Query::create()
  ->from('Manufacturer m')
  ->where('m.id = ?', $id)
  ->execute();
 
foreach($objects as $object)
  $object->delete();
to make the Doctrine_Record::delete() be called whenever an object is supposed to be deleted, which finally means that the desired SoftDelete behavior is fired always when it should be.


objects related to SoftDelete-able objects

But there comes another question - what to do with related records of a soft-deleted object? For example, we have two model classes with the SoftDelete behavior: Manufacturer and Supplier which have a m:n relation table, ManufacturerSupplier which defines who supplies which manufacturers. If some specific manufacturer and supplier objects are marked as soft-deleted, their corresponding ManufacturerSupplier object (if it exists, let's suppose it does) is left and no one knows how to treat it. It is not marked as soft-deleted, since it hasn't got SoftDelete behavior. And it still exists in the database, like a full-fledged record. Probably, it does not raise any application problems, but there is a data consistency question - what does such m:n table record represent, when at least one of its related objects are soft-deleted? Shall it be soft-deleted after the related master object is soft-deleted? Or maybe hard-deleted?

MySQL error on rename when dropping column with a constraint

a nasty MySQL bug


Recently I wanted to remove a column from phpmyadmin that is covered with a constraint. Impossible to do so, MySQL throws following error:

ERROR 1025 (HY000): Error on rename of './optiner_prestadmin/#sql-6bc_3e6' to './optiner_prestadmin/order_history' (errno: 150)


You may get above error when executing a query removing index, e.g.

ALTER TABLE order_history DROP INDEX user_id;
Fortunately, there is a solution, not very easy to find in the internet (I used comments from this article).

  1. execute the following in MySQL:
    SHOW CREATE TABLE order_history;
    You shall get something like:
    order_history | CREATE TABLE `order_history` (
      `order_id` bigint(20) NOT NULL,
      `user_id` bigint(20) NOT NULL,
      `status_id` bigint(20) NOT NULL,
      `informed` tinyint(1) DEFAULT '0',
      `comment` text,
      `created_at` datetime NOT NULL,
      `created_by` bigint(20) NOT NULL DEFAULT '1',
      PRIMARY KEY (`id`),
      KEY `order_id_idx` (`order_id`),
      KEY `user_id_idx` (`user_id`),
      KEY `status_id_idx` (`status_id`),
      KEY `created_by_idx` (`created_by`),
      CONSTRAINT `order_history_created_by_sf_guard_user_id`
        FOREIGN KEY (`created_by`) REFERENCES `sf_guard_user` (`id`)
          ON DELETE CASCADE,
      CONSTRAINT `order_history_order_id_order_info_id`
        FOREIGN KEY (`order_id`) REFERENCES `order_info` (`id`),
      CONSTRAINT `order_history_status_id_order_status_id`
        FOREIGN KEY (`status_id`) REFERENCES `order_status` (`id`),
      CONSTRAINT `order_history_user_id_sf_guard_user_id`
        FOREIGN KEY (`user_id`) REFERENCES `sf_guard_user` (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=28305 DEFAULT CHARSET=utf8
  2. we want to delete the whole column user_id (with all corresponding indices, constraints, etc.) - need to drop the FOREIGN KEY first:
    ALTER TABLE order_history
      DROP FOREIGN KEY `order_history_user_id_sf_guard_user_id`;
    you'll find the name of the foreign key in the last constraint clause.
  3. finally, drop the column:
    ALTER TABLE `order_history` DROP `user_id`;
    and that's it!
    Query OK, 28065 rows affected (3,99 sec)
    Records: 28065  Duplicates: 0  Warnings: 0

symfony & Doctrine behavior review

Doctrine behaviors

The main idea of this article is to create a short summary on Doctrine behaviors (really powerful and useful tools) which speed up development significantly. I tried to mention the most important behaviors, providing brief description and links. It's quite common to reinvent the wheel, being just unconscious that someone has already implemented a feature you are just in need of. Moreover, I hope that both beginners and advanced symfony developers will find this article a useful source of brief information about Doctrine behaviors for everyday use.


Basically, a behavior lets you share some relations, algorithms and other features among your project's model. They are (or at least should be) highly configurable: providing you a mechanism that can be easily customized to fit in different projects. Customization usually includes: enabling/disabling some of the behavior features (you may use only some of them), renaming additional schema columns, configuring them (like adding 'not null'), etc. You can use many Doctrine behaviors in your projects: core behaviors, Doctrine extensions, symfony plugins - or if you've got nothing better to do late at nights - you can create your own behaviors ;)


Core behaviors

The Doctrine ORM is bundled with several behaviors (calles core behaviors), these are:

  • Versionable - add an entire XxxVersion table to store your Xxx model object versions as either only version numbers or numbers versions along with ALL column data - to see object data changes through time,
  • Timestampable - probably the most popular of all behaviors, adds created_at and updated_at timestamp columns to your model, automatically saving datetime when a record is created or updated, respecticely,
  • Sluggable - adds a slug column that stores a slug which is a unique index automatically and can be used along with sfDoctrineRoute class to refer to an object without passing its ID as a parameter; by default the update is disabled, since this may harm your search engines rating,
  • I18n - add an entire XxxTranslation table to provide Internationalization (I18n) for your Xxx model, essential when developing a multi-language project,
  • NestedSet - adds root_id, lft, rgt and level columns to your model to develop an advanced hierarchical data structure (such as product categories), nested sets is an alternative to adjacency model, more details on hierarchical data here,
  • Searchable - choose model columns you want to index and add an entire database table, speeding up a basic search engine development, more info about searching here,
  • Geographical - adds longitude and latitude columns storing geographical coordinates. Ever needed to use gmaps in your project, along with sfEasyGMapPlugin? Not only this behavior suits the data structure you need, but also provides you with getDistance() method to calculate distance between two Geographical objects,
  • SoftDelete - adds a deleted_at column which defines if a record has been marked as deleted (and if so, when). Useful when designing a highly complicated system where data consistency is important and even if some data should be invisible in the backend, it should still remain in the database.

extension behaviors

You may also use Doctrine extensions:

  • Blameable - adds an additional level of auditing capabilities to models, blameable allows you to track who created or last updated any model in an environment with many users, blameable is a great companion to Timestampable behavior,
  • EventLoggable, readme - logs any Doctrine Events (pre/post Delete/Insert/...) fired upon a record, log type may be chosen (e.g. file) and events may be limited to the chosen ones only,
  • Localizable - gives you functionality to convert units of measurement (e.g. kilometers to miles, etc.) or any other conversions desirable,
  • Locatable - gives you functionality using the Google Maps API to automatically populate your model with latitude and longitide information using Google, a fantastic tool to use along with sfEasyGMapPlugin,
  • Sortable - gives you sortable functionality to your models, enabling you to easily fetch next/previous objects, moving them up/down in a sorted list or swapping with another object,
  • Taggable - adds Tagging capabilities, creates a TaggableTag table and a XxxTaggableTag table for each taggable model, using refClass m:n db table relation, provides easy tag management: add, remove, remove all, set, etc, finally, gives you the possibility to get the most popular tags (for a tag cloud, for example) fetched either from chosen models or from all of them,

plugin behaviors

Finally, there are also symfony plugins providing custom behaviors:


useful links about Doctrine behaviors:


notes

If you find any information missing, please, let me know. Hopefully, you'll find it useful, otherwise I'll lose the motivation to do such thing next time ;)

update record changes with sfDoctrineActAsSignablePlugin: Signable behavior

sfDoctrineActAsSignablePlugin


The plugin is fairly simple, it contains only a Doctrine temlate and a template event listener (basic stuff to implement a new Doctrine Behavior). It was initially released by Vitaliy Tverdokhlib.


Signable Doctrine Behavior


The plugin provides a Signable behavior ready to be used in your models:

Model:
  actAs:
    Signable: ~
It works very similarly to Timestampable behavior: created_at and updated_at timestamp columns are added, storing information about the datetime that a record was created and last updated. Signable adds created_by and updated_by columns storing information about the user that created or last updated a record.


The plugin may be used along with sfDoctrineGuardPlugin. This gives you a possibility to refer to the sf_guard_user table. No additional configuration is needed, the plugin automatically checks if sfDoctrineGuardPlugin is installed.


configuration


The most important thing you can configure is to choose the type of the user field: it can be either string or integer:

Model:
  actAs:
    Signable:
      created:
        type: string
      updated:
        type: integer
If string type is chosen, you can simply display the name of the user (e.g. in frontend blog article display page), but can do nothing further. If you choose integer, you store ID of the user, which gives you a possibility to refer to the user in the user database table (which gives you all possibilities of using user Doctrine model).


You can configure the behavior to meet your needs. Let's take a look at some examples:


  • The created_by and updated_by column names are the default ones. You may change them:
    Model:
      actAs:
        Signable:
          created:
            name: creator_id
            type: integer
          updated:
            name: update_username
            type: string
    

  • You want to know which user has created a record only (meaning that the user who updated the record is not important). In this case, disable udpated option:
    Model:
      actAs:
        Signable:
          updated:
            disabled: true
    

  • You want to add the Signable behavior to models of an existing project that already maintaines lots of data. And you want the created_by/updated_by user to be not null, because there's always someone who creates or updates a record. In a big project (a CRM, ERP, etc.) it's a good solution to create a system user (with id=1 for example) who represents all system actions (a daemon creating/updating objects from cron tasks):
    Model:
      actAs:
        Signable:
          created:
            options:
              notnull: true
              default: 1
          updated:
            options:
              notnull: true
              default: 1
    
    In this case, adding created_by/updated_by database columns will fill them initially with 1 (system user) and you can refer to the user (e.g. using getUser() method), being sure that there's always a user referenced. And all further modifications will store real user ids.


real world examples



  • Blog articles are being added by application users. You add Signable behavior (with type: string) and you may use:
    echo $article->getCreatedBy();
    in the app frontend to display who submitted the article.

  • An E-commerce applications management system: users (employees) log in to the system and create orders that customers submit. You add Signable behavior (with type: integer). Whenever an order is created, the created_by is filled with the user ID. The application can generate statistics on which users added the most orders.


No matter if your project is a complex management system with extended backend - or a small company website with mainly frontend developed - you'll find this plugin useful!

new releases of sfApplicationMapPlugin

application map

sfApplicationMapPlugin is an easily configurable documentation-generator tool. It generates a graphical map of your applications using GraphViz software. It is useful when you want to have an overwiev of a specific application or of a whole project - each module/admin-module is marked with all actions and their comments. Take a look at the plugin readme tab to see some examples. You need to have GraphViz installed on your system to use the plugin.




new release

New plugin versions supporting symfony 1.2-1.4 has been released. Thanks to bugs reported by Gordon Bazeley, the plugin should work correctly with no dependency on operating system.




an official GraphViz-based software

sfApplicationMapPlugin is listed as a Software Engineering Tool on the official Graphviz website.


todo and contribution

The main thing to be done is supporting plugins used in projects. Any contribution is welcome, as well as bug reports and questions.

Symfony framework instead of a well-known CMS

Scene from "All Friends Here" aka "Our Folks" aka "Sami Swoi" by Sylwester Chęciński (1967)

Recently I got involved in developing and deploying a small website promoting a book that has just been released. My coordinator asked me to choose one of the most popular Content Management Systems (Wordpress, Joomla, Drupal) to deploy the site on. Since I have a very little experience with developing apps based on a full-featured CMS, I was not so happy to be forced to learn a CMS just for a single small project.


I was given a full, well-prepared layout. The first step was to replace the default layout with the one I have just received. I took a look at Wordpress (because it seems to be the easiest and the smallest CMS). A glance at the template files has given me an overview stating that I will really need to spend some time to fully understand the wordpress templating system. Of course, it doesn't mean that it's difficult or out of my reach - I'm just lazy, as most of us :) - and I prefer designing complex and complicated systems rather than studying boring wordpress engine...


I got an idea of giving up wordpress (after 20 minutes of reading documentation)... At first, it sounded quite crazy - to develop a small site with such a powerful tool like symfony. I asked myself questions like 'do I really need such an advanced tool to develop such a trivial project?' or 'how long is it going to take me to implement all mechanisms that are already implemented in wordpress (such as nice looking menu, panels, intuitive interface and so on)?' Maybe long... but the more I was analysing the time difference between designing the app in an unknown CMS and very well-known symfony framework, the more the symfony option was attractive.


Developing the frontend is only putting the new layout on. Backend is more complex. But still it's all very easy and extremely fast to use. Using sfAdminDashPlugin, sfJqueryReloadedPlugin and sfDoctrineGuardPlugin, the greatest 3 symfony plugins, gave me the basis of a backend engine with many features ready to be improved (e.g. I could define user credentials, I could organise the backend menu, etc). I use these 3 plugins in 99% of my projects (and suppose more people do so) and it takes me very little time to configure those plugins.


Developing backend is mainly working with generator.yml/module interface configuration, because this part is usually different in all web apps (meaning that you can copy lots of model/form/filter methods from your previous projects, to save time). If you implement any feature and use it in any project, you can always copy it later to another project (or make yourself a plugin, which is usually a better choice, because the whole community may reuse your work). Just like my AJAX backend activate/deactivate feature (see easy symfony ajax user friendly example) - after I managed to implement the feature (which originally could have taken me up to 3 hours, including studying entire symfony AJAX, ajax routing, sfJqueryReloaded and so on), now it takes me less than 5 minutes to copy it to a new place. Or to make a similar feature somewhere else. And imagine that some mechanisms, like taggable or commentable behavior can be put into plugins, which makes it even faster to use! This is one of the biggest symfony advantages - the speed on development. All of us shall use it as much as we can.


Choosing symfony for this project was a great decision. It saved me a lot of time, made me proud of how fast a small symfony project can be developed. Still, the code is comprehensible, it's kept well-documented (so the entire project is opened for any modifications). My experience I want to share with you is that symfony is a good choice also for small projects.

symfony file upload - leave original file name

If you want your uploaded files to stay with the original file name (which is not randomly generated by symfony), just add following lines to the model class you want to upload files:

public function generateXxxFileName($file)
{
  return $file->getOriginalName();
}

where Xxx is the name of the column that stores the filename in the database model related table.

symfony crons and cron task logging

Scene from "Frankenstein" by James Whale (1931)

this is really magic...

Recently, I found an update of the sfTaskLoggerPlugin. As the description says, the plugin allows you to run custom tasks and store the results. In fact, this is a highly customizable tool that provides you monitoring all cron tasks, checking their performance (objects processed, running time) and so on. Installing and configuring this plugin in my company CRM was one of the most challenging and most interesting things I have done since many months.


few words about cron tasks

The most important of my projects is a CRM tool that provides some tools for managing an E-commerce company. It is an application that stores lots of data in its own database, but it also has access to several other databases and connects external systems through APIs. Such a complicated system requires several cron tasks (the number is going to be doubled soon). Some of them have to be run once per day, but others have to be run every few minutes. There are few problems you have to face when using cron tasks in a big system:

  • Running each cron consumes server resources and estimating the best time period between the cron finishes his task and before he's run again is not easy. If the period is too small, lots of resources will be consumed with no sense (everything depends on the cron, of course), but if the crons are run too rare, other employees' work is a lot more dificult, because their data s not up to date.
  • Another important thing is task performance - how fast does it take until the cron finishes his task. When you implement a new task, you measure how long does it take to run the task once. But if your data grows incredibly fast during just few months, will you always remember to check if everything is ok with the application performance?
  • Finally, you need statistics to know what is going on in your system - how often a functionality is used, what is the traffic during specific part of the day or for different days of week, etc.
  • Just one more thing - all this information you would like to know about your own crons should be easily accessible. Until now, I had a basic logfile system which was really far away from meetig my expectations (look above).


so, is this plugin useful?

Extremely! As almost everything in life, the most useful things are usually the easiest. And it's the same right here. But I must admit, that at first sight the plugin's readme seemed complicated - but it's only the first sight (according to COil, the author of the plugin). In fact, my original task was handled by the plugin after 20 minute. COil replied to my mail within few hours and I got all the answers I needed to install his plugin into my apps.


few examples of where sfTaskLoggerPlugin is useful

There are some of my cron tasks:

  • migrating data between databases. Different www applications are run independently, each of them having it's own database. It's pretty clear, that an employee won't check several different admin panels each few minutes, so everything has to be in one place. And here comes migrating orders... A customer visits a webpage and submits his order. A system cron is waiting for this to happen and after at most 5 minutes he's run and copies the order data into the CRM database, where all tools to manage this order are available. But what is the time when most orders are submitted? How many, how often? From which of our applications? And how often such cron should be run? And how long does it take...
  • e-mail sending - similar as above.
  • redundant table columns speeding up other functionalities. In my project I have to generate lots of different XML files for external applications such as price comparators or catalogues. We're using the prestashop to deploy shops. Source prestashop database structure can be highly ineffective in our case, so some product data has to be copied between tables once per day (the ineffective queries are executed only once and the result is stored in the redundant columns which are very easily accessible). And the problem is that it has to be run on all products we have in our database. Running a time-consuming query may be dangerous for performance (it's expected that number of products we have is going to be 4-5 times more that it is now). So I need to watch and check if there's need to divide the process into phases (processing parts of data separately) to prevent a breakdown.
  • Another thing to look closely at is accessing external applications' WebAPI. It happens that the WebAPI may be developer-unfriendly :) by providing methods that are really poor. And by the same time, the external application generates great traffic to your company. Well, such integration will be difficult then. Not only you'll have to watch out for lots and lots of traps, problems (and finally - bugs), but also you need to know when to run the cron task and how often. And this is not only about the comfort of other employees' work, but mainly about data consistency, this time. Programming can be art, definitely :).

main sfTaskLoggerPlugin's features

Well, it's pretty easy - each cron run is stored in the database. You know the starting and the ending time of each task (so you know how long does it take to finish the task, how often such task is run, you can check if such task was already run today - and so on). You can define the count of the processed and unprocessed objects (if any error occurs or if just some amount of data has to be processe later - than you define the id of the last processed object). You define the error code, stating if there were any errors during runtime, you're able to check how many crons are running at the moment (or how many of them has broken and was never finished). Finally, you define your own comments for each cron. I use it to generate HTML code that is easily accessible by office workers (in case they need to find if some data was processed and what was the result).

It's easy to display/generate project cron statistics. I'm gonna use stOfcPlugin, for example, to display how many percent of cron task run migrates any data (and how many just checkes that there is nothing to migrate). There is plenty of examle statistics to generate. I'm gonna provide some real cron examples with potential stats soon...

code example

It took me some time to publish it, but finally, an example symfony task basing on sfTaskLoggerPlugin is described inside this article.

why so small popularity?

To sum up, it's such a shame that there are so many good plugins that are not popular, including sfTaskLoggerPlugin. It's really difficult to break through and make your tool popular in the community. Well, using cron tasks is not the most important symfony feature - but I'm pretty sure that there are more than just 4 users who can make use of logging cron tasks... Or am I wrong? :) Just give this plugin a try!

By the way, we're looking for a Propel developer who is willing to contribute to the plugin. Any good contribution is welcome! ;-)

symfony popup with jQuery thickbox - beginner's tutorial

Scene from "The Jetsons" by Hanna-Barbera (introduced in 1962)

Introducing jQuery thickbox pop-up


In this tutorial I will show you an easy way to create a thickbox using jQuery thickbox lib. At first, make sure you are familiar with the thickbox and take a look at the examples tab. We will create our pop-up feature basing on the iFramed Content section.


Install jQuery thickbox


The first thing you need to do is installing the software, of course. I want to make this tutorial as easy as possible, so just run the following commands from the project root directory:


wget http://jquery.com/demo/thickbox/thickbox-code/thickbox-compressed.js -O web/js/thickbox-compressed.js
wget http://jquery.com/demo/thickbox/thickbox-code/thickbox.css -O web/css/thickbox.css
wget http://jquery.com/src/jquery-latest.pack.js -O web/js/jquery-latest.pack.js
wget http://jquery.com/demo/thickbox/images/loadingAnimation.gif -O web/images/loadingAnimation.gif

After the files are downloaded, you need to make symfony include them into the generated output. One of the possible ways is to edit your application/config/view.yml file:


  stylesheets:    [ ..., thickbox.css ]
  javascripts:    [ ..., jquery-latest.pack.js, thickbox-compressed.js ]

The thickbox is already installed in your symfony project.


Basic usage


To make everything elegant, add 2 routes to your application/config/routing.yml file:


index_popup:
  url:   /popup
  param: { module: main, action: popup }
basic_popup:
  url:   /basic-popup
  param: { module: main, action: basicPopup }

The first route is just a demo action to demonstrate thickbox and you can the index_popup action with the following url:


http://local_sf_test/popup

The second route executes an action that shall be displayed inside the popup. Now we shall create the index_popup action inside the application/modules/main/actions/actions.class.php file:


/**
  * Executes popup action
  *
  * @param sfRequest A request object
  */
  public function executePopup(sfWebRequest )
  {
  }

and a link executing our basic popup - add the following code into your application/modules/main/templates/popupSuccess.php file:


<a class="thickbox" href="<?php echo url_for('@basic_popup') ?>?keepThis=true&TB_iframe=true&height=100&width=400">run basic popup!</a>

It's already working - if you press this link in the development mode, you'll get the symfony error Action "main/basicPopup" does not exist. - but generated in the thickbox. Now we're only left to create this action - create the file application/modules/main/templates/basicPopupSuccess.php file:


<h1>basic popup example</h1>

and the last thing - the basicPopup action inside the application/modules/main/actions/actions.class.php file:


/**
  * Executes basic popup action
  *
  * @param sfRequest A request object
  */
  public function executeBasicPopup(sfWebRequest )
  {
  }

The logic is very simple - only two things to be done:

  • The string ?keepThis=true&TB_iframe=true&height=100&width=400 is added to the end of the link's href attribute to pass some extra parameters to the thickbox. Those parameters can be easily modified, according to thickbox documentation.
  • The link has to have the CSS thickbox class
The screenshot below presents the output:




As we can see, the web debug toolbar is not fitting well here, therefore we may disable it by adding the following code to the executeBasicPopup action:


sfConfig::set('sf_web_debug', false);

This would display the popup without web debug no matter if the production or development controller is running:




Advanced usage

Above example could only display some static content. But it's not all that symfony thickbox integration can do - for example, you can embed symfony forms inside a popup. Create new action inside the application/modules/main/actions/actions.class.php file:


/**
  * Executes advanced popup action
  *
  * @param sfRequest A request object
  */
  public function executeAdvancedPopup(sfWebRequest $request)
  {
    sfConfig::set('sf_web_debug', false); // disable sf_web_debug
    $this->form = new ExampleForm(); // create the form object
 
    if ($request->isMethod('post'))
    {
      $this->form->bind($request->getParameter('example_form'));
      if ($this->form->isValid())
      {
        // do some business logic
        $this->getUser()->setFlash('notify', 'this message will be displayed inside the popup');
        return $this->renderPartial('main/message');
      }
    }
    return $this->renderPartial('example_partial');
  }

and a new route inside application/config/routing.yml file, corresponding to the new action:


advanced_popup:
  url:   /advanced-popup
  param: { module: main, action: advancedPopup }

update the popupSuccess.php file:


<a class="thickbox" href="<?php echo url_for('@basic_popup') ?>?keepThis=true&TB_iframe=true&height=100&width=400">run basic popup!</a>
<a class="thickbox" href="<?php echo url_for('@advanced_popup') ?>?keepThis=true&TB_iframe=true&height=100&width=400">run advanced popup!</a>

Now it's up to you to create a form class (the easiest way is to use a model form generated class) and display all its fields inside the _example_partial.php file:


<style>
#form {background: #d3e5f4; padding:5px 15px; color:#1f3037;}
/* some other CSS to be used by the popup form */
</style>

<div id="form">
  <form action="" method="post">
    <?php echo $form->renderHiddenFields() ?>
    <?php echo $form['example_field']->renderError() ?>
    <label class="label" for="example_form_example_field">example label</label>
    <?php echo $form['example_field']->render() ?>
    <input type="submit" value="submit" />
  </form>
</div>

The last thing to be created is the file to be used as the message partial, _message.php:


<span>example message</span>


Live app example


After clicking this button:




the following popup is displayed:




the form validation errors are displayed:




and finally the submission message is displayed:


Embedding videos in symfony projects using sfVideoPlugin

What does the plugin do?


sfVideoPlugin is a plugin created by a small international symfony developers team. It's destined to give an easy interface for online video embedding. The plugin uses flowplayer software and uses files with the flv extension.

It's so easy


sfVideoPlugin comes provided with few ways of embedding a flash video player inside you project.

including a partial

Once the plugin:publish-assets task has been run, flv files can be accessed from the web. Just include the video partial from the sfVideo module (enable it first, of course) passing one obligatory parameter - file (it's taken from the default flv directory):

include_partial('sfVideo/video', array('file' => '01.flv'));
Take a look at the plugin demo site.


video widget

Use the sfVideoWidget class inside your forms to display the player inside a form (you may use predefined sfVideoForm class which has only one widget). An example study case using video widget is uploading video files in the backend - user should have the opportunity to display the video file before publishing it online.


multiple player embedding

A simple parameter has been added to the partial mentioned above to enable displaying more than one video widget on one page. If the player parameter is not set when including a partial, the default value is "player" - you need to override the default value to display more than one player. Put the following code into an action:

$this->players = array(
  array('file' => '01.flv', 'player' => 'player01'),
  array('file' => '02.flv', 'player' => 'player02'),
  array('file' => '03.flv', 'player' => 'player03'),
);
and include the partial for each of defined players:
<?php foreach($players as $p): ?>
<?php include_partial('sfVideo/video', $p) ?>
<?php endforeach; ?>
Take a look at a live demo.


configuration

At now, there are four attributes defined in the app.yml file that are common for all players in the plugin. These are:

  • width
  • height
  • autoplay
  • autobuffering
The first two attributes take an integer value and the rest takes true/false values. Access them using:
sfConfig::get('app_video_autoplay')


contribution

Feel free to comment on the post and to contribute to develop the plugin.

Mail testing configuration in symfony

By default, the symfony mail delivery strategy is set to none. That means, that calling send method upon sfMailer object will perform no action.


Configuring mail delivery_strategy


All possible delivery strategies are described in the symfony docs. A good solution is to edit factories.yml in all application config directories and set production environment to realtime and development environment to single_address. The developer enters an E-mail address he's got access to, puts it in the delivery_address option and all mails sent from the project will be delivered to that address (so there's no worry that a customer/user will receive test E-mail). Put the following lines into factories.yml file to define mail configuration:

prod:
  mailer:
    param:
      delivery_strategy: realtime
dev:
  mailer:
    param:
      delivery_strategy: single_address
      delivery_address: your@address.com
Once we've set those values (even in the very beginning of a project development), we don't have to carry about mail configuration, hence symfony will carry out all mails to the correct addresses when working in the production environment.

Default form values for new objects

what for?


Sometimes you may need to get some random values when creating a new object from the symfony generated form. This can happen in lots of situations, some of them are listed below:

  • populating article comments in a community website - a community website looks a lot better when each article has at least several comments. So when an article is created, few comments need to be created right afterwards. Choosing random names is quite easy, of course, but what if your system allows only logged in users to leave comments (which means, comment table row does not have author string value, but author_id integer foreign key)? In such case you have to look up a random user id in the database, which is pretty impossible. Default form values to the rescue!
  • E-shop product attributes - each product has some attributes that measure its quality, just like reliability, aesthetics, performance and so on. As we know, many information in advertisement is pure bull*hit nowadays, e.g. values of product attributes are probably fabricated. Suppose there are many attributes for each product (available inside the form or inside an embedded form, it doesn't matter). Defining each attribute costs time and an E-commerce shop employee will work slower if he needs to input each value separately. Again, default form values comes with assistance!

solution

Put the following code into the setup or configure method of a form class:

if ($this->isNew())
{
  $this->setDefault('name', Tools::getRandomName());
}

Above feature is really easy to implement and you'll find it really useful when generating some partially random data. Of course, you may also set random values after the form is submitted, when a form field is simply disabled - but then you don't have a posibility to change it during object creation.

Advanced SQL expressions in Doctrine

Scene from "Irma la Douce" by Billy Wilder (1963)

missing element in symfony documentation

Symfony and Doctrine book (chapter 6) describes DQL API, but one very important SQL feature is missing: advanced expressions. Particularly, you may often need to use advanced logical expressions in WHERE clause.


example

For example, we run a cron task searching for all active posts which does not meet all SEO requirements - then a warning mail is sent to a particular employee of a company to do something with it. So the query needs to look for Post objects:

Doctrine_Query::create()->from('Post p')
which are active:
->where('p.active = 1')
and have invalid SEO data at the same time, let's assume that SEO is invalid when at least one of all SEO data columns is empty (title, keywords and description):
->orWhere('LENGTH(p.meta_description) = 0')
->orWhere('LENGTH(p.meta_keywords) = 0')
->orWhere('LENGTH(p.meta_title) = 0') 


Now take a look at the above code - is that correct? Of course not! We want to generate the following query:
SELECT *
FROM Post p
WHERE p.active = 1
AND (
  LENGTH(p.meta_description) = 0 OR 
  LENGTH(p.meta_keywords) = 0 OR 
  LENGTH(p.meta_title) = 0)
Three SEO alternatives need to be enclosed in parenthesis. But Doctrine Query API does not provide specific methods doing that. Fortunately, we may use standard query methods (where, orWhere, andWhere, etc.). Unfortunately, we can use them only on the top-level of the query (meaning that all we write stays outside parenthesis). So the top-level of query is created by two arguments linked with AND logical operator - each of those arguments use one DQL API where method. But the second argument (invalid SEO) is internally ivided into an alternative of three subarguments, using OR logical operator. The final query looks like:
$objects = Doctrine_Query::create()
  ->from('Post p')
  ->where('p.active = 1')
  ->andWhere(
  'LENGTH(p.link_rewrite) = 0 OR '.
  'LENGTH(p.meta_description) = 0 OR '.
  'LENGTH(p.meta_keywords) = 0 OR ')
  ->execute();

Anyway, it works. Maybe the Doctrine API will be more friendly in Symfony 2.