Showing posts with label captchas. Show all posts
Showing posts with label captchas. Show all posts

Wednesday, October 21, 2009

Advanced Zend_Form usage

Zend_Form is the component of Zend Framework that I enjoy the most: it implements reusable forms and inputs as classes and objects, with automatic reinsertion of values during server-side validation; validation that is shared among all the instances and is provided out of the box by Zend_Validate. If you have ever duplicated a form for editing and adding a new entity to your application, or have felt the pain to manually populate text inputs, you now know being able to reuse a form is a killer feature and in fact many php frameworks provide a form library.
Today I have gathered from my experience some know-how I have learnt while taking the Zend_Form component to its limits.

Ignored elements
Every Zend_Form_Element instance has a method setIgnore(). Also you can pass a flag 'ignore' equal to True in the $options array parameter in the constructor, respecting the framework convention, to obtain the same result.
The purpose of this option is to exclude the value assumed by this input from the result returned by getValues(), even if you pass to isValid() the entire POST request (which contains a value for this specific element since it was present on the client side and filled for other purposes). This option comes handy when using Zend_Form_Element_Captcha or Zend_Form_Element_Submit.
$button = new Zend_Form_Element_Submit('submitButton', array('ignore' => true, 'label' => 'Send!'));

Decorators
Both elements and forms (descendants of Zend_Form_Element and Zend_Form respectively) support a stack of decorators used for rendering themselves. The first decorator, ViewHelper, calls a view helper defined by the element or form to produce the basic html and this string result is passed to the subsequent decorators in a chain, each of them working on the previous one output.
This design is an unconventional implementation of the Decorator Pattern, and allow decorators to be reused troughout every kind of form element. The standard decorators cover a vast variety of cases and allow you to produce custom html for your forms. You can even write your own ones by extending Zend_Form_Decorator_Abstract.
$element = new Zend_Form_Element_Text();
$element->addDecorators(array(
    'ViewHelper',
    'Errors',
    array('Description', array('tag' => 'div', 'class' => 'description')),
    array('HtmlTag', array('tag' => 'dd')),
    array('Label', array('tag' => 'dt'))
));

Subforms
Subforms are instances of Zend_Form which are incorporated in a parent form, providing reusability of logic groups of elements. Generally I prefer to use Zend_Form instances with the adequate decorators instead of Zend_Form_SubForm ones, which is also a subclass of it.
Not only you can reutilize forms as fieldsets of a bigger one, but you can encanpsulate the values of this subform's elements transforming the main result in a recursive array.
        $form->setDecorators(array(
                    'FormElements',
                    array('HtmlTag', array('tag' => 'dl')),
                    'FieldSet');
$form->setElementsBelongTo('my_key'); // call this after all elements have already been inserted
// ...
if ($form->isValid($postData)) {
    $values = $form->getValues();
    // $values['my_key'] is an array containing the values of subform's elements
}

Dojo
The Zend_Dojo component contains form elements which extends or substitutes the standard Zend_Form ones, augmenting their capabilities with the Dojo Toolkit widgets.
To set up the right plugin path, simply make your form a subclass of Zend_Dojo_Form instead of Zend_Form, or pass the form instance to the Zend_Dojo::enableForm() static method. Also make sure you are outputting the $this->dojo() view helper in your layout or view script, to print the mandatory script tags.
For instance you can create a real-time filtered select:
$element = $form->createElement('FilteringSelect', 'nameOfelement');
$element->setMultiOptions(array('yellow' => 'Light Yellow', 'blue'   => 'Blue', ...);
This type of elements can also work with a remote data store, just in case you have one million select options and you want to lazy load them. There is even a configurable WYSIWYG editor in the dojo component, available via CDN without having to upload a single js file to your server.

Captchas
With Zend_Form_Element_Captcha, you can create different types of captcha to insert in your forms for the sake of stopping spammers bots to submit them. A captcha not correctly answered will invalidate the form result during the call to Zend_form::isValid(). Though you can specify ascii art as captchas and even on-the-fly generated images, this is the fastest way to include a captcha in a form:
$element = new Zend_Form_Element_Captcha();
$element->setCaptcha('Dumb')
              ->setIgnore(true);
You may be worried about how to automatically test the submit of a form which contains a captcha, since it is built with the purpose of avoid being answered by a machine like your test runner. However, there is a trick that accesses session variables to find the right answer for the captcha during the test method run.

I hope this quick panoramic of functionalities satisfies you. Zend_Form is a complex component but the learning curve is really worth the benefits it gives to your applications. You can even use it in isolation, without the ZF Mvc stack, since the only requirement is a Zend_View object that every element and form instance needs to render itself.
With Zend_Dojo_Form not even uploading javascript plugins is needed for improving the user experience: dojo is loaded remotely via Google or Aol servers. Zend_Form is a complete open source, liberally licensed, object-oriented solution for managing forms and inputs, the most powerful choice I have ever seen in php and one of the top components of Zend Framework.

Saturday, July 04, 2009

Zend_Test and captchas

Do you want test automation for a Zend Framework application that contains captchas?
Zend_Test is a useful component that permits stubbing of http request parameters, methods and headers to allow integration testing of an application. It works by dispatching urls and requests and asserting that the response contains the necessary data and http headers.
This testing automation tool shows problems when encounter a captcha field. Since it is built to prevent doing automatic submission of forms, not being capable of automate the test is a good sign that the captcha generation code is well written.
However, no matter which captcha adapter a form is using, there's a workaround that permits a test to access the captcha value while hidden it from an end user. This trick access $_SESSION variable.

Architecture of Zend_Captcha
A Zend_Form_Element_Captcha uses a Zend_Captcha_Adapter_* instance, that when generating a couple of (id,input) saves the input in a namespace of the session superglobal and sends to the element for rendering only id; an empty text input is added by the element.
So the workflow is viewing a form, extracting the captcha id, pull up from $_SESSION the right input value and submit the form like we were human capable of deciphering it - err, we are human, but phpunit's not.

Here's the code
Add this method in your test case class, or wherever you want, since it has no dependencies. Only requirement is that it must be callable from a Zend_Test_PHPUnit_ControllerTestCase instance. The argument is the html of the page containing the form.
public function getCaptcha($html)
{
$dom = new Zend_Dom_Query($html);
$id = $dom->query('#captcha-id')->current()->getAttribute('value');

foreach ($_SESSION as $key => $value) {
if (ereg("Zend_Form_Captcha_(.*)", $key, $regs)) {
if ($regs[1] == $id) {
return array(
'id' => $id,
'input' => $value['word']
);
}
}
}
}

Typical usage. In this example I assume the element is named 'captcha'.
// viewing form for adding comment via ajax
$this->dispatch("/content/article/{$articleSlug}/add?format=html");
$this->assertQuery('form#otk_content_form_comment');

// adding comment
$html = $this->response->getBody();
$this->newRequest();
$this->request
->setMethod('POST')
->setPost(array(
'author' => uniqid(),
'mail' => 'integration_mail@example.com',
'text' => $text,
'suscription' => true,
'captcha' => $captcha = $this->getCaptcha($html)
));
$this->dispatch("/content/article/{$articleSlug}/add");
$this->assertRedirectTo("/content/article/{$articleSlug}/comments");

And now you can cover with integration testing also form with captchas.
Hope you like it! You know, they say if it ain't tested, it's broken...

Featured post

A map metaphor for architectural diagrams

It is a (two-dimension) representation of a pipe. The map is not the territory , but in software engineering terms they are models of it....

Popular posts