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

doctrine act as signable plugin - new releases

After few months, new versions 1.2.2 and 1.2.3 of sfDoctrineActAsSignablePlugin have been released. In short, the plugin provides a Signable behavior which automatically stores information on who has created or updated a given object.


what's new

A fellow symfony developer, Daniel Möllenbeck, suggested that there are some options missing in the behavior configuration. Until version 1.2.1, the onDelete option was hardcoded as CASCADE, which - as Daniel emphasised - may cause problems when a given user is supposed to be deleted (physically from the database, not softDeleted).


ALTER TABLE customer
ADD CONSTRAINT customer_created_by_sf_guard_user_id
FOREIGN KEY (created_by)
REFERENCES sf_guard_user(id)
ON DELETE CASCADE;

ALTER TABLE customer
ADD CONSTRAINT customer_updated_by_sf_guard_user_id
FOREIGN KEY (updated_by)
REFERENCES sf_guard_user(id)
ON DELETE CASCADE;

Let's imagine user enters customers over and over, then leaves the company. Now the admin deletes the user - probably the hard way (directly on the database, maybe the user is auto-created from somewhere...) --> The constraint will trigger the deletion of all customers created by that user. I have some doubt anybody would be happy about that.

Having the constraint like that does the trick:

a) allow NULL values in created_by/updated_by columns:

ALTER TABLE customer
ADD CONSTRAINT customer_created_by_sf_guard_user_id
FOREIGN KEY (created_by)
REFERENCES sf_guard_user(id)
ON DELETE SET NULL;

ALTER TABLE customer
ADD CONSTRAINT customer_updated_by_sf_guard_user_id
FOREIGN KEY (updated_by)
REFERENCES sf_guard_user(id)
ON DELETE SET NULL;

b) forbid the deletion of the user:

ALTER TABLE customer
ADD CONSTRAINT customer_created_by_sf_guard_user_id
FOREIGN KEY (created_by)
REFERENCES sf_guard_user(id)
ON DELETE RESTRICT;

ALTER TABLE customer
ADD CONSTRAINT customer_updated_by_sf_guard_user_id
FOREIGN KEY (updated_by)
REFERENCES sf_guard_user(id)
ON DELETE RESTRICT;

c) do nothing

moreover


Another fellow symfony developer, Christoph Berg, helped me to trace the bug with fixtures on created_by/updated_by values. Now, the bug is fixed and all fixture data works perfectly.


the community


Taking the opportunity, I'd like to thank Daniel, Christoph and all other symfony developers who share their opinions on my work - they help to find bugs and suggest some good ideas on how to improve the code. Thanks to you, the plugin gets better and better all the time. Thanks, guys!


Please, feel free to share your opinion and comment on the plugin!

symfony custom configuration files

dynamic and cross-application configuration

If your project gets very big and it has several applications, you may want to create cross-application configuration - store it in one place and let all applications use it (no matter how big it is). Or simply if your configuration is becoming really big and you want to arrange it somehow, custom config files is exactly what you are looking for!


how to do that

That's only three easy steps. First, let's create a custom configuration YAML file (config/something.yml, in the project main directory, not the app config directory) and put some data there:

prod:
  test:  ok

dev:
  test: ko

all:
  foo:  bar
  john: doe


The second step is to create/update a config handler YAML file (config/config_handlers.yml) with following example content:

config/something.yml:
  class:    sfDefineEnvironmentConfigHandler
  param:
    prefix: something_


The last step is to register the configuration file in all applications you want (after this step, the configuration will be available for an application). Use the application configuration class (e.g. apps/frontend/config/frontendConfiguration.class.php):

public function configure()
{
  ....
  require_once($this->getConfigCache()->checkConfig('config/something.yml'));
}
Now all config options defined in the new file are available in the application with the _something prefix. The best way to check if the new file is available for the application is to run it in the dev mode in the browser and check the web debug toolbar, config icon, settings section. All configuration available for the application is displayed there. If you followed all three steps above, new file configuration should be available.


sfAdminDashPlugin trick

This plugin provides an easy-to use menu divided into sections, which is configurable in YAML files. If you want to have different menus for different applications, you just have to put all configuration in the application config (by default, it's stored in the plugin config). One way is to put all menu config in the app.yml file, but, as mentioned before, it can make the app.yml file grow to an enormous size, so a better solution is to create a menu.yml file, which is loaded by the config handler. Everything is done the same way as before:

  • create apps/APP/config/menu.yml file and define sfAdminDash menu there
  • create or update (if exists) the config handler file (apps/APP/config/config_handlers.yml)
  • finally, update the APP configuration class (apps/APP/config/APPConfiguration.class.php) by registering new config file


I hope some of you will find this flexible configuration useful :).

Doctrine Event Listeners vs symfony fixtures

introduction


This is just an easy tip on how to use symfony fixtures along with doctrine event listeners in a symfony project.


event listeners


Doctrine Event Listeners are the methods you define to trigger an activity always after/before a Doctrine Record is created, updated, deleted, etc. These can be: postInsert, preInsert, postDelete, preDelete and so on. They can be very useful to keep the code clean and logical. A good example of usage is uploading an image and creating many different files with different sizes, representing the same image (custom thumbnails). This could be done with overriding ImageForm upload mechanisms, but postInsert seems more clean and easier to maintain if uploading is done using more than one symfony form class.


fixtures


Fixtures are a built-in symfony feature which enables you to easily populate the database with some data. It is usually test data, but for some projects, some initial data can be held in fixtures (e.g. initial configuration values). They should be used in all projects, since it's a lot easier to spot any bug using data that pretends to be real.


the problem


The problem occurs when you combine those two features. Imagine you've got two models in your projects, A and B, related 1:1 (e.g. sf_guard_user with sf_guard_user_profile from sfDoctrineGuardPlugin). For each A record there has to exist the B record. This means, when the mother record is created (suppose A is the master model), the B record needs to be immediately created and related to A. A Doctrine Event Listener is what should be used - a A::postInsert() that creates the B record.


So far, so good - but we still want to use symfony fixtures. Let's say, we want to define 10 sample A records along 10 sample B records. So we write a fixture file with 10 A and 10 B fixtures. Next, we fire the

./symfony doctrine:build --all --and-load
command and what do we find? B records are doubled! And this can be quite difficult to spot!


Each time when a A fixture is saved, the postInsert is automatically executed, therefore new B record is created. So instead of creating records in the following order: A,A,A,...(10),B,B,B,...(10), we get A,B,A,B,A,B,...(10),B,B,B,...(10), having 10 A records and 20 B records. And this is not what we wanted. Fortunately, this can be quite easily fixed.


the solution


Just add one line to the Doctrine Event Listener:

public function postInsert($event)
{
  if ('cli' != php_sapi_name())
  {
    // some code here
  }
}
We're basing on the fact, that fixtures are loaded from a task, which uses command line interface (cli). Now, no fixtures use Doctrine Event Listeners.