Archive for PHP

cURL: Adding/Installing/Trusting New Self-Signed Certificate

Scenario

It has been that situation again when I have spent a whole day trying to research and figure out the solution to a problem that has bugged me. Hopefully this will save researching time for people having the same issue.

If you, like me, are trying to get cURL, or rather, plugin such as WordPress OpenID (which uses PHP libcurl) to work with HTTPS sites that use self-signed certificates but do not wish to compromise the security by disabling CURLOPT_SSL_VERIFYPEER and/or CURLOPT_SSL_VERIFYHOST option in the code, the following might be the answer for you.

Some background

You can skip this part actually. But if you are interested in some technical details, you can continue reading. This post is written on the assumption that Ubuntu Linux is used. It should apply to other distros as well with minor differences.

When we are using cURL to retrieve a HTTPS site that is not using a CA-signed certificate, the following problem occurs. Of course, this can simply be overcome by using the -k option.

root@ubuntu:/etc# curl https://example.selfip.com
curl: (60) SSL certificate problem, verify that the CA cert is OK. Details:
error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed
More details here: http://curl.haxx.se/docs/sslcerts.html

curl performs SSL certificate verification by default, using a "bundle"
 of Certificate Authority (CA) public keys (CA certs). The default
 bundle is named curl-ca-bundle.crt; you can specify an alternate file
 using the --cacert option.
If this HTTPS server uses a certificate signed by a CA represented in
 the bundle, the certificate verification probably failed due to a
 problem with the certificate (it might be expired, or the name might
 not match the domain name in the URL).
If you'd like to turn off curl's verification of the certificate, use
 the -k (or --insecure) option.

However, in the case of applications such as WordPress OpenID plugin, we have to amend the code to disable both CURLOPT_SSL_VERIFYPEER and/or CURLOPT_SSL_VERIFYHOST. Not a very good idea as this will disable verification for authentic sites as well. If we know example.selfip.com, in this case is one of our own trusted test servers, we can add its certificate to the trusted list.

Now that we know what to do, we have to find out how to do it. It took me quite a while to look for the solution as I was looking in the wrong places. WordPress OpenID and cURL documentations/forums did not point me in the right direction. I decided to take a look at the source code in cURL and discover that ultimately, the responsibility of the verification lies with OpenSSL.

Arm with this and some right keyword searching, it has led me to this HOWTO, which provided some insight on how OpenSSL recognizes certificate authorities, and an important program. Right now, we are all set to add the new certificate so that our OpenSSL can recognize the HTTPS server.

The solution

Identify which directory your OpenSSL installation uses.

root@ubuntu:~# openssl version -d
OPENSSLDIR: "/usr/lib/ssl"

Change to that directory.

root@ubuntu:~# cd /usr/lib/ssl

List the directory contents. You should see a directory called “certs”.

root@ubuntu:/usr/lib/ssl# ls -la
total 24
drwxr-xr-x  4 root root  4096 2009-04-24 14:37 .
drwxr-xr-x 50 root root 12288 2009-04-24 17:56 ..
lrwxrwxrwx  1 root root    14 2009-04-24 14:37 certs -> /etc/ssl/certs
drwxr-xr-x  2 root root  4096 2009-04-24 14:37 engines
drwxr-xr-x  2 root root  4096 2009-04-24 14:37 misc
lrwxrwxrwx  1 root root    20 2009-04-24 14:37 openssl.cnf -> /etc/ssl/openssl.cnf
lrwxrwxrwx  1 root root    16 2009-04-24 14:37 private -> /etc/ssl/private

Change to that directory.

root@ubuntu:/usr/lib/ssl# cd certs

List the directory contents. You should see from the symlinks that the certificates are actually stored in “/usr/share/ca-certificates”.

root@ubuntu:/usr/lib/ssl/certs# ls -la
total 648
drwxr-xr-x 2 root root  16384 2009-06-24 12:32 .
drwxr-xr-x 4 root root   4096 2009-04-24 14:39 ..
lrwxrwxrwx 1 root root     26 2009-06-24 12:32 00673b5b.0 -> thawte_Primary_Root_CA.pem
lrwxrwxrwx 1 root root     59 2009-06-24 12:32 2edf7016.0 -> Verisign_Class_1_Public_Primary_Certification_Authority.pem
lrwxrwxrwx 1 root root     61 2009-06-24 12:32 thawte_Primary_Root_CA.pem -> /usr/share/ca-certificates/mozilla/thawte_Primary_Root_CA.crt
lrwxrwxrwx 1 root root     94 2009-06-24 12:32 Verisign_Class_1_Public_Primary_Certification_Authority.pem -> /usr/share/ca-certificates/mozilla/Verisign_Class_1_Public_Primary_Certification_Authority.crt
.
.
.

Change to that directory.

root@ubuntu:/usr/lib/ssl/certs# cd /usr/share/ca-certificates

List the directory contents. As you can see, I have added “example.selfip.com.crt” here.

root@ubuntu:/usr/share/ca-certificates# ls -la
total 56
drwxr-xr-x 11 root root  4096 2009-06-24 15:02 .
drwxr-xr-x 98 root root  4096 2009-04-24 17:51 ..
drwxr-xr-x  2 root root  4096 2009-04-24 14:35 brasil.gov.br
drwxr-xr-x  2 root root  4096 2009-04-24 14:35 cacert.org
drwxr-xr-x  2 root root  4096 2009-04-24 14:35 debconf.org
-rw-r--r--  1 root root  1220 2009-06-24 11:57 example.selfip.com.crt
drwxr-xr-x  2 root root  4096 2009-04-24 14:35 gouv.fr
drwxr-xr-x  2 root root 12288 2009-04-24 14:35 mozilla
drwxr-xr-x  2 root root  4096 2009-04-24 14:35 quovadis.bm
drwxr-xr-x  2 root root  4096 2009-04-24 14:35 signet.pl
drwxr-xr-x  2 root root  4096 2009-04-24 14:35 spi-inc.org
drwxr-xr-x  2 root root  4096 2009-04-24 14:35 telesec.de

Change to “/etc” directory and edit the file “ca-certificates.conf”.

root@ubuntu:/usr/share/ca-certificates# cd /etc
root@ubuntu:/etc# nano ca-certificates.conf

Add “example.selfip.com.crt” to the file and save it.

-------------------------------------------------------------------------------
  GNU nano 2.0.9                         File: ca-certificates.conf

# This file lists certificates that you wish to use or to ignore to be
# installed in /etc/ssl/certs.
# update-ca-certificates(8) will update /etc/ssl/certs by reading this file.
#
# This is autogenerated by dpkg-reconfigure ca-certificates.
# Certificates should be installed under /usr/share/ca-certificates
# and files with extension '.crt' is recognized as available certs.
#
# line begins with # is comment.
# line begins with ! is certificate filename to be deselected.
#
example.selfip.com.crt
brasil.gov.br/brasil.gov.br.crt
cacert.org/cacert.org.crt
cacert.org/class3.crt
.
.
.
-------------------------------------------------------------------------------

Execute the program “update-ca-certificates –fresh”.
Note: You might like to backup /etc/ssl/certs before executing the command.

root@ubuntu:/etc# update-ca-certificates --fresh
Clearing symlinks in /etc/ssl/certs...done.
Updating certificates in /etc/ssl/certs....done.
Running hooks in /etc/ca-certificates/update.d....done.

Test with curl on your target HTTPS site and it should work now.

root@ubuntu:/etc# curl https://example.selfip.com
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
.
.
.

Excellent References:
http://www.madboa.com/geek/openssl/#verify-system
http://manpages.ubuntu.com/manpages/karmic/man8/update-ca-certificates.8.html

Comments (13)

Zend Framework: Clearing/Resetting HeadLink, HeadMeta, HeadScript, HeadStyle and HeadTitle

Scenario

Assuming your application is running smoothly and all of the HeadX (Refers to HeadLink, HeadMeta, HeadScript, HeadStyle and HeadTitle collectively) have been setup. Suddenly, an exception occurs and your ErrorController has been activated.

You need to use the HeadX to render your error page but those HeadX needs to be cleared of all the previous unwanted setup before we can start using them to render the page. Somehow, there is no clear-related method in the HeadX to do this simply. If this situation, or somewhat related, applies to you, maybe the following article is for you.

Some background

You can skip this part actually. But if you are interested in the technical details, you can continue reading. This post is written based on Zend Framework v1.7.4.

Zend_View_Helper_Placeholder_Container_Standalone class

If you were to open up the Zend/View/Helper/HeadX.php, you will find that those classes actually extend Zend_View_Helper_Placeholder_Container_Standalone class (We shall term it as Standalone class from now on).

Zend_View_Helper_Abstract (implements . . .)
 +- Zend_View_Helper_Placeholder_Container_Standalone (implements . . .)
     |- Zend_View_Helper_HeadLink
     |- Zend_View_Helper_HeadMeta
     |- Zend_View_Helper_HeadScript
     |- Zend_View_Helper_HeadStyle
     +- Zend_View_Helper_HeadTitle

In this Standalone class, there are a few protected variables that are of interest to us as follows.

  1. $_container: This variable holds a reference to an instance of Zend_View_Helper_Placeholder_Container_Abstract class, which essentially is an extended ArrayObject class.

    ArrayObject
     +- Zend_View_Helper_Placeholder_Container_Abstract
         +- Zend_View_Helper_Placeholder_Container
    
  2. $_registry: This variable holds a reference to an instance of Zend_View_Helper_Placeholder_Registry class, which is sort of a Singleton using Zend_Registry.
  3. $_regKey: This variable is overridden by each of the respective HeadX classes and is assigned a unique key, in this case, the class name itself. You can open up the individual Zend/View/Helper/HeadX.php to find this key.

I have extracted the relevant code from the Standalone class as follows. As you can see with reference to the above mentioned protected variables, the methods are actually quite simple and the important method here is __construct. It is using a common placeholder registry to obtain a container reference for the respective HeadX class that we are using.

Using HeadLink as an example, you will find $_regKey is assigned with the key ‘Zend_View_Helper_HeadLink’ in Zend/View/Helper/HeadLink.php. Essentially, this key will retrieve a unique common container reference purely used by the HeadLink class.

public function __construct()
{
    $this->setRegistry(Zend_View_Helper_Placeholder_Registry::getRegistry());
    $this->setContainer($this->getRegistry()->getContainer($this->_regKey));
}

public function setRegistry(Zend_View_Helper_Placeholder_Registry $registry)
{
    $this->_registry = $registry;
    return $this;
}

public function getRegistry()
{
    return $this->_registry;
}

public function setContainer(Zend_View_Helper_Placeholder_Container_Abstract $container)
{
    $this->_container = $container;
    return $this;
}

public function getContainer()
{
    return $this->_container;
}

Zend_View_Helper_Placeholder_Registry class

From Zend_View_Helper_Placeholder_Registry class, I have extracted the relevant code used by Standalone class constructor. As you can see, the $_regKey in Standalone class is used to retrieve an unique instance of the container in getContainer method. And if container is not created yet, a new instance will be created by createContainer method. The container will be an instance of Zend_View_Helper_Placeholder_Container which extends Zend_View_Helper_Placeholder_Container_Abstract.

protected $_containerClass = 'Zend_View_Helper_Placeholder_Container';

public function getContainer($key)
{
    $key = (string) $key;
    if (isset($this->_items[$key])) {
        return $this->_items[$key];
    }

    $container = $this->createContainer($key);

    return $container;
}

public function createContainer($key, array $value = array())
{
    $key = (string) $key;

    $this->_items[$key] = new $this->_containerClass(array());
    return $this->_items[$key];
}

Zend_View_Helper_Placeholder_Container class

At this point, we now know placeholder registry is the central repository for the containers and Standalone class retrieves the reference from it. We also know that the container is an instance of Zend_View_Helper_Placeholder_Container class, which exposes methods from both it’s _Abstrast parent class and ArrayObject class.

If you were to open up the Zend/View/Helper/HeadX.php files, you will find that operations (such as append, prepend) on the container, returned by getContainer method, is performed using methods exposed by Zend_View_Helper_Placeholder_Container class.

However, inside Zend/View/Helper/HeadTitle.php, you will find that it did not use getContainer method at all and we are able to call set, append and prepend on the instance of Standalone class itself, which does not have those methods. The trick to this is the following __call method in the Standalone class, which indirectly exposes container methods as well.

public function __call($method, $args)
{
    $container = $this->getContainer();
    if (method_exists($container, $method)) {
        $return = call_user_func_array(array($container, $method), $args);
        if ($return === $container) {
            // If the container is returned, we really want the current object
            return $this;
        }
        return $return;
    }

    require_once 'Zend/View/Exception.php';
    throw new Zend_View_Exception('Method "' . $method . '" does not exist');
}

So with the above __call method, calling set, append and prepend methods, which does not exist in Standalone class, will automatically route to the container inside. This will mean that all methods expose by Zend_View_Helper_Placeholder_Container_Abstract and ArrayObject can be invoked on Standalone class itself.

The solution

If you follow the technical stuff above, you will understand that the ArrayObject methods are exposed on the HeadX classes as well. So to clear/reset HeadX, we just need the following line in .phtml file. It’s just that simple. :)

$this->headX()->exchangeArray(array())

You might like to read up on a related post on clearing ArrayObject for more information.

=D As usual, if you have any comments or if there is any better way of doing this, remember to share them with me.

Comments (6)

PHP: Clear/Delete/Remove operations on ArrayObject class

There wasn’t much information available on the Internet by keyword searching on how to do clearing/deleting/removing operations on PHP ArrayObject class so I decided to contribute a small post on this topic and hopefully it will save some time for people researching on it.

As of 7 February 2009, the PHP: ArrayObject – Manual did not document two very important methods in the class: exchangeArray() and getArrayCopy(). So I do agree with some post I came across that ArrayObject in not very well documented and the methods should be included there.

Sunstorm Labs Blog has actually written a very interesting article on the topic of ArrayObject so you might like to read it up. Some foundation here is actually based on that article and we will just concentrate on the operations mentioned in the title.

exchangeArray() and getArrayCopy() methods

You can’t really apply array functions directly on the ArrayObject instance. In order to use them, you need to do it as follows.

$arrayObject = new ArrayObject(array(1, 2, 3));
$array = $arrayObject->getArrayCopy();
$array = array_function($array);
$arrayObject->exchangeArray($array);

Clearing ArrayObject

This can be done using the following line. Simple and straight forward, isn’t it? :)

$arrayObject->exchangeArray(array());

Deleting/Removing value from ArrayObject

This is where the exchangeArray() and getArrayCopy() methods come into action. We will apply array_splice function for the deleting/removal action.

$arrayObject = new ArrayObject(array(1, 2, 3));
$array = $arrayObject->getArrayCopy();
array_splice($array, . . .);
$arrayObject->exchangeArray($array);
=D If there is any better way of doing this, remember to share them with me.

Excellent References:
http://www.php.net/manual/en/class.arrayobject.php
http://www.php.net/~helly/php/ext/spl/classArrayObject.html
http://www.php.net/manual/en/book.array.php
http://www.sunstormlabs.net/blog/2008/02/29/what-good-are-arrayobjects/

Comments (6)

Zend Framework: Extending Decorators Flexibility (Part 2)

In Part 1, we are able to split the content into parts and apply decorators on them separately. This can be achieved by using the following classes. I guess the render method of each class pretty much sums up what they do so I will not explain further.

<?php

/**
 * @see Zend_Form_Decorator_Abstract
 */
require_once 'Zend/Form/Decorator/Abstract.php';

/**
 * This class will store the content into this instance during rendering. You
 * can retrieve the stored content using the complementing class
 * App_Form_Decorator_Retrieve. When adding several instances of this decorator,
 * you will have to alias it. The alias given will be used as the name option of
 * the App_Form_Decorator_Retrieve decorator.
 *
 * Accepts the following option:
 * - clear: whether to clear the passed in content by returning an empty content.
 *
 * Add the following line to the init() function of the Zend_Form-extended class
 * and use 'Store' to instantiate this decorator class.
 *
 * $this->addPrefixPath('App_Form_Decorator', 'App/Form/Decorator', Zend_Form::DECORATOR);
 *
 * @category   App
 * @package    App_Form_Decorator
 */
class App_Form_Decorator_Store extends Zend_Form_Decorator_Abstract
{
    /**
     * @access protected
     * @var boolean
     */
    protected $_clear = null;
    /**
     * @access protected
     * @var string
     */
    protected $_content = null;

    /**
     * Specifies whether to clear the passed in content after it is stored in
     * this instance.
     *
     * @param  boolean  $clear  True if to clear the passed in content.
     * @return  App_Form_Decorator_Store  For method chaining.
     * @throws  App_Form_Decorator_Exception  If parameter is not valid.
     */
    public function setClear($clear)
    {
        if (!is_bool($clear))
        {
            require_once 'App/Form/Decorator/Exception.php';
            throw new App_Form_Decorator_Exception('Clear option is not defined');
        }

        $this->_clear = $clear;

        return $this;
    }

    /**
     * Returns whether to clear the passed in content after it is stored in
     * this instance.
     *
     * @return  boolean  True if to clear the passed in content.
     * @throws  App_Form_Decorator_Exception  If 'clear' option is not valid.
     */
    public function getClear()
    {
        if (is_null($this->_clear))
        {
            if (!is_null($clear = $this->getOption('clear')))
            {
                $this->setClear($clear);
                $this->removeOption('clear');
            }
            else
                $this->setClear(true);
        }

        return $this->_clear;
    }

    /**
     * Clears content stored in this instance.
     *
     * @return  App_Form_Decorator_Store  For method chaining.
     */
    public function clearContent()
    {
        $this->_content = null;

        return $this;
    }

    /**
     * Returns content stored in this instance.
     *
     * @param  boolean  $clear  True if to clear stored content in this instance.
     * @return  string  Stored content.
     */
    public function getContent($clear = false)
    {
        $content = $this->_content;

        if (is_null($content))
            $content = '';
        elseif ($clear)
            $this->clearContent();

        return $content;
    }

    /**
     * Store the passed in content for retrieval.
     *
     * @param  string  $content  Passed in content to store.
     * @return  string  Depending on the clear option, it is either empty content or as is.
     */
    public function render($content)
    {
        // Store the passed in content to this instance.
        $this->_content = $content;

        // Clear or return content.
        return ($this->getClear()) ? '' : $content;
    }
}
<?php

/**
 * @see Zend_Form_Decorator_Abstract
 */
require_once 'Zend/Form/Decorator/Abstract.php';

/**
 * This class will retrieve the stored content from complementing class
 * App_Form_Decorator_Store. When adding several instances of this decorator,
 * you will have to alias it. The alias is purely used to ensure the instances
 * will not overwrite each other. To retrieve the stored content, you will need
 * to pass the alias used in instantiating the App_Form_Decorator_Store
 * decorator to the 'name' option.
 *
 * Accepts the following options:
 * - name: The alias used when creating the App_Form_Decorator_Store instance.
 * - remove: Boolean value determining whether to remove the stored content
 *           after retrieval.
 * - separator: string with which to separate passed in content and retrieved
 *              content.
 * - placement: whether to append, prepend or replace with the retrieved
 *              content.
 *
 * Add the following line to the init() function of the Zend_Form-extended class
 * and use 'Retrieve' to instantiate this decorator class.
 *
 * $this->addPrefixPath('App_Form_Decorator', 'App/Form/Decorator', Zend_Form::DECORATOR);
 *
 * @category   App
 * @package    App_Form_Decorator
 */
class App_Form_Decorator_Retrieve extends Zend_Form_Decorator_Abstract
{
    /**
     * @access protected
     * @var string
     */
    protected $_name = null;
    /**
     * @access protected
     * @var boolean
     */
    protected $_remove = null;

    /**
     * Returns stored content from the App_Form_Decorator_Store instance.
     *
     * @param  boolean  $clear  True if to clear stored content after retrieval.
     * @return  string  Stored content.
     * @throws  App_Form_Decorator_Exception  If wrong decorator instance is retrieved.
     */
    protected function _getContent($clear)
    {
        $store = $this->getElement()->getDecorator($this->getName());

        if (!$store instanceof App_Form_Decorator_Store)
        {
            require_once 'App/Form/Decorator/Exception.php';
            throw new App_Form_Decorator_Exception('"' . $this->getName() . '" is not a App_Form_Decorator_Store decorator');
        }

        return $store->getContent($clear);
    }

    /**
     * Specifies the name/alias of the element decorator to retrieve.
     *
     * @param  string  $name  Name/alias of the element decorator to retrieve.
     * @return  App_Form_Decorator_Retrieve  For method chaining.
     * @throws  App_Form_Decorator_Exception  If parameter is not valid.
     */
    public function setName($name)
    {
        if (!is_string($name))
        {
            require_once 'App/Form/Decorator/Exception.php';
            throw new App_Form_Decorator_Exception('Element decorator name is not defined');
        }

        if (is_numeric($name))
        {
            require_once 'App/Form/Decorator/Exception.php';
            throw new App_Form_Decorator_Exception('Element decorator name must be alphanumeric');
        }

        $this->_name = $name;

        return $this;
    }

    /**
     * Returns name/alias of the element decorator to retrieve.
     *
     * @return  string  Name/alias of the element decorator to retrieve.
     * @throws  App_Form_Decorator_Exception  If 'name' option is not valid.
     */
    public function getName()
    {
        if (is_null($this->_name))
        {
            if (is_null($name = $this->getOption('name')))
            {
                require_once 'App/Form/Decorator/Exception.php';
                throw new App_Form_Decorator_Exception('Element decorator name is not defined in the options');
            }

            $this->setName($name);
            $this->removeOption('name');
        }

        return $this->_name;
    }

    /**
     * Specifies whether to remove the stored content in
     * App_Form_Decorator_Store instance after retrieval.
     *
     * @param  boolean  $remove  True if remove the stored content after retrieval.
     * @return  App_Form_Decorator_Store  For method chaining.
     * @throws  App_Form_Decorator_Exception  If parameter is not valid.
     */
    public function setRemove($remove)
    {
        if (!is_bool($remove))
        {
            require_once 'App/Form/Decorator/Exception.php';
            throw new App_Form_Decorator_Exception('Remove option is not defined');
        }

        $this->_remove = $remove;

        return $this;
    }

    /**
     * Returns whether to remove the stored content in
     * App_Form_Decorator_Store instance after retrieval.
     *
     * @return  boolean  True if remove the stored content after retrieval.
     * @throws  App_Form_Decorator_Exception  If 'remove' option is not valid.
     */
    public function getRemove()
    {
        if (is_null($this->_remove))
        {
            if (!is_null($remove = $this->getOption('remove')))
            {
                $this->setRemove($remove);
                $this->removeOption('remove');
            }
            else
                $this->setRemove(true);
        }

        return $this->_remove;
    }

    /**
     * Retrieves the stored content.
     *
     * @param  string  $content  Passed in content.
     * @return  string  Rendered content.
     */
    public function render($content)
    {
        switch ($this->getPlacement())
        {
            case self::APPEND:
                return $content . $this->getSeparator() . $this->_getContent($this->getRemove());

            case self::PREPEND:
                return $this->_getContent($this->getRemove()) . $this->getSeparator() . $content;

            default:
                return $this->_getContent($this->getRemove());
        }
    }
}

And finally, the App_Form_Decorator_Exception class for completeness.

<?php

/**
 * @see Zend_Exception
 */
require_once 'Zend/Exception.php';

/**
 * @category   App
 * @package    App_Form_Decorator
 */
class App_Form_Decorator_Exception extends Zend_Exception
{
}

To use them, you just need to put these three classes into a folder App/Form/Decorator. You need to add this folder to the include_path similar to what you did for Zend library. Remember to enable autoloading using the Zend_Loader class. Other information can be found in the class remarks.

=D If there is any optimization or bug, please feel free to point them out so that I can update my copy as well.
oh Oh… One more thing, you can also used the same concept to create decorator that does formatting, such as indentation, of the rendered content if you haven’t thought of that.

Leave a Comment

Zend Framework: Extending Decorators Flexibility (Part 1)

During form rendering, the decorators are executed in the order added and rendering are done on the same content throughout. If you were to open up Zend/Form.php, you will find inside an elegant render method as follows that summarizes what I’ve mentioned earlier.

public function render(Zend_View_Interface $view = null)
{
    if (null !== $view) {
        $this->setView($view);
    }

    $content = '';
    foreach ($this->getDecorators() as $decorator) {
        $decorator->setElement($this);
        $content = $decorator->render($content);
    }
    return $content;
}

Here comes the issue. We cannot apply decorators selectively to a part of the content by breaking it up. You can only prepend, append or wrap the whole, not portion of, content with a new decorator. This poses a challenge if you need to create custom tags around both Label and Text Field in a Zend_Form_Element_Text for example.

If you, like me, have been doing lots of research on Zend Form Decorators and is hitting the wall, I guess the following might be a solution that will give flexibility to the way decorators can be used. I hope this will help someone in a way or another.

Before I start presenting the possible solution, let me go through what we want to achieve, what we can achieve using the closest available method on the Internet and my proposed solution that introduces flexibility to what we want to achieve.

What we want to achieve

As an example, here’s what we want to achieve, a class attribute of value label for the first td tag on line 2. This will allow us to style it with CSS class label.

<tr>
    <td class="label">
        <label for="username" class="optional">Username:</label>
    </td>
    <td class="element">
        <input type="text" name="username" id="username" value="" />
    </td>
</tr>

What we can achieve using the closest available method

Here are the decorators applied to Zend_Form_Element_Text instance. On line 5, we have added an option ‘class’ => ‘label’ to the Label decorator.

$element->setDecorators(array(
    'ViewHelper',
    'Errors',
    array(array('data' => 'HtmlTag'), array('tag' => 'td', 'class' => 'element')),
    array('Label', array('tag' => 'td', 'class' => 'label')),
    array(array('row' => 'HtmlTag'), array('tag' => 'tr')),
));

The following is achieved using the above code. As you can see, class label is applied to label tag on line 3, and not td tag on line 2. This is not what we want.

<tr>
    <td>
        <label for="username" class="label optional">Username:</label>
    </td>
    <td class="element">
        <input type="text" name="username" id="username" value="" />
    </td>
</tr>

Proposed solution

We will create two new decorator class: Store and Retrieve. It will be used as follows.

$username->setDecorators(array(
    // Create element and store it.
    'ViewHelper',
    'Errors',
    array(array('td-element' => 'HtmlTag'), array('tag' => 'td', 'class' => 'element')),
    array(array('store-element' => 'Store'), array('clear' => true)),
    
    // Create label and store it.
    'Label',
    array(array('td-label' => 'HtmlTag'), array('tag' => 'td', 'class' => 'label')),
    array(array('store-label' => 'Store'), array('clear' => true)),
    
    // Retrieve both the label and element.
    array(array('retrieve-label' => 'Retrieve'), array('name' => 'store-label', 'remove' => true)),
    array(array('retrieve-element' => 'Retrieve'), array('name' => 'store-element', 'remove' => true)),
    
    // Wrap the retrieved label and element with tr tag.
    array(array('row' => 'HtmlTag'), array('tag' => 'tr')),
));

The following is achieved using the above code as what we have set out initially. We are now able to work on part of the content at a time.

<tr>
    <td class="label">
        <label for="username" class="optional">Username:</label>
    </td>
    <td class="element">
        <input type="text" name="username" id="username" value="" />
    </td>
</tr>

In Part 2, we will present the two newly derived decorator class.

Excellent References:
http://framework.zend.com/manual/en/zend.form.standardDecorators.html
http://devzone.zend.com/article/3450-Decorators-with-Zend_Form
http://zendguru.wordpress.com/2008/10/23/zend-form-decorators/

Comments (1)

Follow

Get every new post delivered to your Inbox.