Archive for the ‘ Zend Framework ’ Category

Zend_Form_Element_Daterange with jQuery UI datepicker

It’s pretty easy to create a wrapper around Zend_Form_Element_Text to make a datepicker, but creating a good, smart daterange picker is a little more tricky.

The only two requirements are jQuery UI and that you have some way of injecting JavaScript into your site layout (I use a $this->layout()->headJavascript).

Other than that, this should be pretty plug-and-play.

Here are the 3x files you’ll need:
1. Your view script for the actual element (check in here for how I’m dealing with the JavaScript):
/application/views/scripts/misc/form/element/daterange.phtml

2. The actual element
/library/M/Form/Element/Daterange.php

3. And a filter to parse out the actual daterange when you do $form->getValues():
/library/M/Filter/Daterange.php

Have fun!

/library/M/Form/Element/Daterange.php
<?php

class M_Form_Element_Daterange extends Zend_Form_Element_Select {
public function init() {

$this->setDecorators(array(array('ViewScript', array(
'viewScript' => 'misc/form/element/daterange.phtml',
'viewModule' => 'default',
'class' => 'form element'
))));

return parent::init();
}
}

/library/M/Filter/Daterange.php

<?php

class M_Filter_Daterange implements Zend_Filter_Interface {

private $_param = null;

public function __construct($param, $options = array()) {
if (!is_string($param) || strlen($param) < 1) {
throw new Zend_Filter_Exception("param must be a string, and must have a length");
}
$this->_param = $param;
}

/**
* (non-PHPdoc)
* @see Zend_Filter_Interface::filter()
*/
public function filter($val) {
if ($val != 'custom') {
$rangestring = self::stringToRange($val);
if ($rangestring) {
return $rangestring;
}
}
if ($this->_param === null) {
throw new Zend_Filter_Exception("Must instantiate this class and pass a valid parameter");
}
$range = Zend_Controller_Front::getInstance()->getRequest()->getParam($this->_param, false);
$start = strtotime(Zend_Controller_Front::getInstance()->getRequest()->getParam($this->_param . '_start', false));
$end = strtotime(Zend_Controller_Front::getInstance()->getRequest()->getParam($this->_param . '_end', false));

if (!$start || !$end) {
return false;
}
return array(
'start' => date('Y-m-d', $start),
'end' => date('Y-m-d', $end),
);

}

/**
* Converts a known relative date range string to an array of dates, returns false on failure
*
* @param string $datestring
* @return array|bool
*/
static public function stringToRange($datestring) {
switch ($datestring) {
case 'yesterday':
$daterange = array(
'start' => date('Y-m-d', strtotime('yesterday')),
'end' => date('Y-m-d'),
);
break;
case 'weektodate':
$daterange = array(
'start' => date('Y-m-d', strtotime('sunday this week')),
'end' => date('Y-m-d'),
);
break;
case 'lastweek':
$daterange = array(
'start' => date('Y-m-d', strtotime('sunday last week')),
'end' => date('Y-m-d', strtotime('saturday -2 weeks')),
);
break;
case 'monthtodate':
$daterange = array(
'start' => date('Y-m-d', strtotime('first day of this month')),
'end' => date('Y-m-d'),
);
break;
case 'lastmonth':
$daterange = array(
'start' => date('Y-m-d', strtotime('first day of last month')),
'end' => date('Y-m-d', strtotime('last day of last month')),
);
break;
case 'yeartodate':
$daterange = array(
'start' => date('Y-m-d', strtotime('first day of this year')),
'end' => date('Y-m-d'),
);
break;
case 'lastyear':
$daterange = array(
'start' => date('Y-m-d', strtotime('first day of last year')),
'end' => date('Y-m-d', strtotime('last day of last year')),
);
break;
case 'all':
$daterange = array(
'start' => date('Y-m-d', strtotime('1970-01-01')),
'end' => date('Y-m-d'),
);
break;
default:
return false;

}
return $daterange;
}

}

/application/views/scripts/misc/form/element/daterange.phtml

<?php
/**
* Output a daterange element
*
*/

/**
* @var Zend_Form_Element
*/
$element = $this->element;
$opts = $element->getDecorator('ViewScript')->getOptions();

ob_start();
?>
<script>
$(document).ready(function() {
var dates = $(<?=json_encode('#' . $element->getName() . '_start, #' . $element->getName() . '_end')?>).datepicker({
changeMonth : true
, onSelect : function( selectedDate ) {
// don't let end date be after start date
var option = this.id == <?=json_encode($element->getName() . '_start')?> ? "minDate" : "maxDate",
instance = $(this).data("datepicker"),
date = $.datepicker.parseDate(
instance.settings.dateFormat || $.datepicker._defaults.dateFormat
, selectedDate
, instance.settings
);
dates.not(this).datepicker("option", option, date);
}

});

$(<?=json_encode('#' . $element->getName())?>).change(function() {
if ($(this).val() == 'custom') {
$(<?=json_encode('#_custom_daterange_' . $element->getName() . '_start')?>).show();
$(<?=json_encode('#_custom_daterange_' . $element->getName() . '_end')?>).show();
} else {
$(<?=json_encode('#_custom_daterange_' . $element->getName() . '_start')?>).hide();
$(<?=json_encode('#_custom_daterange_' . $element->getName() . '_end')?>).hide();
}
});
});
</script>
<?php
$this->layout()->headJavascript .= ob_get_clean();

?>
<table id="_custom_daterange_<?=htmlentities($element->getName())?>">
<tr>
<td><?=$element->getLabel()?></td>
<td><?php
echo $this->{$element->helper}(
$element->getName(),
$element->getValue(),
$element->getAttribs(),
$element->getMultiOptions()
);?>
</td>
</tr>
<tr style="display:none" id="_custom_daterange_<?=htmlentities($element->getName())?>_start">
<td><label for="<?=htmlentities($element->getName())?>_start">Start: </label></td>
<td><input id="<?=htmlentities($element->getName())?>_start" name="<?=htmlentities($element->getName())?>_start" type="text"></td>
</tr>
<tr style="display:none" id="_custom_daterange_<?=htmlentities($element->getName())?>_end">
<td><label for="<?=htmlentities($element->getName())?>_end">End: </label></td>
<td><input id="<?=htmlentities($element->getName())?>_end" name="<?=htmlentities($element->getName())?>_end" type="text"></td>
</tr>
</table>
<div class="error bold"><?=$this->formErrors($element->getMessages())?></div>
<div class="hint"><?=$element->getDescription()?></div>

Zend_Form_Element_Hidden not really hidden

The point of having a hidden field is that it is truly hidden, and doesn’t require labels / etc. Unfortunately, Zend_Form_Element_Hidden doesn’t really recognise this. Here’s how to subclass it and get rid of that stuff so you can create an input that is hidden for real:

class M_Form_Element_Hidden extends Zend_Form_Element_Hidden {
   public function init() {
      $this->setDisableLoadDefaultDecorators(true);
      $this->addDecorator('ViewHelper');
      $this->removeDecorator('DtDdWrapper');
      $this->removeDecorator('HtmlTag');
      $this->removeDecorator('Label');
      return parent::init();
   }
}

Autoload PhpThumb with Zend Framework

Here’s an easy way to autoload PhpThumb (an excellent & fast image resizing / manipulating PHP library) using Zend Framework without having to modify the PhpThumb source at all. First we need to make our own custom autoloader:

class M_Loader_Autoloader_PhpThumb implements Zend_Loader_Autoloader_Interface {

   static protected $php_thumb_classes = array(
      'PhpThumb'        => 'PhpThumb.inc.php',
      'ThumbBase'       => 'ThumbBase.inc.php',
      'PhpThumbFactory' => 'ThumbLib.inc.php',
      'GdThumb'         => 'GdThumb.inc.php',
      'GdReflectionLib' => 'thumb_plugins/gd_reflection.inc.php',
   );

  /**
   * Autoload a class
   *
   * @param   string $class
   * @return  mixed
   *          False [if unable to load $class]
   *          get_class($class) [if $class is successfully loaded]
   */
   public function autoload($class) {
      $file = APPLICATION_PATH . '/../library/PhpThumb/' . self::$php_thumb_classes[$class];
      if (is_file($file)) {
         require_once($file);
         return $class;
      }
      return false;
   }
}

Then simply put this in your Bootstrap:

Zend_Loader_Autoloader::getInstance()->pushAutoloader(new M_Loader_Autoloader_PhpThumb());

Done. Now you can use PhpThumb in your controllers like this:

class PhotoController extends Zend_Controller_Action {
   public function indexAction() {
      $this->_helper->layout()->disableLayout();
      $this->_helper->viewRenderer->setNoRender(true);
      $thumb = PhpThumbFactory::create("/path/to/image.jpg");
      $thumb->adaptiveResize(250, 250);
      $thumb->show();
   }
}

Script to generate command to push MySQL database changes between enviornments

Got sick of manually doing database dumps between our production, test, and development environments, so I just wrote a very handy script using Zend Framework to generate a command to push changes between environments as you make them.

You will have to tweak ssh_to to suit your needs (our test and production dbs are not separated at the moment, so that removes a layer of complexity for us), but if you have everything setup in application.ini like most people do, this should work almost out of box. You’ll need to setup the autoloader on your own.

usage: php push_db.php --to=dev --from=prod

Enjoy!

#!/usr/bin/env php5
<?php
require_once(getcwd() . '/autoload.php');

$opts = new Zend_Console_Getopt(
   array(
      'from=s'    => 'From environment, with required string parameter',
      'to=s'        => 'To environment, with required string parameter',
   )
);

$from = $opts->getOption('from');
$to = $opts->getOption('to');

foreach (array('from','to') as $env) {
      if (!in_array($$env, array('production','prod','testing','test','development','dev'))) {
         die('Error: invalid parameter ' . $env . " = '{$$env}'" . PHP_EOL);
      }
}

$prod_env = new Zend_Config_Ini(APPLICATION_PATH . '/configs/application.ini', 'production');
$test_env = new Zend_Config_Ini(APPLICATION_PATH . '/configs/application.ini', 'testing');
$dev_env = new Zend_Config_Ini(APPLICATION_PATH . '/configs/application.ini', 'development');

$to_server = array();
$from_server = array();
foreach (array('from','to') as $serv) {
   $server = $serv . '_server';
   switch ($$serv) {
      case 'production':
      case 'prod':
         ${$server}['host'] =                   $prod_env->database->params->host;
         ${$server}['username'] =                   $prod_env->database->params->username;
         ${$server}['password'] =                   $prod_env->database->params->password;
         ${$server}['dbname'] =                   $prod_env->database->params->dbname;
         ${$server}['ssh_to'] = 'me@foobar';
         break;
      case 'testing':
      case 'test':
         ${$server}['host'] = $test_env->database->params->host;
         ${$server}['username'] =                   $test_env->database->params->username;
         ${$server}['password'] =                   $test_env->database->params->password;
         ${$server}['dbname'] =                   $test_env->database->params->dbname;
         ${$server}['ssh_to'] = 'me@foobar';
         break;
      case 'development':
      case 'dev':
         ${$server}['host'] = $dev_env->database->params->host;
         ${$server}['username'] =                   $dev_env->database->params->username;
         ${$server}['password'] =                   $dev_env->database->params->password;
         ${$server}['dbname'] =                   $dev_env->database->params->dbname;
         ${$server}['ssh_to'] = 'me@foobar';
         break;
      }
}

print "Run this command on " . $from . ':' . PHP_EOL;
echo 'mysqldump -u ' . $from_server['username'] . ' -p' . $from_server['password'] . ' ' . $from_server['dbname']
. ' | ssh ' . $from_server['ssh_to'] . ' "mysql -u ' . $to_server['username'] . ' -p' . $to_server['password'] . ' ' . $to_server['dbname'] . '"' . PHP_EOL;