If you want to make the documentation for your PHP software good looking (i.e. make it look like the online Symfony documentation) and save it to a PDF file, do the following:
- Save the CSS code below to a file on your computer, name it (for example) symfony-markdown.css.
#content1 { font-size: 13px; margin-right: 0; } .documentation pre { font-size: 100%; } #legacy_bar, #content1 > form, #content1 > hr, #content1 > h1, #content1 > .markdown_help, #topbar, #bar, #filters, #footer, .sensiolabs { display: none; } - Write your documentation in the Markdown format.
- While you are writing, preview your documentation using the Markdown dingus, Symfony style.
- When you are done, create a preview of your documentation for the last time, using the above-mentioned “dingus”.
- Add a custom stylesheet to the page (for example, if you are using Firefox and have installed the Web Developer add-on, by clicking on CSS -> Add User Style Sheet…), select the file you created at the first step. The page should now be “clean”, no header, no footer, no sidebar, etc.
- Check the page settings in your browser and make sure that no headers and footers will be printed.
- Print the page to a PDF printer (for example Bullzip PDF printer)
A good looking PDF document is the result:
Filtering your task list in Eclipse
Granted, it’s not about Symfony per sé, but still somewhat related.
As you know, the Eclipse IDE has a ‘task list’. It is sensitive to comments like @todo, TODO and FIXME etcetera and shows them in a pane. But in the default view there are so many it’s easy to lose track of your todo’s and since the list is so long, you are not triggered to clear it completely.

So I write my TODO’s with my initials in parenthesis:
// TODO (JM): finish this code
Then I create a filter in the task list.
- From the task list’s View menu, choose ‘Configure contents’.
- Create a new configuration
- Give it a handy name
- In ‘Description’, select ‘contains (JM)’ or whatever your text is.
From now on, I can select 'Show' from the task list's View menu, and choose my filter; I will only see my own todo's.

Sometimes you need to add to- or change your database schema and face the tedious error prone task of updating a live or staging server with the changes. If all you did was add tables, add fields or change a model relation or column configuration, you can use the method below to safely backup your data, rebuild your model, forms, filters and database, and then restore the backup again. Things should work just fine after that (note the “should” in that sentence…)
First we make a backup of the existing data in the database:
mysqldump -u<dbuser>-p --no-create-info --complete-insert <dbname> > dump.sql
Note: change the<dbuser> and <dbname> with your database username and database name. You will be prompted for the password.
This command will dump your database to a file called dump.sql. Since we only want the data and not the create statements to build all tables. (this is what symfony will do for us with the build –all command below) we add the option –no-create-info.
We want complete inserts, so the data AND the corresponding column names, so MySQLwill know which value to insert where. If you don’t add the –complete-insert option, and you added a column to your table, MySQL will insert the right value in the wrong column,since it will simply use the order of the columns you had before you made the change, and you will get the wrong offset when re-inserting your data.
./symfony doctrine:build --all --no-confirmation
This command will rebuild all your model, form and filter classes, build the SQL statements for eveything and rebuild you database. After running this command you will have a clean empty database that corresponds to your schema.yml file
mysql -u<dbuser> -p <dbname> < dump.sql
If however, you deleted tables and/or columns in your update, you have to first:
- Backup all your data (better safe than sorry…)
- Delete the tables and columns you don’t need anymore from your database
- And THEN use the method above
This ensures that if you will restore the backup it will find all the tables and columns that are still in your schema and database.
1. In apps/frontend/config/factories.yml, change
prod:
logger:
class: sfNoLogger
param:
level: err
loggers: ~
to
prod:
logger:
class: sfAggregateLogger
param:
level: debug
2. In apps/frontend/config/settings.yml, change
prod:
.settings:
no_script_name: on
logging_enabled: off
error_reporting: <?php echo (E_ALL | E_STRICT)."\n" ?>
to
prod:
.settings:
no_script_name: on
logging_enabled: on
error_reporting: <?php echo (E_ALL | E_STRICT)."\n" ?>
3. In index.php, change
$configuration = ProjectConfiguration::getApplicationConfiguration('frontend', 'prod', false);
to
$configuration = ProjectConfiguration::getApplicationConfiguration('frontend', 'prod', true);
4. symfony cc
Stop MIMEing already!
When handling file upload fields in forms or manipulating media files you probably want some kind of file type detection.
This is called mime type guessing and there are a couple of ways to do this, all of which are used in Symfony (for example in sfValidatorFile and sfImageTransformPlugin):
Match the file extension against a known list of extensions
This is the lamest and most unreliable of the three. It’s like pulling over a drunken driver and make him recite the alphabet backwards to see if he’s too intoxicated; it works in some cases, but it’s just more reliable to analyze their breath or blood: “Look! It’s a Word document dangling down the Electronic Highway. Let’s pull him over and make him show his extension, if you know what I mean.”
Grab the magic bytes from the file header and compare them with known signatures
This is the approach for the more sensible developer. Most binary files start with a unique sequence of bytes, which can be used to identify the file format. For example: PNGs start with 89 50 4E 47 0D 0A 1A 0A (50 4E 47 = “PNG”).
Use *nix magic database
On Unix platforms there is a file command for detecting file formats. It uses the foregoing method and matches the bytes against a database of magic numbers. This database is located in a file called magic. A term reminiscence of a bygone era in which we developers were still whizzkids and Symfony 2 one of Beethoven’s early works.
There are a two PHP modules that utilize the magic file to determine content types:
A third method in PHP is the deprecated mime_content_type() function.
The aforementioned sfImageTransformPlugin also has a fallback to getimagesize; mime type detection from PHP’s GD library that only supports a limited set of image types.
The type of detection in this image transform plugin is configurable:
sfImageTransformPlugin:
default_adapter: GD
mime_type:
auto_detect: true
library: gd_mime_type # gd_mime_type (GD), Fileinfo (PECL), MIME_Type (PEAR)
Make sure you have the necessary libraries installed when chosing for Fileinfo or MIME_Type!
When using Fileinfo (finfo) make sure PHP can find the magic database. On our servers it is located in /usr/share/file/magic, but the default for finfo_open is /usr/share/misc/magic. Use one of the following methods to define the path to your magic file:
- Pass is as the second parameter to
finfo_open
NB: Symfony’s design of sfValidatorFile makes it impossible to do this. They forgot to make it accessible through the validator options, I guess. There is however the option to add custom mime type guessing callbacks.
The sfImage class of sfImageTransformPlugin has the same limitation. Workaround this by:- Overwriting sfImage or sfValidatorFile in your app lib (and modifying the necessary code), so your file takes precedence over the original
- Subclassing sfImage or sfValidatorFile
- Set the
MAGICenvironment variable: - Create a symlink or duplicate the magic database to the default location
Note that your *nix distribution might not have the most recent version of file (the current version is 5.04), which could lead to unexpected results.
Finally there are some issues with finfo_open. Check http://php.net/manual/en/function.finfo-open.php for more information.
I hope this sheds some light on mime type detection with PHP, but it is certainly not the complete story, so feel free to share your wisdom in the comments.
getInstance method for Doctrine tables
Autogenerated table classes already contain a static public getInstance method, so as an alternative to
$table = Doctrine_Core::getTable('Page');
you may use
$table = PageTable::getInstance();
If you wish to have the benefits of code completion, you have to tell your IDE somehow that the returned object is an instance of PageTable.
In the first case, you may accomplish this by adding the following comment to your code:
/* @var $table PageTable */
In the second case, you only have to add a comment block right before the method getInstance is defined:
class PageTable extends Doctrine_Table
{
/**
* @return PageTable
*/
public static function getInstance()
{
return Doctrine_Core::getTable('Page');
}
//...
}
From now on your IDE knows what kind of object the getInstance method returns and will give you full autocompletion while writing your code.
I needed to create an extra pulldown for the in a CMS. This needs to add something to the search query.
The case is:
- I have a table called ‘work’.
- It has a field called ‘adlib_id’.
- The value of the adlib_id field is ‘collection_something’ or ‘library_something’, where ‘something’ is a number.
- I want a pulldown that, if set, allows me to filter between ‘collection_something’ and ‘library_something’ records.
My solution:
Create the filter widget in the WorkFormFilter.class.php:
public function configure()
{
...
$worktypesArray = array('' => '',
'collection' => 'Collection',
'library' => 'Library');
$this->widgetSchema['work_type'] = new sfWidgetFormChoice(array('choices' => $worktypesArray));
$this->validatorSchema['work_type'] = new sfValidatorChoice(array('required' => false, 'choices' => array_keys($worktypesArray)));
$this->getWidgetSchema()->setLabels(array(
'work_type' => 'Type of work',
));
}
Run the filter, by adding this to the form class:
public function addWorkTypeColumnQuery(Doctrine_Query $query, $field, $values)
{
if (!is_array($values))
{
$values = array($values);
}
if (!count($values))
{
return;
}
if (strlen($values[0]))
{
$query->andWhere('adlib_id like ?', $values[0] . '_%');
}
}
}
Make sure the filter get shown by adding it in the useFields() method of the configure() step:
$this->useFields(array('work_type',
'title',
'discipline_id',
'no_theme',
'no_assets'));
Of course you can use different widget types too…
Publish your own Symfony plugin
This post is a detailed manual for publishing your own Symfony plugin(s) to the Symfony plugins directory. If you have any additions, please leave a comment.
1. Create an account at http://www.symfony-project.org/user/new
2. Log in
3. Write a readme file, containing installation and usage instructions. Use the Markdown format (syntax). You can preview the readme file at http://www.symfony-project.org/plugins/markdown_dingus Save the file in the root directory of your plugin as README
4. Add a file LICENSE file to the root directory of your plugin, containing for example the MIT license for open source software (source: Wikipedia):
Copyright (c) [year] [copyright owners]
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the “Software”), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
5. Add a package.xml file to the directory of your plugin. The code below may be used as a starting point. A few remarks will follow below.
<?xml version="1.0" encoding="UTF-8"?> <package packagerversion="1.4.6" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0 http://pear.php.net/dtd/tasks-1.0.xsd http://pear.php.net/dtd/package-2.0 http://pear.php.net/dtd/package-2.0.xsd"> <name>drTwitterFeedPlugin</name> <channel>pear.symfony-project.com</channel> <summary>Short description of your plugin</summary> <description>A more elaborate description of your plugin</description> <lead> <name>Your name</name> <user>Your username</user> <email>your@emailaddress</email> <active>yes</active> </lead> <date>2010-09-30</date> <time>15:00:00</time> <version> <release>1.0.0</release> <api>1.0.0</api> </version> <stability> <release>stable</release> <api>stable</api> </stability> <license uri="http://www.symfony-project.org/license">MIT license</license> <notes>-</notes> <contents> <dir name="/"> <file role="data" name="README" /> <file role="data" name="LICENSE" /> <dir name="lib"> <dir name="model"> <dir name="doctrine"> <file role="data" name="User.class.php" /> <file role="data" name="UserTable.class.php" /> </dir> </dir> ... </contents> <dependencies> <required> <php> <min>5.1.0</min> </php> <pearinstaller> <min>1.4.1</min> </pearinstaller> <package> <name>symfony</name> <channel>pear.symfony-project.com</channel> <min>1.4.0</min> <max>1.5.0</max> <exclude>1.5.0</exclude> </package> </required> </dependencies> <phprelease /> <changelog> <release> <version><release>1.0.0</release> <api>1.0.0</api> </version> <stability> <release>stable</release> <api>stable</api> </stability> <date>2010-09-30</date> <license uri="http://www.symfony-project.org/license">MIT license</license> <notes> First public release </notes> </release> </changelog> </package>
If the value of the <stability> tag is according to yourself “stable”, your version numbers in the <version> and <api> tags should be greater than 1. Version numbers should always consist of three digits divided by a dot.
Required packages should be mentioned too, for example if you need the sfWebBrowserPlugin, add the following lines inside the <required> tag.
<package> <name>sfWebBrowserPlugin</name> <channel>pear.symfony-project.com</channel> <min>1.0.0</min> <max>2.0.0</max> <exclude>2.0.0</exclude> </package>
The children of the <contents> tag should reflect the entire file structure of your plugin. There are tools available to auto-generate these the <dir> and <file> tags (see http://www.electricmonk.nl/log/2009/04/12/easy-pear-package-creation/).
6. Once you are done editing your plugin codebase, the package.xml file, and the README file, go to the root directory of your plugin and run the command
pear package
A zipped tar file will be created in the same directory, containing your plugin as a Pear package. You may upload this file to the symfony-project.org website after the next step.
7. Go to http://www.symfony-project.org/plugins/new Fill in the name of your plugin. You may let your plugin be hosted on the symfony-project.com website. In that case, fill in the repository URL http://svn.symfony-project.com/plugins/[theNameOfYourPlugin]. A repository will be created automatically within an hour. You may of course use your own Subversion repository if you have one that is accessible for everyone. The rest of the “New plugin” form is self explanatory I guess.
Submit the form and you’ll find at the bottom of the page an upload field. You can upload your Pear package there.
Once the Pear package is uploaded, every change to your plugin requires you to upload a new package with a higher version number (for example 1.0.1, 1.0.2, etc.).
The following steps only apply when you wish your plugin to be hosted on the symfony-project.com website. If this is the case, you should have filled in the repository URL in the “New plugin” form. After an hour at the most, the repository is available for you.
8. In your original project, create a directory tags, as a sibling of the trunk, and commit this directory. Create a tag for the current version of your plugin:
svn copy file:///var/repos/[plugin_name]/trunk file:///var/repos/[plugin_name]/tags/release_1_0_0
(For every future release you should repeat these steps: work in the trunk – or temporarily in a branch – and whenever you release a new version, make a tag for it.)
9. You can now create a checkout of your plugin’s repository, hosted on symfony-project.com. Use the URL http://svn.symfony-project.com/plugins/[theNameOfYourPlugin].
10. Export the directory “tags” (or the specific tags you wish to make publicly available) to the checkout of your plugin’s repository.
11. Commit the exported files. You will be asked for your credentials. These are the same as the credentials for the symfony-project.org/plugins website. The plugin files you’ve just committed are immediately available at http://svn.symfony-project.com/plugins/[theNameOfYourPlugin]
Throwing and catching exceptions
As we all know, PHP and Symfony allow you to throw exceptions from your code. The code that calls the code throwing the exception can then handle it using a try-catch block. This is a very powerful method of programming, but to use it to its full potential requires some thought. I personally had some previous experience with exceptions from Java. Java exceptions are a lot more developed than PHP/Symfony exceptions out of the box.
For one thing, in Java you must catch all exceptions, or your code won’t even compile. So there will never be exceptions that are unhandled. PHP and Symfony do not enforce exception handling as such. (Well, the framework will just crash with a 500 server error when it encounters an unhandled exception.) Another big difference is that all the standard Java classes (and there are a lot of these) have built-in exceptions that are very specific. For instance, the File object can, amongst others, throw an IOException or a NullPointerException.
Now, we know two things:
- Java classes throw specific exceptions
- All exceptions must be caught
In Java, you are strongly advised to be very specific when throwing exceptions, and when checking your exceptions. What does that mean?
Say you are trying to open a file via a URL, and it fails.You could throw and catch an IOException, but that wouldn’t tell you too much. So it might be better to use a FileNotFoundException in case the file is not found, and an UnknownHostException if the url is not valid.
In the calling code, you can then catch these separate exceptions, and decide what you want to do with them. In this example, the FileNotFoundException may indicate a user entered the wrong data, whereas the UnknownHostException might indicate some configuration error. You will probably want to handle these differently.
Since you must catch all possible exceptions in Java, you could just catch the Exception object in your main method and not catch anything lower down. But this is frowned upon because you are killing all the exception handling logic (which is made of kittens).
So, how does this translate to Symfony?
You might write some code, and if something goes wrong throw a new sfException and put some info in the message or code. In the calling code, you catch sfException, examine the message/code and decide what to do. I believe this is not very good practice. For one thing, it means you catch all possible exceptions in a particular place. If something went wrong that you would want to handle higher up the call stack, you can’t, unless you throw another exception.
So in our example here, you might throw sfException, with the message being either ‘file not found’ or ‘unknown host’ and some arbitrary codes. You have look at the exception, and if you want to handle the error in code higher up the call stack, you have to throw a new exception. Even if you want to handle both errors in one caller, you’d still need to look at the code or message to decide what to show the user. You can’t change the code or message because your exception handling will break.
There is another issue in catching sfException: You don’t know what you will be catching. Say that in your code, you use an object written by a colleague. This code may well throw its own exceptions. If you don’t catch these in your called code, they will get propagated up the call stack and your calling code will catch it, probably in a place where you don’t want to handle it. If in your code you only catch the exceptions you are prepared to handle, unchecked exceptions will get propagated up the stack until they are caught or crash the app. But it’s quite feasible you want to catch and handle a configuration error higher up the call stack than a ‘file not found’.
If you throw specific exceptions, you can catch what you want, where you want. Any exceptions you want to check higher in the call stack, you just don’t catch lower down. Any exceptions you are not prepared to handle will be propagated up. Any logic on what to do with exceptions no longer relies on the message, but on the exception type.
Of course this means that you have to define all the exceptions for your objects by extending sfException. You could just autoload a single PHP file with all your exceptions as empty class definitions. It also means you have to give more thought to what could actually go wrong, and how you’d want to handle that, and that is never a bad thing.
In short: Throw specific exceptions, catch specific exceptions.
Doctrine and Oracle on Windows
For one of our clients we are developing a touchscreen application with a symfony backend (Flash + AMFPHP amongst others).
One of the technical requirements is that it has to work with an Oracle database. Since we don’t have any experience whatsoever with Oracle, this was a tough cookie to crumble. This post is meant to be a quick start for other developers who have to use symfony/Oracle in a Windows environment.
Below are the most important things i learned and figured out on my “journey” towards a working Windows/Oracle/symfony setup.
1. Install the free Oracle Express Edition database
I downloaded the OracleXEUniv.exe Univeral edition from this page.
After installation you can login at this (local) url: http://127.0.0.1:8080/apex
Or use a pre-installed database, and get the right connection parameters from the DBA.
2. Create a user in the database with the right permissions
I Just gave the new user all permissions so the symfony doctrine:build -all command will be able to create triggers, sequences etc. Of course you have to ask (or beg) your Oracle DBA to give you these permissions so you can still use your symfony cli commands
3. Download the Oracle Instant Client from the Oracle website
You can find all sorts of downloads here (you need to create an account to download) Important thing is that you have to add the installation path to your windows PATH environment variable.
This is necessary so the Oracle PHP extension can locate the right libraries to work with (which are included in the Instant Client software).
4. Use the free SQL Developer program or a similar db management program
I used SQL Developer to manage / inspect the Oracle database. There will be other programs i don’t (yet) know about. This worked for me.
5. Configure the database connection
I had some trouble connecting to the database, since with Oracle you can connect in a few ways.
There is a file called tnsnames.ora where you can define all connection settings under a short SID (service id). You can then connect to the database, referring to this shortcut SID. The client software (i suppose) reads the tnsnames.ora file an does the rest. Because the express edition always has the same SID (which is XE) it will be simple to connect.
The location of this file is (in my case):
<Oracle-XE-install-dir>\xe\app\oracle\product\10.2.0\server\NETWORK\ADMIN\tnsnames.ora
There i found the “XE” SID for the Oracle Express Edition DB i just installed:
XE =
(DESCRIPTION =
(ADDRESS = (PROTOCOL = TCP)(HOST = localhost)(PORT = 1521))
(CONNECT_DATA =
(SERVER = DEDICATED)
(SERVICE_NAME = XE)
)
)
You probably will get this SID from your DBA.
Don’t use the (experimental) Oracle PDO driver (crashes, crashes…)
Of course it states on the PHP Oracle PDO manual page that it’s highly experimental. But somehow (stubbornness?) i kept trying this, and believing that i did something wrong myself, or that my configuration was faulty etc. etc. Until i stumbled upon this post . Many thanks for that. This put me on the right track finally.
So, use the latest oci8 driver from the windows PECL build page at http://downloads.php.net/pierre
When creating the connection, Doctrine will first check if the PDO extension is enabled. If it is enabled (most servers will have this enabled is suppose?) it will check if a PDO driver is available. So if you use oci: in your DSN it will use the experimental PDO OCI extension (don’t, don’t!) If PDO is not loaded or if no matching PDO driver is found, it will try to create an instance of Doctrine_Adapter_<DB_TYPE>. In our case this is just fine, because we want a Doctrine_Adapter_Oracle to connect to our database. SO that’s why you have to use oracle: als the dbtype in your DNS.
For more information and details, check the post i mentined above
Just to be clear, here’s the combination of enabled / disabled extensions in my php.ini:
... extension=php_mysql.dll extension=php_oci8.dll extension=php_pdo.dll extension=php_pdo_mysql.dll ;extension=php_pdo_oci.dll ;extension=php_pdo_oci8.dll ...
So use the oracle: db type in your databases.yml. This is the only db type setting that will use the right oci extension and not the PDO one. Here’s the corresponding databases.yml setup:
doctrine:
class: sfDoctrineDatabase
param:
dsn: oracle:dbname=//127.0.0.1/XE;charset=AL32UTF8
username: yourdbuser
password: yourdbpass
attributes:
quote_identifier: true
Other things
Date formats / decimals.
I had some trouble with decimals, because the default setting , (comma) and not a . (dot) as the decimal separator.
Maybe this can be done in an easier way, but i created a trigger to achieve this. This trigger will be executed after every logon. I also added the preferred way for dates, but i believe symfony already tries to set this before every query.
CREATE OR REPLACE TRIGGER "DRIEBIT_CUSTOM_FORMATS" AFTER LOGON ON DATABASE BEGIN execute immediate 'alter session set nls_numeric_characters = ''. '' ' ; execute immediate 'alter session set nls_date_format = ''YYYY-MM-DD HH24:MI:SS'' ' ; END; / ALTER TRIGGER "DRIEBIT_CUSTOM_FORMATS" ENABLE;
N.B. In the live environment this wasn’t necessary because it had already been done, but out of the box i had to do this to be able to succesfully save decimal values.
Column types
Because our project didn’t need to store large amounts of textual data, i could get away with using the standard string column type (which gets translated to the VARCHAR2 type in Oracle).
But there are other column types like CLOB (Character Large Object) which can store up to 8 Tb (or even more) I don’t know how well this works with Doctrine though.
Purge Recyclebin…
In the SQL developer tool i noticed (after running the doctrine:build commands a few times) a lot of “old” database objects / triggers / sequences.
After some reseacrh i figured out that these could be purged with the query: PURGE RECYCLEBIN;
I don’t know if this is Oracle specific behavior or just a feature of the SQL Developer tool.
I will try to update this post with new insights, corrections etc. as my insight in the combination Symfony/Oracle/Windows evolves.
Please let me know if you have any additions, clarifications, comments or corrections to this post.



