Nerds who love the symfony-project
8 Oct
I’ve been making some changes to an app that was using the Google Charts API to draw a couple of line and pie charts in a ‘reporting’ section. Sherif suggested while I was at it, I may as well look for some sexier charts, and a few Googles later came up with pChart. A quick look at their site will tell you it definitely fits the ’sexy’ criteria, and has some very cool features like caching (but who cares about, that main thing is it’s sexy
).
There are two ways to get pChart going with Symfony. Probably the easiest is to use xsPchartPlugin which was written specifically for the job and is as simple as running ./symfony plugin:install xsPChartPlugin within your app and creating and chmoding the web/images/tmp/xspchart/ directory it is configured to use.
xsPChartPlugin takes care of a few otherwise manual steps and includes some additional features like a function to clear the image directory. I think it’s a great plugin but I was just a little concerned I then had two things I’d have to keep up to date, the pChart lib and the plugin, or the plugin might fall behind and I’d have to remove it anyway.
Fortunately after installing xsPChartPlugin and seeing how it worked, I could now see easily how pChart would need to be installed for Symfony use, so here’s the steps for using it by itself:
./symfony cc to clear the cache and autoload the classes
// Dataset definition $DataSet = new pData; $DataSet->AddPoint(array(10,2,3,5,3),"Serie1"); $DataSet->AddPoint(array("January","February","March","April","May"),"Serie2"); $DataSet->AddAllSeries(); $DataSet->SetAbsciseLabelSerie("Serie2"); // Initialise the graph $Test = new pChart(420,250); $Test->drawFilledRoundedRectangle(7,7,413,243,5,240,240,240); $Test->drawRoundedRectangle(5,5,415,245,5,230,230,230); $Test->createColorGradientPalette(195,204,56,223,110,41,5); // Draw the pie chart $Test->setFontProperties("Fonts/tahoma.ttf",8); $Test->AntialiasQuality = 0; $Test->drawPieGraph($DataSet->GetData(),$DataSet->GetDataDescription(),180,130,110,PIE_PERCENTAGE_LABEL,FALSE,50,20,5); $Test->drawPieLegend(330,15,$DataSet->GetData(),$DataSet->GetDataDescription(),250,250,250); // Write the title $Test->setFontProperties("Fonts/MankSans.ttf",10); $Test->drawTitle(10,20,"Sales per month",100,100,100); $Test->Render("example10.png");
You don’t need to include the
// Standard inclusions include("pChart/pData.class"); include("pChart/pChart.class");
lines obviously as Symfony has taken care of that for us.
$Test->setFontProperties("Fonts/tahoma.ttf",8);
becomes (in both spots)
$Test->setFontProperties(sfConfig::get('sf_lib_dir')."/vendor/pChart-1.27d/Fonts/tahoma.ttf",8);
Then
$Test->Render("example10.png");
becomes
$Test->Render(sfConfig::get('sf_web_dir') . "/images/pchart/example1.png");
For the lazy people like me who just want something to cut and paste, the whole thing might look something like:
public function executeIndex(sfWebRequest $request) { // Dataset definition $DataSet = new pData; $DataSet->AddPoint(array(10,2,3,5,3),"Series1"); $DataSet->AddPoint(array("January","February","March","April","May"),"Series2"); $DataSet->AddAllSeries(); $DataSet->SetAbsciseLabelSerie("Series2"); // Initialise the graph $Test = new pChart(420,250); $Test->drawFilledRoundedRectangle(7,7,413,243,5,240,240,240); $Test->drawRoundedRectangle(5,5,415,245,5,230,230,230); $Test->createColorGradientPalette(195,204,56,223,110,41,5); // Draw the pie chart $Test->setFontProperties(sfConfig::get('sf_lib_dir')."/vendor/pChart-1.27d/Fonts/tahoma.ttf",8); $Test->AntialiasQuality = 0; $Test->drawPieGraph($DataSet->GetData(),$DataSet->GetDataDescription(),180,130,110,PIE_PERCENTAGE_LABEL,FALSE,50,20,5); $Test->drawPieLegend(330,15,$DataSet->GetData(),$DataSet->GetDataDescription(),250,250,250); // Write the title $Test->setFontProperties(sfConfig::get('sf_lib_dir')."/vendor/pChart-1.27d/Fonts/tahoma.ttf",12); $Test->drawTitle(10,20,"Sales per month",100,100,100); $Test->Render(sfConfig::get('sf_web_dir') . "/images/pchart/example1.png"); }
All that’s left to do now is show the image by using a normal image tag in your template:
echo image_tag('pchart/example1.png')
Now this post doesn’t show you how to use the pChart cache and a lot of other things, but it does get you up and running! It’s personal choice as to whether you want to use the plugin or do it by hand, but either way have fun with these sexy charts! ![]()
15 Sep
We came across an interesting problem today when setting up an application we’d built earlier in a fresh dev environment to add some features…
Because we’re big fans of MySQL Workbench we made our initial schema changes there, generated the SQL to update the DB, then used a symfony propel:build-schema to generate an updated schema.yml followed by a symfony propel:build-model to update the model…
All good so far, until I began work on the first change and tried to fire off the task I’d just written…
Fatal error: Class 'BaseSMMConfig' not found in /usr/local/projects/smm/lib/model/SMMConfig.php on line 3
After some looking around, I realised my base class was called BaseSmmConfig - spot the problem? Yes, the ‘mm’ was lower case, while my class was looking for ‘MM’ in capitals…
So what caused this?
The application had originally been created by writing the schema.yml file (including the phpName being specified) then doing a symfony propel:build-all to create the DB and the model together. This is fine as the capitalisation used in the hand written schema.yml flowed through to the model. When I had done the reverse, creating a schema.yml from the DB, all it has to go on is the names of the tables, using camel case to identify where an underscore exists in the table name, so the ’smm_config’ table becomes SmmConfig in the schema.yml and model, not SMMConfig as originally typed. Because your extendable model classes don’t get updated (so as not to overwrite your changes) your app then breaks down.
So the lesson:
6 Mar
If you are defining your schema.yml and have a field that is a decimal value, you will find you will need to somehow pass through the precision (maximum number of digits to the left of the point) and the scale (total number after the decimal point). The documentation for the syntax of this is quite poor.
For all those that want to know, in Symfony 1.0 this is how you would define a decimal:
my_decimal: { type: decimal, size: 8,3 }
For Symfony 1.1+, the Syntax is a bit different:
my_decimal:
type: decimal
size: 8
scale: 3
Where 8 is the precision and 3 is the scale. Hope this helps.
4 Feb
Why tcpdf
TCPDF is current, well supported, well used and more up to date than equivalents such as domPDF which have had an unresolved security advisory out since 02/05/2008.
It is also very easy to integrate into a symfony project since it does not require external libraries for basic functions.
Features
The features below are from the TCPDF home page:
* no external libraries are required for the basic functions;
* supports all ISO page formats;
* supports custom page formats, margins and units of measure;
* supports UTF-8 Unicode and Right-To-Left languages;
* supports TrueTypeUnicode, OpenTypeUnicode, TrueType, OpenType, Type1 and CID-0 fonts;
* supports document encryption;
* includes methods to publish some (x)HTML code;
* includes graphic (geometric) and transformation methods;
* includes Javascript and forms support;
* includes a method to print various barcode formats (CODE 39, CODE 39 EXTENDED, Interleaved 2 of 5, CODE 128 A/B/C, EAN 13, UPC-A, POSTNET, CODABAR);
* includes methods to set Bookmarks and print a Table of Content;
* includes a method to move pages;
* includes methods for automatic page header and footer management;
* supports automatic page break;
* supports automatic page numbering and page groups;
* supports automatic line break and text justification;
* supports JPEG and PNG images natively, all images supported by GD (GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM) and all images supported via ImagMagick;
* supports stroke and clipping mode for text;
* supports clipping masks;
* supports Grayscale, RGB, CMYK, Spot Colors and Transparencies;
* supports several annotations, including links, text and file attachments;
* supports page compression (requires zlib extension);
* supports user rights management so Adobe Reader users can save filled-in copies of forms they complete.
Using tcpdf in a symfony project
I’m assuming here that you’re using PHP5 and that you’re happy to simply try this out by including the TCPDF libs in a project (as opposed to making them accessible by any project on your system).
Start by downloading TCPDF and unpacking this into your project lib folder … so where PROJECT is your project directory:
cd PROJECT/lib curl -O http://transact.dl.sourceforge.net/sourceforge/tcpdf/tcpdf_4_5_010.zip unzip tcpdf_4_5_010.zip
That’s it you’re ready - before commiting to svn (or your preferred code version repo) I also “clean up” the above PROJECT/lib/tcpdf/ directory by doing this:
rm -rf doc examples CHANGELOG.TXT README.TXTConfiguration
One can modify the properties of the generated PDF documents in the PROJECT/lib/tcpdf/config/tcpdf_config.php file.
These 6 settings are particularly important for the look and feel of the document.
The “my_logo.JPG” file should be placed in the PROJECT/lib/tcpdf/images/ directory.
What follows below is a list of the kind of properties you may wish to modify.
define ('PDF_PAGE_FORMAT', 'A4'); define ('PDF_PAGE_ORIENTATION', 'P'); define ('PDF_UNIT', 'mm'); define ('PDF_MARGIN_HEADER', 5); define ('PDF_MARGIN_FOOTER', 10); define ('PDF_MARGIN_TOP', 27); define ('PDF_MARGIN_BOTTOM', 25); define ('PDF_MARGIN_LEFT', 15); define ('PDF_MARGIN_RIGHT', 15); define ('PDF_FONT_NAME_MAIN', 'helvetica'); define ('PDF_FONT_SIZE_MAIN', 10); define ('PDF_FONT_NAME_DATA', 'helvetica'); define ('PDF_FONT_SIZE_DATA', 8);
Write your own class
Within the lib folder of the PROJECT, write a myPDF.php class that calls the tcpdf lib and writes a pdf file based on your PROJECT requirements.
Coupled with the configuration shown above, and the code below, it is possible to create a PDF document containing a table of data based on an array.
<?php require_once('tcpdf/config/lang/eng.php'); require_once('tcpdf/tcpdf.php'); // extend TCPF with custom functions class myPDF extends TCPDF { // Colored table public function ColoredTable($header,$data) { // setting up colours, widths and normal - not bold fontSetDrawColor(0, 0, 0); $this->SetLineWidth(0.3); $this->SetFillColor(211, 211, 211); $this->SetTextColor(0); $this->SetFont(''); // Data foreach($data as $row) { // custom call to do a two column table on one page // left column is smaller so as to accomodate longer text in the right column $this->Cell(70, 6, $row[0], 'LTR', 0, 'C', 1); $this->Cell(110, 6, $row[1], 'LTR', 0, 'C', 0); $this->Ln(); } $this->Cell(0, 0, '', 'T'); } }
The code above expects an array of data to be rendered into table rows. The array is of this format:
[0] => Array ( [0] => sfNerd: [1] => eHabib ) [1] => Array ( [0] => sfNerd: [1] => BigM ) [2] => Array ( [0] => sfNerd [1] => AK )
The following tcpdf methods are used to render the table with appropriate colours, line widths, populate content in table cells.
You can invoke the above class in the following manner (refactor to suit your needs etc). Place this code you your desired Symfony module/action.
public static function buildPDF($order_page, $myfileloc){ // array used to define the language and charset of the pdf file to be generated $language_meta = Array(); $language_meta['a_meta_charset'] = 'UTF-8'; $language_meta['a_meta_dir'] = 'ltr'; $language_meta['a_meta_language'] = 'en'; $language_meta['w_page'] = 'page'; // create new PDF document $pdf = new myPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false); // set document information $pdf->SetAuthor('An Order Form'); $pdf->SetTitle('An Order'); $pdf->SetSubject('An Order'); // set default header data $pdf->SetHeaderData(PDF_HEADER_LOGO, PDF_HEADER_LOGO_WIDTH, PDF_HEADER_TITLE, PDF_HEADER_STRING); // set header and footer fonts $pdf->setHeaderFont(Array(PDF_FONT_NAME_MAIN, '', PDF_FONT_SIZE_MAIN)); $pdf->setFooterFont(Array(PDF_FONT_NAME_DATA, '', PDF_FONT_SIZE_DATA)); //set margins $pdf->SetMargins(PDF_MARGIN_LEFT, PDF_MARGIN_TOP, PDF_MARGIN_RIGHT); $pdf->SetHeaderMargin(PDF_MARGIN_HEADER); $pdf->SetFooterMargin(PDF_MARGIN_FOOTER); //set auto page breaks $pdf->SetAutoPageBreak(TRUE, PDF_MARGIN_BOTTOM); //set image scale factor $pdf->setImageScale(PDF_IMAGE_SCALE_RATIO); //set some language-dependent strings $pdf->setLanguageArray($language_meta); // set font $pdf->SetFont('helvetica', '', 12); // add a page $pdf->AddPage(); //Column titles $header = array('Name', 'Type'); // lets build the array of data that needs to be published in a pdf $value=''; $data = array(); // iterate the orderpage variable and build our array foreach($order_page as $key => $val) { if($val != null) { array_push($data,array($key,$val)); } } // insert a colored table into the document with the above vars // ColoredTable is the function defined in a custom class in lib/ $pdf->ColoredTable($header, $data); //Close and output PDF document $pdf->Output($myfileloc, 'F'); }
All of these methods are documented here
Documentation and further examples
Excellent documentation on all aspects of TCPDF can be found here.
Usage examples can be found here.
Using the documentation and examples, one can quickly establish how to generate PDF documents using TCPDF.
11 Oct
If you are looking for a way to distribute your Symfony application - either as a deployment package, to host on another server without Symfony libs, or giving it out as a product you could always use the Symfony sandbox to do so. However, this becomes hard to maintain when Symfony versions keep getting updated, and you have to keep your sandbox updated. So whats the best way to do this?
There is a great post over at the “Left on the Web” blog, that walks you through how to distribute your Symfony application enbedded within your project by using svn:externals. Be sure to check it out.
10 Oct
So where we are currently working, we have a back catalogue of Symfony 1.0 applications, and are beginning to roll out a few Symfony 1.1 apps to production, as well as a few other things floating around on other versions. As helpful as the compatability plugin is, it can’t magically make any 1.0 app run under 1.1, and we made the decision that we just couldn’t afford the time to re-write… So what’s the best way of running multiple versions of Symfony apps together on the one environment?
The first thing we thought of was just using PEAR to install multiple version in different directoris, but unfortunately it cannot handle installing more than one version, so that’s out.
You could grab a Symfony sandbox for each application, and if you only had one or two 1.0 apps this is probabaly quite a valid option, but if you have a few more, your code-base would get very large, very quickly, and you miss out on the benefit of re-using components across your apps from one location.
So what did we actually do? In the end, we installed multiple versions of Symfony via SVN, allowing the environment to operate much as anyone who installed Symfony with PEAR would be familiar with, allowing each Symfony application to reference the global Symfony install and libraries etc. The example below document how this was set up in Ubuntu (our favoured development VM), but shouldn’t be too different form other Unix environments.
Step 1 - Un-install Symfony via PEAR
If you have Symfony installed via PEAR, get rid of it, it will only confuse you with what we are about to do.
express@express-dev:~$ sudo pear uninstall symfony/symfony
uninstall ok: channel://pear.symfony-project.com/symfony-1.1.0
Step 2 - Setup a structure for Symfony
In our case, I still want to install symfony in /usr/share/php/symfony, so lets set that up:
express@express-dev:~$ cd /usr/share/php
express@express-dev:/usr/share/php$ sudo mkdir symfony
Step 3 - Checkout each Symfony version you need
Now lets use SVN checkout to grab each Symfony version we are after, lets put these in a different folder under the base Symfony directory. Note: If you are behind a proxy, change your SVN settings first to go through your proxy. To change your proxy settings:
express@express-dev:/usr/share/php/symfony$ sudo nano /etc/subversion/servers
Now lets checkout each symfony version:
express@express-dev:/usr/share/php$ cd symfony/
express@express-dev:/usr/share/php/symfony$ sudo svn co http://svn.symfony-project.com/branches/1.0 symfony10
...
express@express-dev:/usr/share/php/symfony$ sudo svn co http://svn.symfony-project.com/branches/1.1 symfony11
...
express@express-dev:/usr/share/php/symfony$ sudo svn co http://svn.symfony-project.com/branches/1.2 symfony12
...
We now have created three installations of Symfony.
Step 4 - Create symbolic links for each version
The next step is for us to create symlinks for each version of Symfony. Lets place these in the standard bin directory:
sudo ln -s /usr/share/php/symfony/symfony10/data/bin/symfony /usr/bin/symfony10
sudo ln -s /usr/share/php/symfony/symfony11/data/bin/symfony /usr/bin/symfony11
sudo ln -s /usr/share/php/symfony/symfony12/data/bin/symfony /usr/bin/symfony12
Now lets test the sym links:
express@express-dev:~$ symfony10 -V
symfony version 1.0.19-PRE
express@express-dev:~$ symfony11 -V
symfony version 1.1.5-DEV (/usr/share/php/symfony/symfony11/lib)
express@express-dev:~$ symfony12 -V
symfony version 1.2.0-DEV (/usr/share/php/symfony/symfony12/lib)
What next? - Creating a new project
So to create a new project, you will need to use the relevant Symfony command. For example, to create a Symfony 1.0 project:
sudo symfony10 init-project test1
or to create a Symfony 1.1 or Symfony 1.2 Project:
sudo symfony11 generate:project test11
sudo symfony12 generate:project test12
Once you create a new project, check in the project Config to ensure its picked up the right version. For Symfony 1.0:
express@express-dev:/usr/local/express/projects/$ sudo symfony10 init-project test10
express@express-dev:/usr/local/express/projects/$ cat config/config.php
// symfony directories $sf_symfony_lib_dir = '/usr/share/php/symfony/symfony10/lib'; $sf_symfony_data_dir = '/usr/share/php/symfony/symfony10/data';
for Symfony 1.1:
express@express-dev:/usr/local/express/projects/test$ cat config/ProjectConfiguration.class.php
require_once '/usr/share/php/symfony/symfony11/lib/autoload/sfCoreAutoload.class.php'; sfCoreAutoload::register(); class ProjectConfiguration extends sfProjectConfiguration { public function setup() { } }
and for Symfony 1.2, its the same, just make sure its including the right 1.2 files. Thats it! Hope this helps!
1 Oct
If you are defining your schema.yml and have a field that is a decimal value, you will find you will need to somehow pass through the precision (maximum number of digits to the left of the point) and the scale (total number after the decimal point). The documentation for the syntax of this is quite poor.
For all those that want to know, in Symfony 1.0 this is how you would define a decimal:
my_decimal: { type: decimal, size: 8,3 }
For Symfony 1.1+, the Syntax is a bit different:
my_decimal:
type: decimal
size: 8
scale: 3
Where 8 is the precision and 3 is the scale. Hope this helps.
28 Sep
There is a great thread of posts in the Symfony Users Google Group about good practices on how to run multiple Symfony versions on the same machine. The different tips include:
Be sure to check it out
6 Aug
I’m working on a project that requires some batch data loads and export to excel functions. This was really the first time I’ve used Symfony batch.
Here are some random tips that helped me write my batch script. I think the Symfony book could use a bit more documentation detail when it comes to symfony batch as there are some things that aren’t even mentioned. Note: This is for Symfony 1.0.
Random Tip #1: Creating a symfony batch
I’m amazed how the documentation was lacking the details of what to put in the symfony batch, they just need a better explanation. So here is an example:
./symfony init-batch default symfony-nerds-look-good backend
In the example above, I’ve created a batch script called symfony-nerds-look-good, now, you might think that’s a problem in itself, but that’s another blog post! So the format of creating a batch job is of this is:
./symfony init-batch default scriptName appName
Note that ‘default’ is the name of the batch template. You can define multiple batch templates, but most of the time, the ‘default’ should be all that you need.
Random Tip #2: Symfony batch logs
If you are used to logging some custom logs with the Symfony logging classes in your view, its different for a batch component. In the actions class, this is all you have to do to write a log:
$this->logMessage("Creating spreadsheet...", "debug");
However, in batch you must do the following:
$logger = sfLogger::getInstance(); //firstly declare the instance of the sfLogger (otherwise you wont be able to log) $logger->log("Creating spreadsheet", SF_LOG_DEBUG); //Then use the standard symfony log constance (eg SF_LOG_DEBUG or SF_LOG_WARN) etc...
So it is slightly different to how you might normally do it.
Random Tip #3: Using model objects and the database with batch
If you would like to use the generated ORM objects that Propel has generated for you, you wont be able to straight away. You will notice if you try to, you will get the error “No connection params set for propel”. You must first instantiate a sfDatabaseManager, then initialise it:
$databaseManager = new sfDatabaseManager(); $databaseManager->initialize(); //Then you can access your model objects and write your criteria as per the usual way
Hope this helps you get started. Do you have any tips with Symfony batch? Let us know!
6 Aug
I am working on an interesting project where I have been given the task of creating a web interface that allows an authenticated and authorised user to conduct specific types of search on a huge data set.
The data set begins as flat text logs which are transformed and loaded into a normalised schema - which was designed well before I got involved. So, I have been very impressed by the way that propel and creole have consistently reverse engineered the schema and built a reliable model.
This is not a post about Propel or Creole but I thought to put this into context since I have been given a development specification that requires me to present data on a browser in a manner different to the way in which it is ordered and layed out in the database schema.
So - consider a table that has the following structure:
id (auto increment unique id) my_date (of type date) my_date_as_int (a conversion of date to int eg 2008-08-06 would be 20080806)
The insertion of dates into the above fields is not ordered - so id 1 may have an older date than id 2 and so on…for example:
| id | my_date_as_int | my_date |
| 1 | 20080602 | 2008-06-02 |
| 2 | 20080724 | 2008-07-24 |
| 3 | 20080723 | 2008-07-23 |
| 4 | 20080411 | 2008-04-11 |
| 5 | 20080412 | 2008-04-12 |
My task was to create a “drop down” select tag that showed the above sorted in an ascending order by the date field.
Step 1. Create a __toString method to return the date field as opposed to the Id.
public function __toString() { return $this->getMyDate(); }
Step 2. Create a custom method in the relevant Peer class to get a sorted list:
public static function getSortedDate() { $c = new Criteria(); $c->addAscendingOrderByColumn(DatePeer::MY_DATE_AS_INT); $rs = DatePeer::doSelect($c); return $rs; }
As you can see, I choose to sort by the integer representation of the date field, but you can use the MY_DATE field in the same manner.
Step 3. Create a drop down select_tag in your view.
There are two options here.
Option 1. Standard form name, value pairs being the date and id fields respectivey.
<?php echo object_select_tag('', 'getMyDate', array ( 'related_class' => 'Date', 'peer_method' => 'getSortedDate', 'include_blank' => true, )) ?>
Option 2. Form name, value pairs being the my_date and the my_date_as_int fields respectivey.
<?php echo select_tag('', objects_for_select(DatePeer::getSortedDate(), 'getMyDateAsInt','getMyDate'), array('id'=>"this_date",'name'=>"this_date")) ?>
That is it! The view then renders a drop down select that is constructed firstly by a call to the getSortedDate method, which in turn returns an array of sorted items - each of which is shown in the drop down using the __toString method to convert from an id to the date.