Posts by Tag "ZendFramework"

Subscribe to posts of this tag

Using Symfony Dependency Injection with Zend_Application

As a follow-up to my recent post on Dependency Injection containers and Zend_Application I was eager to find out if its possible to integrate the new Symfony Dependency Injection Container into the Zend Framework. To my suprise its possible without having to make any changes to one of the two components. An example use-case would look like:

$container = new sfServiceContainerBuilder();
 
$loader = new sfServiceContainerLoaderFileXml($container);
$loader->load(APPLICATION_PATH.'/config/objects.xml');
 
$application = new Zend_Application(
    APPLICATION_ENV,
    APPLICATION_PATH . '/config/application.xml'
);
$application->getBootstrap()->setContainer($container);
$application->bootstrap()
            ->run();

Resources instantiated by Zend_Application are then injected into the container by the name of the resource and are given the resource instance. Any object from the Symfony container can then use these dependencies in their object setup. The only drawback of the integration is the fact that the Symfony Container is case-sensitive in regards to the service names but Zend_Application lower-cases all service before injecting them into the container. The following code is a restatement of my previous example of a MyModel class which requires a Zend_Db and Zend_Cache constructor argument.

$container->register('myModel', 'MyModel')
          ->addArgument(new sfServiceReference('db'))
          ->addArgument(new sfServiceReference('cache'));

Access to a MyModel instance with its dependencies is then granted through the call $container->myModel throughout the application. Make sure to call this after running Zend_Application::bootstrap, so that the Resource dependencies are injected first.

Using a Dependency Injection Container with Zend_Application

Much has been written on Dependency Injection lately, mostly by Padraic Brady (1, 2) and Fabien Potencier (1, 2, 3, 4, 5). My subjective feeling tells me there are now more PHP DI containers out there than CMS or ORMs implemented in PHP, including two written by myself (an overengineered and a useful one). Its an awesome pattern if used on a larger scale and can (re-) wire a complex business application according to a clients needs without having to change much of the domain code. It aims at a complete seperation of object instantiation and dependency tracking from the business logic.

Beginning with version 1.8 Zend Framework is able to integrate any of these DI containers into its Zend_Application component easily. The Application component intializes a set of common resources and pushes them into the MVC stack as additional Front Controller parameters. Technically a Zend_Registry instance holds all these resources with their respective resource names as keys. The resources are accessible inside the Front Controller and the Action Controller classes. Assume the resource 'Db' is initialized in your application, you can access it with:

$front = Zend_Controller_Front::getInstance();
$container = $front->getParam('bootstrap')->getContainer();
$db = $container->db;
 
// or:
class FooController extends Zend_Controller_Action {
    public function barAction()
    {
        $container = $this->getInvokeArg('bootstrap')->getContainer();
        $db = $container->db;
    }
}

This is pretty much dependency injection already, but the default registry approach suffers from two serious problem: New instances can only be added to the Container by implementing new Zend_Application resources and these instances cannot be lazy loaded. All resources used by Zend_Application are always loaded on every request. But the application container implementation was developed with Dependency Injection in mind and is not tied to the use of Zend_Registry. Only three magic methods are required by any container that wants to be Zend_Application compliant: __get(), __set() and __isset(). Each instantiated resource is pushed via __set() into the container. If required by another resource, __isset() is used to check wheater a resource with the given name exists inside the container and __get() is used to retrieve the instances from the container.

Some month ago I extended Yadif, a lightweight PicoContainer-like DI container for PHP written by Thomas McKelvey, with several features that make it a very powerful DI container in my opinion. It has a detailed documentation and examples on the GitHub Page if you want to check it out. I extended Yadif to be Zend_Application compliant in a way that the application-wide Zend_Application resources can be used as dependencies for objects that are lazy-loaded from the container. Skipping the tech-talk, here is an example. First we have to replace the default Container with Yadif:

$objects = new Zend_Config_Xml(APPLICATION_PATH."/config/objects.xml");
$container = new Yadif_Container($objects);
 
$application = new Zend_Application(
    APPLICATION_ENV,
    APPLICATION_PATH . '/config/application.xml'
);
// Set Yadif as Container
$application->getBootstrap()->setContainer($container);
$application->bootstrap()
            ->run();

Assume you run Zend_Application with a "Db" and a "Cache" resource. These resources are loaded on every request. The objects configured in Yadif however are not instantiated until they are requested for the first time from the container. We can merge these two worlds and make use of the Application resources inside the Yadif Configuration "objects.xml", which looks like:

<?xml version="1.0" ?>
<objects>
    <myModel>
        <class>MyModel</class>
        <arguments arg1="db" arg2="cache" />
    </myModel>
</objects>

Instantiating a myModel class inside the Action Controller uses the the Database and Cache resources as constructor arguments:

class FooController extends Zend_Controller_Action {
    public function barAction()
    {
        $container = $this->getInvokeArg('bootstrap')->getContainer();
        $myModel = $container->myModel;
    }
}

Given the possibilites by the Yadif_Container you are now empowered to use Dependency Injection for all your application objects and make use of Zend_Applications resource system. Furthermore any other dependency injection container, simple or complex, can also be integrated easily.

Using Zend_Soap Server and Autodiscover in a Controller

I am in a dilemma. I have condemned the usage of Zend_Soap_Server (or any other webservice server handler) inside a Model-View-Controller application before. Still I get questions about my old Zend_Soap_Server and Zend_Soap_AutoDiscover example not working in the MVC scenario. The following example provides you with a working Soap server inside a Zend_Controller_Action, although I discourage the use of it and would suggest using a dedicated script outside the dispatching process to gain multitudes of performance, which webservices often require.

require_once "/path/to/HelloWorldService.php";
 
class MyDiscouragedSoapServerController extends Zend_Controller_Action
{
    public function serverAction()
    {
        $server = new Zend_Soap_Server("http://example.com/pathto/wsdl");
        $server->setClass('HelloWorldService');
        $server->handle();
    }
 
    public function wsdlAction()
    {
        $wsdl = new Zend_Soap_AutoDiscover();
        $wsdl->setUri('http://example.com/pathto/server');
        $wsdl->setClass('HelloWorldService');
        $wsdl->handle();
    }
}

Now all you have to do is create two routes, one that makes http://example.com/pathto/server point to MyDiscouragedSoapServerController::serverAction and the other route that makes http://example.com/pathto/wsdl point to MyDiscouragedSoapServerController::wsdlAction. The wrong version (Version Mismatch) error comes from sending a request for the WSDL file to the actual Soap Server, which he doesn't like.

Zend_Form and the Model: Yet another perspective using a Mediator

Matthew Weier O'Phinney of the Zend Framework Devteam wrote a controversial post on integrating Zend_Form and the Model last month. He separated concerns of view and model that communicate via a Form, by calling just thee validation functions on the Form inside the mode. On request you could retrieve the model to the controller and view layers. I already wrote into his comments that I didn't like the solution because it relys on implicit rules for the developers to use the Form component correctly in all layers. Additionally the building of the form using this approach would be performed inside the model, although strictly speaking this is responsibility of the View Layer. Another negative point is duplication of input filtering code that has to be performed to use certain variables inside the controller or when different forms talk with the same model.

Jani took it up and proposed writing validators for forms and attaching them to the Form as sort of a mediator. I am not a fan of this approach either, because the validator would have to include domain logic but is not really a part of the domain logic anymore but just a validator. Developers might forget using the validator inside the model for all their actions or there would be duplication of code in some places. In a perfect world, only functions of the models public interface should be called for validation.

My personal favorite for Form and Model integration is a mediator object between the two layers. Your model will have to include an additional interface with one function acceptFormRequest($values); which accepts an array of validated Zend Form field values. It then tries to apply the validated data into a record. Additional required validations of the model can take place in this function, which separates the concerns of Form validation and Model data validation. Still the mediator merges those differences together: You can throw an Exception and it will be attached as a custom error message to the Form. The following very short code will show the required interface and the mediator code. This code is very simple and might produce maintenance overhead fast, but I propose some refactoring enhancements later in the discussion.

interface WW_Model_AcceptFormRequest
{
    /**
     * Acceept a form request
     * @param array $values
     * @return WW_Record_Interface
     */
    public function acceptFormRequest($values);
}
class WW_Model_FormMediator
{
    /**
     * Try to push the form request to the model
     * 
     * @param Zend_Form $form
     * @param WW_Model_AcceptFormRequest $model
     * @return WW_Record_Interface
     */
    public function pushFormToModel(Zend_Form $form, WW_Model_AcceptFormRequest $model)
    {
        if(!$form->isValid()) {
            throw new Exception("Form not valid!");
        } else {
            $values = $form->getValues();
            try {
                $record = $model->acceptFormRequest($values);
            } catch(Exception $e) {
                // This exception message comes from the model, because validation failed
                $form->addErrorMessage($e->getMessage());
                throw new Exception("Form request not accepted by model!");
            }
        }
        return $record;
    }
}

You can see the mediator has two different stages where errors can occour: When the form is not valid or the model is not valid. Both exits can be catched inside the controller and are the indicator that the form has to be displayed again for further input corrections. When successful the model returns a valid record that applies to the form and model requirements and can be displayed. If this record should be persistent this would have been done inside the acceptFormRequest function already. An example using a very simple Model using the a BankAccount example. We have a form that validates all the incoming request data for a withdrawel of money, though does not validate it against the models internal state. Our BankAccountModel implements the WW_Model_AcceptFormRequest interface and returns a valid BankAccount. If found the given amount is withdrawn.

class BankAccountModel implements WW_Model_AcceptFormRequest {
    public function acceptFormRequest($values)
    {
        $bankAccount = $this->getBankAccountBy($values['bankAccountNumber'], $values['pin']);
        if($values['action'] == "withdraw") {
            $bankAccount->withdraw($values['amount']);
            $this->save($bankAccount);
        } else {
            // unknown action...
        }
    }
    public function getBankAccountBy($key, $password) {
        // Find by Primary Key returning 'BankAccount' instance or exception if not found.
    }
    public function save(BankAccount $ba) {
        // Sql for saving the Bank Account
    }
}
 
class BankAccount
{
    public function withdraw($amount)
    {
        if( ($this->getBalance()-$amount) < 0 ) {
            throw new Exception("You cannot withdraw more money than your bank account holds!");
        }
        $this->balance -= $amount;
    }
}

Two exceptions might be thrown in this case: The Bank Account number does not exist or the password is wrong. Or you are not allowed to withdraw the given amount of money. If any of those exceptions is thrown the Model does not accept the form data and the form will have to be displayed again for the client showing the new error message that was returned from the model. The controller handling this process would lool like this:

class BankAccountController extends Zend_Controller_Action {
    public function performWithdrawlAction() {
        $form = new BankAccountWithdrawlForm(); // extends Zend_Form and builds the form
 
        if($this->getRequest()->isPost()) {
            $mediator         = new WW_Model_FormMediator();
            $bankAccountModel = new BankAccountModel();
            try {
                $bankAccount = $mediator->pushFormToModel($form, $bankAccountModel);
 
                $this->view->assign('bankAccount', $bankAccount); // Show new balance in view!
            } catch(Exception $e) {
                $this->view->assign('withdrawlForm', $form);
                $this->_redirect('showWithdrawl');
            }
        } else {
            $this->view->assign('withdrawlForm', $form);
            $this->_redirect('showWithdrawl');
        }
    }
}

You can see the mediator tightly integrates Form and Model without both components knowing too much of each other. Still you can add error messages recieved from the model into the Form and redisplay it. One negative point of this approach is the fact that you only have one method for accepting form data, which could result in variable checking and redispatching in the case of many different operations that can be performed on the same model. For this case you might want to either:

  1. Rewrite the mediator to accept a specific model class (not the interface) and call the required custom method that matches the forms request. (Best approach for separation concerns)
  2. Rewrite the mediator to also pass the get_class($form); value to the model for decision making (Faster approach)

There is still some overhead on using the mediator. Since its generic you could build an Action Helper for it and use the direct call mechanism to save some lines of code.

Finally: Zend_Mail charset and/or long lines header encoding bug fixed

There was this lurking bug in Zend_Mail which destroyed every Mail-Header (and corresponding Mail) with non US-ASCII Chars and more than an encoded length of 74 chars. This is quite a huge subset of mails, but it seems a nice solution was not so easy, at least nobody tried to fixed it for quite some time.

Where many hackish solutions we're offered, Ota Mares aka Littlex spent incredible time to hunt the original problem down and with his help I tag-teamed the bug to death today. Saturo Yoshida of Zend Fame added some further spice regarding an alternative solution with Base64 Encoding instead of Quoted Printable Mime Header encoding.

In the end the solution we chose was, not to re-use the Mime encoding function that is specific to MIME bodies according to RFC2045, but to write a completely new algorithm for Mime Headers, whose rules are specified in RFC2047. This is now done and unit-tests prove its working according to standard.

What is missing now is people trying that fix on as many Mail platforms as possible and giving feedback in the issue if a lengthy subject with non-ASCII chars is displayed correctly.