Friday, July 26, 2013

Escaping Zend_Form Error Element Labels in ZF1

Problem:
You have a form element label with extra characters (e.g. *, ", :, etc...) and you want to escape or remove it in the generated error message.

Example Zend Form Element:

$firstName = new Zend_Form_Element_Text('first_name');
$firstName->class = "size_long";
$firstName->setLabel('* First Name:')
          ->setRequired(true)
          ->ddFilters(array('StringTrim', 'StripTags'))
          ->setDecorators(array(
                array('ViewHelper', array('helper' => 'formText')),
                array('Label', array('class' => 'label')),
                array('HtmlTag', array('tag' => 'div', 'class' => 'el_wrapper')),
          ));

Notice the setLabel() method string have characters "*" and ":"
These characters will show up in your error UL labels that are generated by Zend_Form_Decorator_FormErrors.

Solution:
In your controller when an error is generated...

// assume $form is an instance of Zend_Form
if (!$form->isValid($_POST)) {
    $view = $form->getView();
    $escape = function ($string) use ($view) {
        $string = htmlspecialchars($string, ENT_COMPAT, $view->getEncoding());
        $string = str_replace(array('#', ':', '*'), '', $string);
        return trim($string);
    };
    $view->setEscape($escape);
}

This will override the default "htmlspecialchars" escaping of Zend_Form that is being used in the rendering of form elements.

Thursday, July 25, 2013

How ZF2's SharedEventManager Identifies Which Class to Listen to.

This is documented in the ZF2 official docs but I just want to save my thoughts while I was messing with the EventManager.

Problem: I was trying to grab an EventManager instance from 2 different modules.

Solution/Explanation:
The Zend\EventManager\EventManager class in ZF2 is not a singleton so EVERY module has its own instance of the EventManager. Therefore you cannot trigger events from other modules!
 
That is where the Zend\EventManager\SharedEventManager comes to the rescue although it itself is not a singleton you may ask but it is managed by Zend\EventManager\StaticEventManager which is one.

Confusion:
// Listener
$eventManager->getSharedManager()->attach('TriggerClass', 'shout', function ($e) {
    // do something
});
vs
// Listener
$eventManager->attach('shout', function ($e) {
    // do something

});
Please notice the extra argument of attach() TriggerClass. That is how the Shared Event Manager can find which shout event to trigger.

You may asked though how can the ShareEventManager find the event by just passing a string to attach() when the target trigger just reads like this:
class TriggerClass {
    public function shout()
    {
        $this->eventManager->trigger('shout', $this, array('Hey you!')); // notice i am NOT attaching to the SharedEventManager
    }
}
Sure it maps it using the same classname (although it does not matter) named TriggerClass but what if I want to use a different id like this one below to my listener and still want to trigger the same event?
// Listener
$eventManager->getSharedManager()->attach('Blah', 'shout', function ($e) {
    // do something
});
This is where the setIdentifiers() comes in. Back when implementing the setEventManager() of EventManagerInterface set the identifiers like below:
    public function setEventManager(EventManager\EventManagerInterface $events)
    {
        $events->setIdentifiers(array(
            'Blah'
        ));

        $this->events = $events;
        return $this;
    }
Now you can have a listener:
$this->getEventManager()->getSharedManager()->attach('Blah' ,'shout', function ($e) {
    var_dump($e->getParams());
    var_dump($e->getTarget());
 });
That is how the SharedEventManager maps out which Event to trigger.

Tuesday, July 23, 2013

Creating A Project in Composer Using a Specific Version

You want to use a specific 2.3.* develop version found in:
https://packagist.org/packages/zendframework/zendframework

You would do:
php composer.phar create-project zendframework/zendframework=2.3.x-dev /your/path/
or
php composer.phar create-project zendframework/zendframework /your/path/ 2.3.x-dev

Or you want to just use the develop branch:
php composer.phar create-project zendframework/zendframework /your/path/ dev-develop

Warning: beware of this method though. If the package author changes version and they do explicit branching, when you do composer update your update will fail! Further reading to fix this problem through branch aliasing (https://igor.io/2013/01/07/composer-versioning.html)

Reference:
https://github.com/composer/composer/issues/957
http://getcomposer.org/doc/03-cli.md#create-project

Monday, July 22, 2013

Overriding the ZF1 Zend_Form Validator's setRequired message

The Zend_Form_Element's isRequired() method uses the Zend_Validate_NotEmpty validator under the hood and thus uses its message template for outputs.

For simple Zend_Form_Element_Text override you can just do this:

$e = new Zend_Form_Element_Text('text_element');
$e->setRequired(true) // needs to be true
  ->addValidators(array('NotEmpty')); // needs to be explicitly set
$e->getValidator('NotEmpty')
  ->setMessage(
      'This is my new message that you entered an empty string!',
      Zend_Validate_NotEmpty::IS_EMPTY
  );

For Multi elements though, you need to set the NotEmpty's breakChainOnFailure to true as below:

$e = new Zend_Form_Element_MultiCheckbox('text_element');
$e->setRequired(true); // needs to be true
  ->addValidators(array(
      array('NotEmpty', true) // notice "true" is set for breakChain option
  ));

$e->getValidator('NotEmpty')
  ->setMessage(
      'This is my new message that you entered an empty string!',
      Zend_Validate_NotEmpty::IS_EMPTY
  );

Friday, July 12, 2013

Awesome Example of ZF1 Zend_Form Rendering With Groupings and Fieldsets

http://zendgeek.blogspot.com/2009/07/zend-form-display-groups-decorators.html

Using An Existing Method on a Mocked Object

Problem: You want to use an existing method in a class for unit testing in a mocked object but cannot use it because the method is returning an error when called.

Example:
class A
{
    public function methodA()
    {
        return "Hello";
    }
}

Unit test:
$mock = $this->getMockBuilder('A')
             ->getMock();
$mock->methodA(); // returns an error

Solution: 
$mock = $this->getMockBuilder('A')
             ->setMethod(null) // the trick is explicitly using a null
             ->getMock();
$mock->methodA(); // returns "Hello"

For the curious, what will happen if you pass an empty array:
$mock = $this->getMockBuilder('A')
             ->setMethod(array())
             ->getMock();
$mock->methodA(); // returns null

You shouldnt really do this at all because you can just instantiate class A after but its just a good thing to know!

Friday, July 5, 2013

JQuery UI Model with submit form on confirm

I was having frustrating problem using jquery's submit button inside a Jquery ui modal box when a user submit. the problem occurs when you explicitly set the submit() to return false that you cannot do anything after it even explicitly saying submit(function (){return true;})

http://www.jensbits.com/2009/08/10/modal-confirmation-dialog-on-form-submit-javascript-jquery-ui-and-thickbox-varieties/

solution is to NOT use the submit function at all after confirm:

    $( "#dialog-confirm" ).dialog({
        resizable: false,
        autoOpen: false,
        height: 140,
        modal: true,
        buttons: {
            "Delete pendings": function() {
                document.scheduled_release_form.submit();
            },
            Cancel: function() {
                $( this ).dialog("close");
            }
        }
    });

<form action="" method="post" name="scheduled_release_form" id="scheduled_release_form">
<input type="button" name="submit_update" value="submit update" id="submit_update"><input type="button" name="submit_delete" value="delete pendings" id="submit_delete">
</form>

Mac vagrant port forwarding

Just reposting this blog for reference.

http://www.dmuth.org/node/1404/web-development-port-80-and-443-vagrant

If you have a vagrant instance that is running on 8080 for web services and you want to forward 80 traffic to it so you will not be appending ports to your url like http://localhost:8080 but instead just use http://localhost do the following bellow:

sudo ipfw add 100 fwd 127.0.0.1,8080 tcp from any to me 80
sudo ipfw add 101 fwd 127.0.0.1,8443 tcp from any to me 443