Archive for the ‘Zend Framework’ Category

More Doctrine 2 and Zend Framework integration goodies!

Friday, July 15th, 2011

Introduction

So, it’s been a while since I’ve posted but I’ve been hard at work the last few evenings on a few very cool features.

  1. I’ve written a Spiffy\Entity class that you can extend Doctrine 2 entities from to gain a few new features.
    • Zend Validator and Zend Filter annotations for entities.
    • You can now validate directly on the model (this is how it should be, IMO).
  2. I’ve written Spiffy\Form and Spiffy\Dojo\Form that integrates with Spiffy\Entity to do field type detection automatically somewhat similar to Symfony 2.
    • The form is smart enough to read the validators/filters from Spiffy\Entity. No more adding them directly to each form element!
    • You have the option to autoPersist Spiffy\Entity if the form validates.
  3. I’ve added three new form elements – Spiffy_Form_Element_Entity, Spiffy_Dojo_Form_Element_ComboBoxEntity, and Spiffy_Dojo_Form_Element_FilteringSelectEntity.
    • These elements allow you to map ManyToOne relations directly into your form. They’re still a work in progress (need to add store data option to the Dojo elements).
This project is currently in early development and I would not recommend use in a production environment! Follow the GitHub repository README for when I consider it stable.

Usage

Let’s get to the nitty gritty and build a complete registration form, controller, and entity. At the time of writing (7/15/2011) this post is accurate, however, the latest documentation is always available on my GitHub repository.

Setting up the Spiffy library

  1.  by downloading or cloning it into your include_path.
  2. Add the namespace to application.ini: autoloaderNamespaces[] = Spiffy
  3. Add the Spiffy pluginPaths to application.ini: pluginPaths.Spiffy_Application_Resource = “Spiffy/Application/Resource”
  4. Add the Spiffy annotations to Doctrine in your bootstrap: \Doctrine\Common\Annotations\AnnotationRegistr::registerFile(“Spiffy/Doctrine/Annotations/Zend/Validator.php”);
  5. Set the default entity manager for Spiffy\Form. \Spiffy\Form::setDefaultEntityManager($em);

Creating an entity

// My/Entity/User.php
namespace My\Entity;
use Spiffy\Entity;

/**
 * @ORM\Table(name="user")
 * @ORM\Entity
 */
class User extends Entity
{
        /**
         * @var integer $id
         *
         * @ORM\Column(name="id", type="integer")
         * @ORM\Id
         * @ORM\GeneratedValue(strategy="AUTO")
         */
        private $id;

        /**
         * @var string $username
         *
         * @ORM\Column(type="string")
         */
        private $username;

        /**
         * @var string $email
         *
         * @Assert\EmailAddress()
         * @ORM\Column(type="string")
         */
        private $email;

        /**
         * @var string $password
         *
         * @ORM\Column(type="string")
         */
        private $password;

        public function __toString() {
                return "{$this->username}";
        }

        public function getId() {
                return $this->id;
        }

        public function setId($id) {
                $this->id = $id;
        }

        public function getUsername() {
                return $this->username;
        }

        public function setUsername($username) {
                $this->username = $username;
        }

        public function getEmail() {
                return $this->email;
        }

        public function setEmail($email) {
                $this->email = $email;
        }

        public function getPassword() {
                return $this->password;
        }

        public function setPassword($password) {
                $this->password = $password;
        }
}

The Form

// My/Forms/Register.php
namespace My\Forms\Register;
use Spiffy\Dojo\Form;

class Register extends Form
{
        /**
         * (non-PHPdoc)
         * @see Zend_Form::init()
         */
        public function init() {
                $this->setName('register');
                $this->add('username');
                $this->add('email');
                $this->add('password', 'PasswordTextBox');
                $this->add('submit', 'SubmitButton');
        }

        /**
         * (non-PHPdoc)
         * @see Spiffy.Form::getDefaultOptions()
         */
        public function getDefaultOptions() {
                return array('entity' => 'Blitzaroo\Entity\User');
        }
}

The Controller

// application/controllers/RegisterController.php
use My\Forms\Register;

class RegisterController extends Zend_Controller_Action
{
        public function indexAction() {
                $form = new Register();
                $request = $this->getRequest();

                if ($request->isPost()) {
                        if ($form->isValid($request->getPost())) {
                                // success!

                                // user entity
                                $user = $form->getEntity();

                                // outputs the value of $form->username
                                echo $user->getUsername();
                        }
                }

                $this->view->form = $form;
        }
}

Wrap Up

Well, there’s a small example of what you can do with the new classes. This is not an exhaustive example nor have all the features (Filters) been implemented. Enjoy!

Super sexy URLs with ZF and the joy of controller plugins!

Friday, April 1st, 2011

I’m in a bit of a hurry so this one will be short and sweet. I wanted to be able to use sexy uris (/your-homepage-rocks) instead of the lame-o /module/controller/action/id/# URIs. I also wanted error handling to work properly if they tried to access a page that didn’t exist so, what to do!? Build a controller plugin of course – ain’t (English police get me) they sexy?

// My\Controller\Plugin\Page.php

// I'm a huge-mongous fan of namespaces
namespace My\Controller\Plugin;
use Zend_Controller_Plugin_Abstract, Zend_Controller_Front;

class Page extends Zend_Controller_Plugin_Abstract
{
        // predispatch so that we can take over before routing kicks in
        public function preDispatch($request)
        {
                $front = \Zend_Controller_Front::getInstance();
                $dispatch = $front->getDispatcher();
                
                // normally when something isn't dispatchable an exception is thrown and we're whisked away to the errorController
                if (!$dispatch->isDispatchable($request)) {
                        $bootstrap = $front->getParam('bootstrap');
                        $website = $bootstrap->getResource('website');
                        
                        // did we find a page?
                        $page = \My\Service::getRepository('Website\Page')->findOneBy(
                                array('website' => $website->getId(), 'slug' => $request->getControllerName()));

                        if ($page) {
                                // page exists, reroute by setting controller, action, and an additional "page" param
                                $request->setModuleName('default')
                                        ->setControllerName('page')
                                        ->setActionName('index')
                                        ->setParam('page', $page);
                        }
                }
        }
}
// application\controllers\PageController

class PageController extends Zend_Controller_Action
{
        // before dispatching, verify a few page properties
        public function preDispatch()
        {
                // grab the page from earlier - if it doesn't exist it will be handled with an exception
                $page = $this->_request->page;
                if (!$page || !$page->getPublished()) {
                        // page must exist and be published
                        throw new \My\Exception\NotFound();
                } elseif (!$page->isAllowed('read')) {
                        // user must have read permission
                        throw new \My\Exception\AccessForbidden();
                }
                
                // send it to the view to take care of
                $this->view->page = $page;
        }

        public function indexAction()
        {}
}

And, it works! You can handle the case where a page doesn’t exist as usual with the errorController. If a page does exist then everything is routed over to the pageController to display as normal. The only downside to this sexy magic is the additional query when an invalid request is dispatched. Fortunately, the overhead on that is minimal.

Formatting your API to work with dojox.data.JsonRestStore (#dojo)

Wednesday, March 30th, 2011

Greetings! Long time no post, I know. I’ve been busy with PetWise and as well as the occasional game of Heroes of Newerth and/or WoW arenas. This little post will attempt to help those working with JsonRestStore (JRS) when their API doesn’t output exactly what JRS is expecting. What we’re going to be doing is extending dojox.data.JsonRestStore and creating our own store in its place while overriding the _processResults() method.

The first thing you will want to do is register a Dojo module path so that Dojo knows exactly where to find your custom store. In my example, I’m going to be using Zend Framework so in my application.ini file I simply include a line like resources.dojo.modulePaths.blitz = “/js/dojo/blitz”. We’ll also want to be sure that we require the appropriate class when working with our new store. You can do this globally in the application.ini by adding resources.dojo.requireModules[] = “blitz.data.JsonRestStore” or by adding it to whatever page the store will be used in.

Next, you’ll need to declare the class by creating the file. In my example, I create a file called JsonRestStore.js in /js/dojo/blitz/data.

// /js/dojo/blitz/data/JsonRestStore.js

dojo.provide("blitz.data.JsonRestStore");
dojo.require("dojox.data.JsonRestStore");

// declare the store extending from JsonRestStore
dojo.declare("blitz.data.JsonRestStore", dojox.data.JsonRestStore, {
        // process results takes the results from getQuery and turns them into something the store can handle
        _processResults:function(results, deferred) {
                // custom format for blitz api
                results = results.result; // this simple line causes JsonRestStore to work with my own API implementation
                // index the results
                var count = results.length;
                // if we don't know the length, and it is partial result, we will guess that it is twice as big, that will work for most widgets
                return {totalCount:deferred.fullLength || (deferred.request.count == count ? (deferred.request.start || 0) + count * 2 : count), items: results};
        }
});

Here’s a dump of my profiles API inside of Blitzaroo to show why this works. Notice that all the data comes from the result key which is why I have the line results = results.result. This allows me to include extra information about the request whenever the API is hit.

{
  -request: {
    uri: "/api/profiles"
    format: "json"
    action: "list"
  }
  -result: [
    -{
      id: 1
      userId: 1
      name: "Igatchew"
      private: false
      -created: {
        date: "2011-03-25 15:50:20"
        timezone_type: 3
        timezone: "America/Chicago"
      }
    -updated: {
        date: "2011-03-25 15:50:22"
        timezone_type: 3
        timezone: "America/Chicago"
      }
    }
   -{
      id: 2
      userId: 1
      name: "Snaggins"
      private: true
      -created: {
        date: "2011-03-25 20:32:34"
        timezone_type: 3
        timezone: "America/Chicago"
      }
      -updated: {
        date: "2011-03-25 20:32:36"
        timezone_type: 3
        timezone: "America/Chicago"
      }
    }
  ]
}

Hope that’s helpful!