PHP + Symfony Rapid application development Jonathan Bell Nov 18, 2010 A Developer’s User Story • • • • It’s 5:00pm on Sunday Need to have a web app together for Monday Want to sleep tonight Want to have a “complete app” (validation, security, etc) • Don’t have time machine • EJB? No… • .Net? No… Enter Symfony Symfony is a code generator/MVC framework for PHP. Sounds a lot like ruby on rails. Why PHP? PHP is super C-like, might be easier to pick up • ORB mapping (pluggable) • CRUD generation • Validation • • • • Routing Templating Plugins Unit Testing Object Brokering (Model layer) • Automatically generates fully documented PHP classes from config file • Removes need for repeated DB calls, provides abstraction schema.yml propel: weblog_post: _attributes: { phpName: Post } id: title: varchar(255) excerpt: longvarchar body: longvarchar created_at: PHP Code $p = new Post(); $p->setTitle(“Test Post”); $p->setBody(“the body”); $p->save(); $posts = PostPeer::doSelect(new Criteria()); foreach($posts as $post) { print “Post: “.$post->getTitle(); } CRUD Generation + Validation (Controller + View) • Web programmer’s worst nightmare: writing huge forms with dozens of inputs, associated form processors and validators • Symfony to the rescue: generates template Create/Read/Update/Delete • Very, very easy to customize once generated symfony propel:generate-module admin findable FindableItem indexSuccess.php: <h1>Findable List</h1> <table> <thead> <tr> <th>Id</th> <th>Name</th> <th>Cardinality</th> <th>Points</th> <th>Description</th> <th>S findable category</th> </tr> </thead> <tbody> <?php foreach ($findable_item_list as $findable_item): ?> <tr> <td><a href="<?php echo url_for('findable/edit?id='.$findable_item->getId()) ?>"><?php echo $findable_item->getId() ?></a></td> <td><?php echo $findable_item->getName() ?></td> <td><?php echo $findable_item->getCardinality() ?></td> <td><?php echo $findable_item->getPoints() ?></td> <td><?php echo $findable_item->getDescription() ?></td> <td><?php echo $findable_item->getSFindableCategoryId() ?></td> </tr> <?php endforeach; ?> </tbody> </table> <a href="<?php echo url_for('findable/new') ?>">New</a> actions.class.php: public function executeIndex(sfWebRequest $request) { $this->findable_item_list = FindableItemPeer::doSelect(new Criteria()); } Validation • Free with form generation! • We already defined the form “widgets” in the original .yml file, including basic data ranges BaseFindableItemForm $this->setValidators(array( 'id' => new sfValidatorPropelChoice(array('model' => 'FindableItem', 'column' => 'id', 'required' => false)), 'name' => new sfValidatorString(array('max_length' => 255, 'required' => false)), 'cardinality' => new sfValidatorInteger(array('required' => false)), 'points' => new sfValidatorInteger(array('required' => false)), 'description' => new sfValidatorString(array('required' => false)), 's_findable_category_id' => new sfValidatorPropelChoice(array('model' => 'FindableCategory', 'column' => 'id')), )); Can easily override this in the implementation for FindableItemForm with new validators. Or, don’t bother and leave it at default! Aspect Oriented Development? • Symfony supports “behaviors” • Automatically creates many hook-spots • Classic example: “paranoid” behavior – Replace deletes with setting a flag – Augment selects to avoid records with that flag Routing • Problem: We all love having simple looking URLs: – http://cusearch09.com/user/image/5/320 – Instead of http://cusearch09.com/user/upload/imageT humb.php?id=5&width=320 • Solution: – Apache mod_rewrite • No... too complicated and hard to change on the fly. Who wants to deal with RegExp? – Symfony: • routing.yml resized_image: url: image/:id/:width param: {module: upload, action: imageThumb} Templating • • • • Generic template setup Allows for “partial” templates Inheritance! Helpers – link_to() – img_tag() Plugins • Easy to extend Symfony with plugins • Follow observer pattern – static public function listenToSomeSortOfEvent(sfEvent $event) – Hooks your plugin to any part of Symfony • Uses: objects, aspects – Security? AJAX? Free Lime Tests! (note: not Lime’s Disease tests) Unit test the model $t = new lime_test(1, new lime_output_color()); $t->diag('->retrieveByUsername()'); $user = UserPeer::retrieveByUsername('jbell'); $t->is($user->getLastName(), 'Bell', '->retrieveByUsername() returns the User for the given username'); Functional test the controllers (WOW!) $b = new sfTestBrowser(); $b->get('/foobar/edit/id/1'); $request = $b->getRequest(); $context = $b->getContext(); $response = $b->getResponse(); // Get access to the lime_test methods via the test() method $b->test()->is($request->getParameter('id'), 1); $b->test()->is($response->getStatuscode(), 200); $b->test()->is($response->getHttpHeader('content-type'), 'text/html;charset=utf-8'); $b->test()->like($response->getContent(), '/edit/'); But wait, there’s more! • Who would want to run each test manually, or write a script even (bah!) • Put all tests in the “unit” folder • then... • $ symfony test:all A code review: Rentpost • Managing landlord/tenant relations • PHP, Symfony, AJAX