Кэширование с помощью eaccelerator

Не секрет, что популярный фреймворк Zend Framework не поддерживает кеширование через eAccelerator. Я решил исправить это и написал backend eAccelerator для ZF.
Напомню, что eAccelerator, помимо автоматического кеширования, поддерживает пользовательское. Как его использовать, можно найти в официальном мануале.

< ?php
/**
 * @author Aco <aco dot best at gmail dot com> | WebITeam
 */
 
/**
 * @see Zend_Cache_Backend_Interface
 */
require_once 'Zend/Cache/Backend/ExtendedInterface.php';
 
/**
 * @see Zend_Cache_Backend
 */
require_once 'Zend/Cache/Backend.php';
 
/**
 * @package Zend_Cache
 * @subpackage Zend_Cache_Backend
 */
class Zend_Cache_Backend_Eaccelerator extends Zend_Cache_Backend implements Zend_Cache_Backend_ExtendedInterface
{
 /**
 * Log message
 */
 const TAGS_UNSUPPORTED_BY_CLEAN_OF_EA_BACKEND = 'Zend_Cache_Backend_Eaccelerator::clean() : tags are unsupported by the eaccelerator backend';
 const TAGS_UNSUPPORTED_BY_SAVE_OF_EA_BACKEND = 'Zend_Cache_Backend_Eaccelerator::save() : tags are unsupported by the eaccelerator backend';
 
 protected $_options = array();
 
 public function __construct(array $options = array())
 {
 if (!extension_loaded('eaccelerator'))
 {
 Zend_Cache::throwException('The eaccelerator extension must be loaded for using this backend !');
 }
 $funcs = get_extension_funcs('eaccelerator');
 if(!in_array("eaccelerator_get",$funcs) || !in_array("eaccelerator_put",$funcs))
 {
 Zend_Cache::throwException('The eaccelerator extension must be loaded completely for using this backend !');
}
parent::__construct($options);
}
/**
* Save some string datas into a cache record
*
* Note : $data is always "string" (serialization is done by the
* core not by the backend)
*
* @param string $data datas to cache
* @param string $id cache id
* @param array $tags array of strings, the cache record will be tagged by each string entry
* @param int $specificLifetime if != false, set a specific lifetime for this cache record (null => infinite lifetime)
* @return boolean true if no problem
*/
public function save($data, $id, $tags = array(), $specificLifetime = false)
{
$lifetime = $this->getLifetime($specificLifetime);
if (count($tags) > 0)
{
$this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_EA_BACKEND);
}
return eaccelerator_put($id, $data, $lifetime);
}
/**
* Test if a cache is available for the given id and (if yes) return it (false else)
*
* WARNING $doNotTestCacheValidity=true is unsupported by the Eaccelerator backend
*
* @param string $id cache id
* @param boolean $doNotTestCacheValidity if set to true, the cache validity won't be tested
* @return string cached datas (or false)
*/
public function load($id, $doNotTestCacheValidity = false)
{
return eaccelerator_get($id);
}

public function test($id)
{
$tmp = eaccelerator_get($id);
if ($tmp) {
return $tmp;
}
return false;
}

/**
* Remove a cache record
*
* @param string $id cache id
* @return boolean true if no problem
*/
public function remove($id)
{
return eaccelerator_rm($id);
}

/**
* Clean some cache records
*
* Available modes are :
* 'all' (default) => remove all cache entries ($tags is not used)
* 'old' => unsupported
* 'matchingTag' => unsupported
* 'notMatchingTag' => unsupported
* 'matchingAnyTag' => unsupported
*
* @param string $mode clean mode
* @param array $tags array of tags
* @throws Zend_Cache_Exception
* @return boolean true if no problem
*/
public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
{
switch ($mode)
{
case Zend_Cache::CLEANING_MODE_ALL:
return eaccelerator_clear() && eaccelerator_clean();
break;
case Zend_Cache::CLEANING_MODE_OLD:
return eaccelerator_gc();
break;
case Zend_Cache::CLEANING_MODE_MATCHING_TAG:
case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG:
case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG:
$this->_log(self::TAGS_UNSUPPORTED_BY_CLEAN_OF_EA_BACKEND);
break;
default:
Zend_Cache::throwException('Invalid mode for clean() method');
break;
}
}

/**
* Return true if the automatic cleaning is available for the backend
*
* DEPRECATED : use getCapabilities() instead
* 
* @deprecated 
* @return boolean
*/
public function isAutomaticCleaningAvailable()
{
return false;
}

/**
* Return the filling percentage of the backend storage
* 
* @throws Zend_Cache_Exception
* @return int integer between 0 and 100
*/
public function getFillingPercentage()
{
$eaInfo = eaccelerator_info();
if ($eaInfo["memorySize"] == 0) {
Zend_Cache::throwException("can't get eaccelerator memory size");
}
if ($eaInfo["memoryAllocated"] > $eaInfo["memorySize"]) {
return 100;
}
return ((int) (100. * ($eaInfo["memoryAllocated"] / $eaInfo["memorySize"])));
}

/**
* Return an array of stored tags
*
* @return array array of stored tags (string)
*/
public function getTags()
{ 
$this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_EA_BACKEND);
return array();
}

/**
* Return an array of stored cache ids which match given tags
* 
* In case of multiple tags, a logical AND is made between tags
*
* @param array $tags array of tags
* @return array array of matching cache ids (string)
*/
public function getIdsMatchingTags($tags = array())
{
$this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_EA_BACKEND);
return array(); 
}

/**
* Return an array of stored cache ids which don't match given tags
* 
* In case of multiple tags, a logical OR is made between tags
*
* @param array $tags array of tags
* @return array array of not matching cache ids (string)
*/ 
public function getIdsNotMatchingTags($tags = array())
{
$this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_EA_BACKEND);
return array(); 
}

/**
* Return an array of stored cache ids which match any given tags
* 
* In case of multiple tags, a logical AND is made between tags
*
* @param array $tags array of tags
* @return array array of any matching cache ids (string)
*/
public function getIdsMatchingAnyTags($tags = array())
{
$this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_EA_BACKEND);
return array(); 
}

/**
* Return an array of stored cache ids
* 
* @return array array of stored cache ids (string)
*/
public function getIds()
{
return eaccelerator_list_keys();
}

/**
* Return an array of metadatas for the given cache id
*
* The array must include these keys :
* - expire : the expire timestamp
* - tags : a string array of tags
* - mtime : timestamp of last modification time
* 
* @param string $id cache id
* @return array array of metadatas (false if the cache id is not found)
*/
public function getMetadatas($id)
{
return false; 
}

/**
* Give (if possible) an extra lifetime to the given cache id
*
* @param string $id cache id
* @param int $extraLifetime
* @return boolean true if ok
*/
public function touch($id, $extraLifetime)
{

$tmp = eaccelerator_get($id);
if ($tmp !== null)
{
eaccelerator_put($id, $tmp, $extraLifetime);
return true;
}
return false;
}

/**
* Return an associative array of capabilities (booleans) of the backend
* 
* The array must include these keys :
* - automatic_cleaning (is automating cleaning necessary)
* - tags (are tags supported)
* - expired_read (is it possible to read expired cache records
* (for doNotTestCacheValidity option for example))
* - priority does the backend deal with priority when saving
* - infinite_lifetime (is infinite lifetime can work with this backend)
* - get_list (is it possible to get the list of cache ids and the complete list of tags)
* 
* @return array associative of with capabilities
*/
public function getCapabilities()
{
return array(
'automatic_cleaning' => false,
'tags' => false,
'expired_read' => false,
'priority' => false,
'infinite_lifetime' => false,
'get_list' => true
);
}
}
?>

Добавлено: 25 Мая 2018 07:30:56 Добавил: Андрей Ковальчук

Использование FirePHP через Zend Framework

Во время отладки и оптимизации зачастую встает вопрос грамотного профилирования запросов в базу данных, вывод отладочной информации и просто логирования. В таких случаях крайне полезным может оказаться FirePHP, дополнение к небезызвестному FireBug. К счастью разработчики zend позаботились об этом, и нам не надо изобретать велосипед или подставлять костыли для этого. Достаточно использовать расширения стандартных классов логирования и профилирования. <!--more-->Чтобы это сделать, нужно добавить следующее в bootstrap:

$Log = new Zend_Log();
$Writter = new Zend_Log_Writer_Firebug();
$Log -> addWriter($Writter);
Zend_Registry::set('logger');
// Теперь можно в любом месте проекта написать, что-то типа такого:
Zend_Registry::get('logger') -> log('Something wrong', Zend_Log::DEBUG);
$DbAdapter = Zend_Db::factory('MYSQLi', array(/*mysql params*/));
$Profiler = new Zend_Db_Profiler_Firebug();
$Profiler -> setEnabled(true);
$DbAdapter -> setProfiler($Profiler);

Стоит отметить, что на продакшене все это лучше убрать, или отключить. Для логирования можно использовать любой из вариантов предлагаемых фреймворком врайтеров (запись в БД, файлы, в /dev/null), а профилирования отключается установкой setEnabled(false).

Добавлено: 20 Мая 2018 21:40:39 Добавил: Андрей Ковальчук

Валидируем даты

Недавно я узнал, что Zend_Date может использоваться в двух модах iso и php, причём iso используется по умолчанию.

При использовании валидатора Zend_Validate_Date в формах, мне больше нравится использовать php формат, к которому я привык за долгое время разработки web приложений.

Вот код, который создаёт элемент:

$subForm->addElement('text', 'start_date', array(
            'filters' => array('StringTrim', 'StripTags'),
            'required' => true,
            'label' => 'Start date',
            'validators' => array(
                array('Date', true, array('format'=>'j F Y')),
            ),
        ));

Как вы уже заметили, я хочу чтобы дата была в формате "8 November 2010".

Этого добиться довольно таки просто, добавив код в Bootstrap.php:
function _initDateFormat()
{
    Zend_Date::setOptions(array('format_type' => 'php'));
}

Обратите внимание на то, что это статический вызов, так что он распространяется на все инстанции Zend_Date.

Так же я обнаружил, что при использовании формата php многие выражения Zend_Date, такие как Zend_Date::MONTH не работают.

У нас есть несколько дорог, по которым мы можем пойти.

Менять формат отображения там, где это необходимо. Примерно так:
$currentOptions = Zend_Date::setOptions();
$currentFormatType = $currentOptions['format_type'];
Zend_Date::setOptions(array('format_type' => 'iso'));
 
// Теперь вы можете использовать Zend_Date::MONTH, ZEND_DATE::ISO и т.д.
 
// После этого вренуть всё как было
Zend_Date::setOptions(array('format_type' => $currentFormatType));

Так же мы можем перезаписать Zend_Validate_Date:
class App_Validate_Date extends Zend_Validate_Date
{
    public function isValid ($value)
    {
        $currentOptions = Zend_Date::setOptions();
        $currentFormatType = $currentOptions['format_type'];
        Zend_Date::setOptions(array('format_type' => 'php'));
 
        $valid = parent::isValid($value);
 
        Zend_Date::setOptions(array('format_type' => $currentFormatType));
     }
}

Так же у меня имеется несколько требований к валидации:

Определение пустого $value;
Формат Y-m-d так же должен проходить валидацию.
class App_Validate_Date extends Zend_Validate_Date
{
    public function isValid ($value)
    {
        $this->_setValue($value);
         
        if (empty($value)) {
            return true;
        }
 
        $valid = $this->_testDateAgainstFormat($value, $this->getFormat());
        if (!$valid) {
            // проеряем на формат
            $valid = $this->_testDateAgainstFormat($value, 'Y-m-d');
        }
 
        if ($valid) {
            return true;
        }
        $this->_error(self::INVALID_DATE);
        return false;
    }
 
    protected function _testDateAgainstFormat($value, $format)
    {
        $ts = strtotime($value);
        if ($ts !== false) {
            $testValue = date($format, $ts);
            if ($testValue == $value) {
                return true;
            }
        }
        return false;
    }
}

Этот код не будет работать если вы будете иметь дело с локализованными датами! Но вы всегда можете подстроить его под себя!

Добавлено: 16 Мая 2018 16:50:10 Добавил: Андрей Ковальчук

Валидируем почтовые индексы

В этом уроке мы научимся расширять стандартные вариаторы, в частности Zend_Validate_PostCode.

library/App/Validate/PostCode.php:

<?php
 
/**
 * @see Zend_Validate_PostCode
 */
require_once 'Zend/Validate/PostCode.php';
 
class App_Validate_PostCode extends Zend_Validate_PostCode
{
    public function isValid($value)
    {
        $this->_setValue($value);
        if (!is_string($value) && !is_int($value)) {
            $this->_error(self::INVALID);
            return false;
        }
 
        if ($this->getLocale() == 'en_GB') {
            $value = strtoupper(str_replace(' ', '', $value));
            $format = "/^([A-PR-UWYZ]([0-9]([0-9]|[A-HJKSTUW])?|[A-HK-Y][0-9]"
                . "([0-9]|[ABEHMNPRVWXY])?)[0-9][ABD-HJLNP-UW-Z]{2}|GIR0AA)$/";
        } else {
            $format = $this->getFormat();
        }
         
        if (!preg_match($format, $value)) {
            $this->_error(self::NO_MATCH);
            return false;
        }
 
        return true;
    }
}

Тут на лицо превосходная работа системы загрузки плагинов в ZF. Данный код, просто перезаписывает функциональность Zend_Validate_PostCode, так что мы добились того, чего хотели.

Добавлено: 16 Мая 2018 14:52:39 Добавил: Андрей Ковальчук

Создание собственного объекта View с Zend_Application

Предположим, что вам необходимо создать свой объект вида view в приложении Zend Framework. О том, как правильно это сделать, узнаете в этом уроке.

Создайте новый файл library/App/View.php:

class App_View extends Zend_View
{
    // custom methods here
}

Так же не забудьте добавить пространство имён App_ в application.ini:
autoloadernamespaces[] = "App_"

Всё, что нам необходимо сделать так это сообщить Zend_Application, о том, что у нас есть свой собственный класс. Это может быть реализовано двумя путями: непосредственно в Bootstrap.php или предварительно создав отдельный ресурс.

_initView() в Bootstrap.php
На первый взгляд тут всё довольно-таки просто. В application/Bootstrap.php, мы добавили наш собственный метод, который создаёт объект вида:
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
    protected function _initView()
    {
        $view = new App_View();
 
        $viewRenderer = new Zend_Controller_Action_Helper_ViewRenderer();
        $viewRenderer->setView($view);
        Zend_Controller_Action_HelperBroker::addHelper($viewRenderer);
        return $view;
    }
}

Данный метод мы назвали _initView() для того, чтобы он заменил собой уже существующий метод. Однако такой способ приведёт к тому, что данные вида resources.что-то из application.ini будут полностью игнорироваться, так что введём несколько изменений:
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
    protected function _initView()
    {
        $resources = $this->getOption('resources');
        $options = array();
        if (isset($resources['view'])) {
            $options = $resources['view'];
        }
        $view = new App_View($options);
 
        if (isset($options['doctype'])) {
            $view->doctype()->setDoctype(strtoupper($options['doctype']));
            if (isset($options['charset']) && $view->doctype()->isHtml5()) {
                $view->headMeta()->setCharset($options['charset']);
            }
        }
        if (isset($options['contentType'])) {
            $view->headMeta()->appendHttpEquiv('Content-Type', $options['contentType']);
        }
         
        $viewRenderer = new Zend_Controller_Action_Helper_ViewRenderer();
        $viewRenderer->setView($view);
        Zend_Controller_Action_HelperBroker::addHelper($viewRenderer);
        return $view;
    }
 
}

Данная версия функции принимает во внимание ресурсы из application.ini и очень похожа на встроенную функциональность, которую предоставляет Zend Framework.

Собственные ресурсы
Теперь самое время переопределить Zend_Application_Resource_View нашим ресурсам вида. Для этого создадим класс App_Resource_View и расположим его в library/App/Resource/View.php. Всё, что нам нужно, так это переопределить метод getView():
class App_Resource_View extends Zend_Application_Resource_View
{
    public function getView()
    {
        if (null === $this->_view) {
            $options = $this->getOptions();
            $this->_view = new App_View($options);
 
            if (isset($options['doctype'])) {
                $this->_view->doctype()->setDoctype(strtoupper($options['doctype']));
                if (isset($options['charset']) && $this->_view->doctype()->isHtml5()) {
                    $this->_view->headMeta()->setCharset($options['charset']);
                }
            }
            if (isset($options['contentType'])) {
                $this->_view->headMeta()->appendHttpEquiv('Content-Type', $options['contentType']);
            }
        }
        return $this->_view;
    }
}

Для того, чтобы заставить Zend_Application загрузить наш собственный ресурс просто добавьте данную строку application.ini:
pluginPaths.App_Resource = "App/Resource"

Добавлено: 16 Мая 2018 09:02:15 Добавил: Андрей Ковальчук

Создание собственного элемента формы в Zend Framwork

Недавно у меня возникла необходимость получить дату рождения пользователя используя Zend_Form. Для этого мне необходимо было создать свой собственный элемент.

После небольшого поиска я нашёл следующую информацию, которая мне очень помогла.

Для удобства я решил создать свой собственный элемент Zend Form, который будет использовать встроенные элементы формы. Далее создаём объект элемента и объект помощника вида. Использование:

<?php
 
class Application_Form_Details extends Zend_Form
{
    public function init()
    {
        $this->addPrefixPath('App_Form', 'App/Form/');
 
        // other elements before
         
        $this->addElement('date', 'date_of_birth', array(
            'label' => 'Date of birth:'
        ));
         
        // other elements after
 
        $this->addElement('submit', 'Go');
    }
}

В моём случае данная форма расположена в application/forms/Detail.php и отображена в скрипте вида. При определении формы, мы создали элемент 'date'. Благодаря добавлению вызова addPrefixPath мы сообщаем форме, что у нас есть ещё дополнительные элементы (наши собственные), которые расположены в library/App/Form.

Файлы формы расположены в library/App/Form/Element/Date.php. Элемет Date выглядит следующим образом:
// Основано на
// http://codecaine.co.za/posts/compound-elements-with-zend-form
class App_Form_Element_Date extends Zend_Form_Element_Xhtml
{
    public $helper = 'formDate';
 
    public function isValid ($value, $context = null)
    {
        if (is_array($value)) {
            $value = $value['year'] . '-' .
                $value['month'] . '-' .
                $value['day'];
             
            if($value == '--') {
                $value = null;
            }
        }
 
        return parent::isValid($value, $context);
    }
 
    public function getValue()
    {
        if(is_array($this->_value)) {
            $value = $this->_value['year'] . '-' .
                $this->_value['month'] . '-' .
                $this->_value['day'];
 
            if($value == '--') {
                $value = null;
            }
            $this->setValue($value);
        }
 
        return parent::getValue();
    }
 
}

Тут мы много чего наворотили. В первую очередь мы определили имя помощника вида. Так же мы знаем, что этот элемент будет содержать три селект бокса, так что для их значений, нам лучше создать массивы для дня, месяца и года. В результате, нам необходимо переопределить метод isValid() для обеспечения валидации элемента. Так же необходимо переопределить метод getValue(), который будет возвращать строку.

Для элемента у нас всё готово. Теперь нам необходимо создать помощник вида для того, чтобы отобразить элемент. Расположим его в library/App/View/Helpers/FormDate.php и зарегестрируем его в application.ini:
autoloadernamespaces[] = "App_"
resources.view.helperPath.App_View_Helper = "App/View/Helper"

Помощник вида formDate будет выглядеть следующим образом:
<?php
 
// основано на
// http://codecaine.co.za/posts/compound-elements-with-zend-form
 
class App_View_Helper_FormDate extends Zend_View_Helper_FormElement
{
    public function formDate ($name, $value = null, $attribs = null)
    {
        $day = '';
        $month = '';
        $year = '';
        if (is_array($value)) {
            $day = $value['day'];
            $month = $value['month'];
            $year = $value['year'];
        } elseif (strtotime($value)) {
            list($year, $month, $day) = explode('-', date('Y-m-d', strtotime($value)));
        }
        
        $dayAttribs = isset($attribs['dayAttribs']) ? $attribs['dayAttribs'] : array();
        $monthAttribs = isset($attribs['monthAttribs']) ? $attribs['monthAttribs'] : array();
        $yearAttribs = isset($attribs['yearAttribs']) ? $attribs['yearAttribs'] : array();
 
        $dayMultiOptions = array('' => '');
        for ($i = 1; $i < 32; $i ++)
        {
            $index = str_pad($i, 2, '0', STR_PAD_LEFT);
            $dayMultiOptions[$index] = str_pad($i, 2, '0', STR_PAD_LEFT);
        }
        $monthMultiOptions = array('' => '');
        for ($i = 1; $i < 13; $i ++)
        {
            $index = str_pad($i, 2, '0', STR_PAD_LEFT);
            $monthMultiOptions[$index] = date('F', mktime(null, null, null, $i, 01));
        }
 
        $startYear = date('Y');
        if (isset($attribs['startYear'])) {
            $startYear = $attribs['startYear'];
            unset($attribs['startYear']);
        }
 
        $stopYear = $startYear + 10;
        if (isset($attribs['stopYear'])) {
            $stopYear = $attribs['stopYear'];
            unset($attribs['stopYear']);
        }
 
        $yearMultiOptions = array('' => '');
 
        if ($stopYear < $startYear) {
            for ($i = $startYear; $i >= $stopYear; $i--) {
                $yearMultiOptions[$i] = $i;
            }
        } else {
            for ($i = $startYear; $i <= $stopYear; $i++) {
                $yearMultiOptions[$i] = $i;
            }
        }
 
        // возвращает 3 селекта, разделённых  
        return
            $this->view->formSelect(
                $name . '[day]',
                $day,
                $dayAttribs,
                $dayMultiOptions) . ' ' .
            $this->view->formSelect(
                $name . '[month]',
                $month,
                $monthAttribs,
                $monthMultiOptions) . ' ' .
            $this->view->formSelect(
                $name . '[year]',
                $year,
                $yearAttribs,
                $yearMultiOptions
            );
    }
}

Да.. это конечно немалый кусок кода, но я надеюсь, что он вам понятен. Мы провели много операций для настройки трёх селект боксов; для дня, месяца и года. Я предпочёл использовать имена месяцев, но их быстро можно исправить на номера. Так же вам необходимо определить начальный и конечный год.

На этом всё. Для того, чтобы создать свой элемент Zend_Form вам понадобилось два отдельных файла. Так что теперь перед вами нет преград в том, чтобы создавать свои собственные элементы форм для Zend Framework.

Добавлено: 16 Мая 2018 09:00:57 Добавил: Андрей Ковальчук

Форма в вашем layout

Часто возникает вопрос о том, как расположить одну форму на всех страницах вашего Zend приложения. Допустим я хочу расположить форму подписки в файле layout.phtml для того, чтобы она располагалась на каждой странице. Команда layout->content() работает с действиями и контроллерами... Как же реализовать то, что нам нужно?

Одно из решений данной задачи - это создание помощника действия.

Начнём с настройки ZF приложения:

$ zf create project layoutform
$ cd layoutform
$ zf enable layout

Очистите файл application/views/scripts/index/index.phtml и вставьте что-то вроде этого:

application/views/scripts/index/index.phtml:

<p>This is the home page</p>

Теперь можем начинать.

Форма
Создадим новую форму:
$ zf create form signup

А так же поля, которые нам необходимы:

application/forms/Signup.php:
class Application_Form_Signup extends Zend_Form
{
    public $processed = false;
 
    public function init()
    {
        $this->addElement('text', 'name', array(
            'label' => 'Name',
            'required' => true,
            'validators' => array(
                array('StringLength', false, array('max'=>75)),
            ),
        ));
        $this->addElement('text', 'email', array(
            'label' => 'Email',
            'required' => true,
            'validators' => array(
                array('StringLength', false, array('max'=>150)),
                'EmailAddress',
            ),
        ));
        $this->addElement('submit', 'go', array(
            'label' => 'Sign up',
        ));
    }
}

Форма у нас есть. Осталось её вывести.

Помощник действия
Мы используем помощник действия для того чтобы инициализировать форму.

Добавьте строку в application.ini:
resources.frontController.actionhelperpaths.Application_Controller_Helper = APPLICATION_PATH "/controllers/helpers"

Теперь система знает в каком месте искать помощники действий, так что можем идти дальше:

application/Bootstrap.php:
<?php
 
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
    protected function _initMyActionHelpers()
    {
        $this->bootstrap('frontController');
        $signup = Zend_Controller_Action_HelperBroker::getStaticHelper('Signup');
        Zend_Controller_Action_HelperBroker::addHelper($signup);
    }
}

Помощник действия будет выглядеть следующим образом:

application/controllers/helpers/Signup.php:
<?php
 
class Application_Controller_Helper_Signup extends Zend_Controller_Action_Helper_Abstract
{
    public function preDispatch()
    {
        $view = $this->getActionController()->view;
        $form = new Application_Form_Signup();
 
        $request = $this->getActionController()->getRequest();
        if($request->isPost() && $request->getPost('submitsignup')) {
            if($form->isValid($request->getPost())) {
                $data = $form->getValues();
                // process data
 
                $form->processed = true;
            }
        }
         
        $view->signupForm = $form;
    }
}

Тут ничего особенного нет. Просто обратите внимание на класс родитель.

Помощник вида
Для того чтобы отобразить форму, создадим помощник вида, который будет выглядеть следующим образом:

application/views/helpers/SignupForm.php:
<?php
 
class Zend_View_Helper_SignupForm extends Zend_View_Helper_Abstract
{
    public function signupForm(Application_Form_Signup $form)
    {
        $html = '<h2>Sign up for our newsletter</h2>';
        if($form->processed) {
            $html .= '<p>Thank you for signing up</p>';
        } else {
            $html .= $form->render();
        }
        return $html;
    }
}

Всё, что нам осталось, так это сделать вывод формы в layout.phtml:

application/layouts/scripts/layout.phtml:
<?php
$this->headMeta()->prependHttpEquiv('Content-Type', 'text/html; charset=UTF-8');
$this->headTitle('Layout form test');
echo $this->doctype(); ?>
<html>
<head>
<?php echo $this->headMeta()->setIndent(4); ?> 
<?php echo $this->headTitle()->setIndent(4); ?> 
</head>
<body>
    <div id="maincontent">
    <?php echo $this->layout()->content; ?> 
    </div>
    <div id="secondary">
    <?php echo $this->signupForm($this->signupForm); ?>
    </div>
</body>
</html>

Получилось
Вот и всё. Мы добились той функциональности, которую задумали.

Добавлено: 13 Мая 2018 15:05:54 Добавил: Андрей Ковальчук

Помощники видов и модули

Недавно я столкнулся с тем, что мне необходимо было получить доступ к помощнику вида из модуля по умолчанию. Проблема была в том, что доступ нужно было получить из другого модуля. Я столкнулся с этим из-за того, что мой layout.phtml использует помощников, которые расположены в application/views/helpers.

По умолчанию вы получите вот такое вот сообщение:

Plugin by name 'LoggedInAs' was not found in the registry; used paths:
Plan_View_Helper_: /www/funkymongoose/habuplan/application/modules/plan/views/helpers/
Zend_View_Helper_: Zend/View/Helper/

Решение довольно таки простое. Всё что вам нужно, так это добавить строку в файл application.ini file:
resources.view.helperPath.Zend_View_Helper = APPLICATION_PATH "/views/helpers"

Эта строка служит своего рода гарантом, что помощники, расположенные в application/views/helpers будут всегда доступны.

Если вы используете модели, то синтаксис чуть изменится:
resources.view.helperPath.{Module}_View_Helper =
    APPLICATION_PATH "/modules/{module}/views/helpers"

Не забывайте о том, что порядок определения важен, если у вас есть помощники с одинаковыми именами в разных модулях.

Добавлено: 11 Мая 2018 11:24:23 Добавил: Андрей Ковальчук

Азы работы с Zend Framework (2/3) – Реализация приложения

Доброго времени суток всем гостям сайта ruseller.com. Сегодня мы продолжим наш практический экскурс в один из самых продвинутых и популярных PHP фрэймворков – Zend Framework. В сегодняшнем уроке мы реализуем простое Zend приложение, а именно: создадим layout, научимся взаимодействовать с моделями (Базами данных), формами и прочими фичами.

В первую очередь, я бы хотел напомнить вам, что мы создаём простое Zend приложение для создания картотеки наших любимых фильмов. Для того чтобы вы хорошо ориентировались в данном уроке, предлагаю вам ознакомиться с первой частью нашей серии уроков – «Азы работы с Zend Framework (1/3) - Установка и создание проекта».

Итак, на данный момент у вас должно быть создано Zend Framework приложение, состоящее из одного контроллера – Index, и четырёх action – index, add, edit, delete.

Создание базы данных и таблицы
Для того чтобы нам было с чем работать, создадим таблицу и заполним её информацией:

CREATE DATABASE  `zfdemo` ;
USE `zfdemo` ;
CREATE TABLE IF NOT EXISTS `movies` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `director` varchar(100) NOT NULL,
  `title` varchar(100) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=6 ;
INSERT INTO `movies` (`id`, `director`, `title`) VALUES
(1, 'Питер Джексон', 'Властелин Колец'),
(2, 'Кристофер Нолан', 'Начало'),
(3, 'Тодд Филлипс', 'Мальчишник в Вегасе'),
(4, 'Кристофер Нолан', 'Темный рыцарь'),
(5, 'Эндрю Стентон', 'ВАЛЛ-И');

Для подключения к базе данных вы обычно пользовались файлом db.php или что-то вроде этого. В Zend Framework для этих целей существует отдельный файл - application.ini, который находится в каталоге вашего проекта zfdemo\application\configs\application.ini. Перейдите в сегмент development ([development : production]) и вставьте следующий код:
resources.db.adapter = PDO_MYSQL
resources.db.params.host = localhost
resources.db.params.username = root
resources.db.params.password = sql4root
resources.db.params.dbname = zfdemo

PDO_MYSQL – это адаптер, который мы будем использовать для работы с базами данных. Если вы когда-либо работали с базами данных, то следующие параметры будут вам знакомы.

Теперь для того чтобы взаимодействовать с только что созданной таблицей, нам необходимо создать модель. Сделаем мы это при помощи Zend Tool (установка и настройка данного инструмента подробно описана в прошлой статье). Откройте консоль или командную строку. Перейдите в каталог вашего проекта (обязательно). Теперь наберите команду:
zf create db-table Movies movies

После того как вы выполните данную операцию, у вас будет создан новый каталог (application/models/DbTable) и файл Movies.php. Откройте данный файл и заполните его следующим содержимым:
<?php
 
class Application_Model_DbTable_Movies extends Zend_Db_Table_Abstract
{
    // Имя таблицы, с которой будем работать
    protected $_name = 'movies';
     
    // Метод для получения записи по id
    public function getMovie($id)
    {
        // Получаем id как параметр
        $id = (int)$id;
 
        // Используем метод fetchRow для получения записи из базы.
        // В скобках указываем условие выборки (привычное для вас where)
        $row = $this->fetchRow('id = ' . $id);
 
        // Если результат пустой, выкидываем исключение
        if(!$row) {
            throw new Exception("Нет записи с id - $id");
        }
        // Возвращаем результат, упакованный в массив
        return $row->toArray();
    }
     
    // Метод для добавление новой записи
    public function addMovie($director, $title)
    {
        // Формируем массив вставляемых значений
        $data = array(
            'director' => $director,
            'title' => $title,
        );
         
        // Используем метод insert для вставки записи в базу
        $this->insert($data);
    }
     
    // Метод для обновления записи
    public  function updateMovie($id, $director, $title)
    {
        // Формируем массив значений
        $data = array(
            'director' => $director,
            'title' => $title,
        );
         
        // Используем метод update для обновления записи
        // В скобках указываем условие обновления (привычное для вас where)
        $this->update($data, 'id = ' . (int)$id);
    }
     
    // Метод для удаления записи
    public function deleteMovie($id)
    {
        // В скобках указываем условие удаления (привычное для вас where)
        $this->delete('id = ' . (int)$id);
    }
}

Мы только что создали модель с очень длинным названием Application_Model_DbTable_Movies. Такое наименование необходимо согласно правилам построения проекта. Дело в том, что когда мы будем создавать объект данной модели, Zend Framework должен определить какой файл ему подключить. Данное название класса говорит о том, что модель находится в каталоге application/models/db-table, а сама модель называется Movies.php. Таким образом, Zend Framework автоматически подключит данную модель.

Если вы обратили внимание, то наша модель наследуется от класса Zend_Db_Table_Abstract. Благодаря этому наследованию мы можем пользоваться методами insert(), update(), delete(), fetchRow(), fetchAll() и многими другими.

Теперь, когда всё необходимое для работы с базой данных готово, приступим к созданию layout.

Создание Layout
Как вы уже поняли из первой статьи, у каждого контроллера есть серия своих action, каждый из которых в свою очередь имеет свой view. Если вы откроете какой-то view, допустим application\views\scripts\index\edit.phtml, то не увидите ни doctype, ни meta тегов… Вы спросите: «Что мне в каждом файле писать doctype?». Ответ - конечно же нет! Для этого в Zend Framework существует Layout.

Откройте консоль или командную строки и перейдите в каталог вашего проекта. Для того чтобы создать layout, наберите следующую команду:
zf enable layout

После выполнения данной команды в вашем application.ini появится новая строка:

resources.layout.layoutPath = APPLICATION_PATH "/layouts/scripts/"
Ниже этой строки добавьте следующую запись, которая сообщит приложению какой использовать doctype:
resources.view.doctype = "XHTML1_STRICT"

Первая строка сообщит Zend Framework, где ему искать layout скрипт. Откройте каталог application/layouts/scripts. В данной папке вы увидите файл layout.phtml. Откройте данный файл и вставьте следующее содержание:
<?php
    // Создаём meta тег Content-Type
    $this->headMeta()->appendHttpEquiv('Content-Type', 'text/html;charset=utf-8');
    // Указываем символ разделитель в теге title
    $this->headTitle()->setSeparator(' - ');
    // Определяем содержимое тега title
    $this->headTitle('Уроки по Zend Framework от Ruseller.com');
     
    // Выводим doctype, который считывается из application.ini
    echo $this->doctype();
?>
 
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
    <?php
        // Выводим meta теги
        echo $this->headMeta();
    ?>
    <?php
        // Выводим заголовок сайта
        echo $this->headTitle();
    ?>
    <?php
        // Прикрепляем таблицу стилей
        echo $this->headLink()->prependStylesheet($this->baseUrl().'/css/site.css');
    ?>
</head>
<body>
    <div id="content">
        <h1>
        <?php
            // Выводим заголовок странице, который будем формировать в каждом view
            echo $this->escape($this->title);
        ?>
        </h1>
        <?php
            // Данная запись выводит содержимое view
            echo $this->layout()->content;
        ?>
    </div>
</body>
</html>

Теперь этот код будет вставляться на каждую страницу, которую мы будем просматривать, а вместо записи echo $this->layout()->content; будет вставляться содержимое, указанное в view (отдельно сформированное содержание каждой страницы). Теперь, когда мы захотим подключить какую-то библиотеку, таблицу стилей или скрипт, нам необходимо будет просто добавить запись в данный файл.

Для того чтобы полностью покончить с оформлением, в каталоге public создайте папку css и файл site.css:
body,html {
    margin: 0 5px;
    font-family: Verdana,sans-serif;
}
h1 {
    font-size: 1.4em;
    color: #000080;
}
a {
    color: #000080;
}
 
/* Таблица */
th {
    text-align: left;
}
td, th {
    padding-right: 5px;
}
 
/* Форма */
form dt {
    width: 100px;
    display: block;
    float: left;
    clear: left;
}
form dd {
    margin-left: 0;
    float: left;
}
form #submitbutton {
    margin-left: 100px;
}

Создание формы
Для того чтобы вставлять информацию в базу данных, нам необходимо создать форму, через которую пользователь будет вносить свои данные. Откройте командную строку, перейдите в каталог вашего проекта и наберите следующую команду:
zf create form Movie

После выполнения данной команды у вас создастся новый каталог forms, в котором вы найдёте файл Movie.php. Откройте данный файл и вставьте следующее содержание:
<?php
 
class Application_Form_Movie extends Zend_Form
{
    // Метод init() вызовется по умолчанию
    public function init()
    {
        // Задаём имя форме
        $this->setName('movie');
 
        // Создаём элемент hidden c именем = id
        $id = new Zend_Form_Element_Hidden('id');
        // Указываем, что данные в этом элементе фильтруются как число int
        $id->addFilter('Int');
         
        // Создаём переменную, которая будет хранить сообщение валидации
        $isEmptyMessage = 'Значение является обязательным и не может быть пустым';
 
        // Создаём элемент формы – text c именем = director       
        $director = new Zend_Form_Element_Text('director');
         
        /*
        * Далее пишем содержание label, который будет отображаться для данного поля,
        * указываем, является элемент обязательным или нет,
        * пишем список фильтров, которые будут применяться к данному элементу,
        * и наконец, указываем валидатор и сообщение об ошибке, которое будет выведено пользователю
        */
        $director->setLabel('Режиссёр')
            ->setRequired(true)
            ->addFilter('StripTags')
            ->addFilter('StringTrim')
            ->addValidator('NotEmpty', true,
                array('messages' => array('isEmpty' => $isEmptyMessage))
            );
         
        // Создаём второй текстовой элемент формы и проделываем те же операции
        $title = new Zend_Form_Element_Text('title');
        $title->setLabel('Название')
            ->setRequired(true)
            ->addFilter('StripTags')
            ->addFilter('StringTrim')
            ->addValidator('NotEmpty', true,
                array('messages' => array('isEmpty' => $isEmptyMessage))
            );
         
        // Создаём элемент формы Submit c именем = submit
        $submit = new Zend_Form_Element_Submit('submit');
        // Создаём атрибут id = submitbutton
        $submit->setAttrib('id', 'submitbutton');
 
        // Добавляем все созданные элементы к форме.
        $this->addElements(array($id, $director, $title, $submit));
    }
}

Благодаря наследованию от Zend_Form у нас есть возможность создавать мощные и безопасные формы.

Zend Framework содержит классы для всех элементов форм HTML. В данном фрагменте кода мы создали 4 элемента формы. Одно скрытое, 2 текстовых поля и кнопку submit.

Теперь я бы хотел подробнее остановиться на создании одного из текстовых полей, а именно на фрагменте:
->addValidator('NotEmpty', true,
    array('messages' => array('isEmpty' => $isEmptyMessage))
);

Как вы уже поняли, данная запись отвечает за валидацию текстового элемента. В данном случае мы добавляем валидатор NotEmpty, который будет выводить сообщение об ошибке, если поле будет пустым. У валидатора NotEmpty есть несколько вариантов проверки: isEmpty и notEmptyInvalid. Для того чтобы просмотреть все возможные валидаторы и их опции необходимо открыть саму библиотеку Zend/Validate. В данном каталоге находятся все возможные валидаторы.

Если вы приписали одному элементу несколько валидаторов, то вам пригодится второй параметр метода addValidator. Если данный параметр равен true, то это значит, что если валидация не удалась, последующие проверки, касающиеся данного элемента проводиться не будут.

Третий параметр - это сообщения об ошибках валидации. Там мы должны указать ключ ошибки и написать сообщение. Этого можно и не делать, но тогда сообщения будут выводиться на английском языке.

Теперь, когда все подготовительные работы завершены, мы можем приступить к реализации наших action-view.

Реализация CRUD
Выводим список фильмов
Откройте контроллер IndexController, который находится в каталоге application/controllers и вставьте следующий метод:
public function indexAction()
{
    // Создаём объект нашей модели
    $movies = new Application_Model_DbTable_Movies();
 
    // Применяем метод fetchAll для выборки всех записей из таблицы,
    // и передаём их в view, через следующую запись
    $this->view->movies = $movies->fetchAll();
}

В этом фрагменте мы получили все наши любимые фильмы и передали их в view, в котором будем обращаться к ним по переменной $this->movies. Откройте файл index.phtml, который находится в каталоге application\views\scripts\index и вставьте следующий код:
<?php
// Создаём заголовок страницы, который загрузится в layout
$this->title = "Мои любимые фильмы";
$this->headTitle($this->title);
?>
<p><a href="<?php echo $this->url(array('controller'=>'index','action'=>'add'));?>">Добавить новый фильм</a></p>
<table>
    <tr>
        <th>Название</th>
        <th>Режиссёр</th>
        <th> </th>
    </tr>
    <?php
        // Создаём patrialLoop, передав массив фильмов, полученных от контроллера
        echo $this->partialLoop('partials/movie.phtml', $this->movies);
    ?>
</table>

Т.к. использовать цикл для вывода фрагментов html кода не рекомендуется, будем использовать специальный метод partialLoop. Первый параметр - это путь к скрипту, второй массив. Создайте каталог partials в папке application\views\scripts и файл movie.phtml со следующим содержанием:
<tr>
    <td><?php echo $this->escape($this->title);?></td>
    <td><?php echo $this->escape($this->director);?></td>
    <td>
        <a href="<?php echo $this->url(array('controller'=>'index', 'action'=>'edit', 'id'=>$this->id));?>">Редактировать</a>
        <a href="<?php echo $this->url(array('controller'=>'index', 'action'=>'delete', 'id'=>$this->id));?>">Удалить</a>
    </td>
</tr>

В данном фрагменте используем метод escape в качестве фильтра. Теперь, если вы сделали всё правильно, то на странице www.zfdemo.web/index/index вы должны увидеть список фильмов.

Создание нового фильма
Откройте IndexController и вставьте ещё один метод:
public function addAction()
{
    // Создаём форму
    $form = new Application_Form_Movie();
     
    // Указываем текст для submit
    $form->submit->setLabel('Добавить');
     
    // Передаём форму в view
    $this->view->form = $form;
     
    // Если к нам идёт Post запрос
    if ($this->getRequest()->isPost()) {
        // Принимаем его
        $formData = $this->getRequest()->getPost();
         
        // Если форма заполнена верно
        if ($form->isValid($formData)) {
            // Извлекаем режиссёра
            $director = $form->getValue('director');
             
            // Извлекаем название фильма
            $title = $form->getValue('title');
             
            // Создаём объект модели
            $movies = new Application_Model_DbTable_Movies();
             
            // Вызываем метод модели addMovie для вставки новой записи
            $movies->addMovie($director, $title);
             
            // Используем библиотечный helper для редиректа на action = index
            $this->_helper->redirector('index');
        } else {
            // Если форма заполнена неверно,
            // используем метод populate для заполнения всех полей
            // той информацией, которую ввёл пользователь
            $form->populate($formData);
        }
    }
}

Сначала мы создаём объект формы и передаём её в view. Далее следует код, который проверяет валидность заполненной формы. Если пользователь ввёл все верно, то происходит вставка в базу. Если нет, то пользователю представится возможность исправить ошибки. Откройте файл add.phtml. Добавьте следующий код:
<?php
    // Создаём заголовок страницы, который загрузится в layout
    $this->title = "Добавление любимого фильма";
    $this->headTitle($this->title);
     
    // Выводим форму, полученную из контроллера
    echo $this->form ;
?>

Редактирование фильма
Добавьте следующий код в IndexController:
public function editAction()
{
    // Создаём форму
    $form = new Application_Form_Movie();
     
    // Указываем текст для submit
    $form->submit->setLabel('Сохранить');
    $this->view->form = $form;
     
    // Если к нам идёт Post запрос
    if ($this->getRequest()->isPost()) {
        // Принимаем его
        $formData = $this->getRequest()->getPost();
         
        // Если форма заполнена верно
        if ($form->isValid($formData)) {
            // Извлекаем id
            $id = (int)$form->getValue('id');
             
            // Извлекаем режиссёра
            $director = $form->getValue('director');
             
            // Извлекаем название фильма
            $title = $form->getValue('title');
             
            // Создаём объект модели
            $movies = new Application_Model_DbTable_Movies();
             
            // Вызываем метод модели updateMovie для обновления новой записи
            $movies->updateMovie($id, $director, $title);
             
            // Используем библиотечный helper для редиректа на action = index
            $this->_helper->redirector('index');
        } else {
            $form->populate($formData);
        }
    } else {
        // Если мы выводим форму, то получаем id фильма, который хотим обновить
        $id = $this->_getParam('id', 0);
        if ($id > 0) {
            // Создаём объект модели
            $movies = new Application_Model_DbTable_Movies();
             
            // Заполняем форму информацией при помощи метода populate
            $form->populate($movies->getMovie($id));
        }
    }
}

Данный фрагмент мало отличается от предыдущего. Различия только в конце. Для того чтобы заполнить форму текстом, мы извлекаем её при помощи написанного нами метода getMovie(). Откройте файл edit.phtml и вставьте код:
<?php
    // Создаём заголовок страницы, который загрузится в layout
 
    $this->title = "Редактирование любимого фильма";
    $this->headTitle($this->title);
 
    // Выводим форму, полученную из контроллера
    echo $this->form ;
?>

Удаление фильма
Откройте IndexController для добавления последнего метода:
public function deleteAction()
{
    // Если к нам идёт Post запрос
    if ($this->getRequest()->isPost()) {
        // Принимаем значение
        $del = $this->getRequest()->getPost('del');
         
        // Если пользователь подтвердил своё желание удалить запись
        if ($del == 'Да') {
            // Принимаем id записи, которую хотим удалить
            $id = $this->getRequest()->getPost('id');
             
            // Создаём объект модели
            $movies = new Application_Model_DbTable_Movies();
             
            // Вызываем метод модели deleteMovie для удаления записи
            $movies->deleteMovie($id);
        }
         
        // Используем библиотечный helper для редиректа на action = index
        $this->_helper->redirector('index');
    } else {
        // Если запрос не Post, выводим сообщение для подтверждения
        // Получаем id записи, которую хотим удалить
        $id = $this->_getParam('id');
         
        // Создаём объект модели
        $movies = new Application_Model_DbTable_Movies();
         
        // Достаём запись и передаём в view
        $this->view->movie = $movies->getMovie($id);
    }
}

Для того чтобы поставить точку, откройте файл delete.phtml и внесите в него следующее содержание:
<?php
    $this->title = "Удаление любимого фильма";
    $this->headTitle($this->title);
?>
<p>Вы действительно хотите удалить фильм
    '<?php echo $this->escape($this->movie['title']); ?>' от
    '<?php echo $this->escape($this->movie['director']); ?>'?
</p>
<form action="<?php echo $this->url(array('action'=>'delete')); ?>" method="post">
    <div>
        <input type="hidden" name="id" value="<?php echo $this->movie['id']; ?>" />
        <input type="submit" name="del" value="Да" />
        <input type="submit" name="del" value="Нет" />
    </div>
</form>

В данном файле мы выводим форму, для того чтобы пользователь подтвердил своё желание удалить запись.

Заключение
В этом уроке вы познакомились с созданием layout, форм и моделей. Теперь у вас должно сложиться более ясное впечатление о том, как создавать приложения на Zend Framework. На следующей неделе я покажу вам, как создать аутентификацию средствами Zend.

На этом у меня всё, а вы помните, что…

Добавлено: 11 Мая 2018 09:07:07 Добавил: Андрей Ковальчук

Валидируем почтовые индексы

В этом уроке мы научимся расширять стандартные вариаторы, в частности Zend_Validate_PostCode.

library/App/Validate/PostCode.php:

<?php
 
/**
 * @see Zend_Validate_PostCode
 */
require_once 'Zend/Validate/PostCode.php';
 
class App_Validate_PostCode extends Zend_Validate_PostCode
{
    public function isValid($value)
    {
        $this->_setValue($value);
        if (!is_string($value) && !is_int($value)) {
            $this->_error(self::INVALID);
            return false;
        }
 
        if ($this->getLocale() == 'en_GB') {
            $value = strtoupper(str_replace(' ', '', $value));
            $format = "/^([A-PR-UWYZ]([0-9]([0-9]|[A-HJKSTUW])?|[A-HK-Y][0-9]"
                . "([0-9]|[ABEHMNPRVWXY])?)[0-9][ABD-HJLNP-UW-Z]{2}|GIR0AA)$/";
        } else {
            $format = $this->getFormat();
        }
         
        if (!preg_match($format, $value)) {
            $this->_error(self::NO_MATCH);
            return false;
        }
 
        return true;
    }
}

Тут на лицо превосходная работа системы загрузки плагинов в ZF. Данный код, просто перезаписывает функциональность Zend_Validate_PostCode, так что мы добились того, чего хотели.

Добавлено: 10 Мая 2018 15:18:31 Добавил: Андрей Ковальчук

Локальные файлы конфигурации и Zend_Application

Недавно одной моей знакомой потребовалось 2 конфигурационных файла. Один для общего использования, а другой для локального.

Самый простой путь решения данной проблемы, так это редактирование файла index.php. Вот код:

require_once 'Zend/Application.php';
require_once 'Zend/Config/Ini.php';
 
$config = new Zend_Config_Ini(APPLICATION_PATH . '/configs/application.ini', APPLICATION_ENV,
            array('allowModifications'=>true));
$localConfig = new Zend_Config_Ini(APPLICATION_PATH . '/configs/local.ini', APPLICATION_ENV);
$config->merge($localConfig);
$config->setReadOnly();
 
// Создать приложение,bootstrap и запуститься
$application = new Zend_Application(
    APPLICATION_ENV,
    $config
);
$application->bootstrap()
            ->run();

Как вы можете заметить, мы создали несколько объектов Zend_Config. Один для application.ini, другой для local.ini Так же в обоих случаях мы используем APPLICATION_ENV. Это означает, что структура local.ini должна содержать такие же секции ("master") как и application.ini.

Для application.ini, мы выставили опцию "allowModifications' для того, чтобы в последствии мы могли воспользоваться методом merge(), чтобы перезаписать файл $config новыми данными из $localConfig.

После того, как мы объединим настройки, воспользуемся методом setReadOnly(), чтобы убедиться в том, что файлы не будут редактироваться в дальнейшем.

После этого, при создании Zend_Application, мы предаём $config.

Второй способ намного проще первого:
$application = new Zend_Application(
    APPLICATION_ENV,
    array(
        'config' => array(
            APPLICATION_PATH . '/configs/application.ini',
            APPLICATION_PATH . '/configs/local.ini'
        )
    )
);

Вот и всё.

Добавлено: 10 Мая 2018 13:51:59 Добавил: Андрей Ковальчук

Убираем index.php из URL

Если вы успели заметить, то Zend Framework использует помощник вида baseUrl() для того, чтобы сформировать ссылку к CSS и другим статическим файлам. Но работа скрипта нарушается, если URL содержит строку index.php.

Предположим, что в layout.phtml подключение к CSS выглядит так:

<?php echo $this->headLink()->prependStylesheet($this->baseUrl().'/css/site.css'); ?>

Это код метода baseUrl():
<?php
class Zend_View_Helper_BaseUrl
{
    function baseUrl()
    {
        $fc = Zend_Controller_Front::getInstance();
        return $fc->getBaseUrl();
    }
}

Более наглядный пример подключения к файлам:
/zfproject/public/index.php/css/site.css
/zfproject/public/css/site.css.

Решение! Используйте mod_rewrite
Для решения проблемы просто воспользуйтесь mod_rewrite и добавьте строку в ваш файл .htaccess:
RewriteCond %{THE_REQUEST} ^[A-Z]{3,9} /([^/]+/)*index.php
RewriteRule ^index.php(.*)$ /zfproject/public$1 [R=301,L]

Ещё вариант, но похуже
В самом начале файла index.php измените REQUEST_URI:
$_SERVER["REQUEST_URI"] = str_replace('index.php','',$_SERVER["REQUEST_URI"]);

Этот способ работает, но его не следует применять! Может существуют и другие варианты, но мне первый больше нравится.

Добавлено: 10 Мая 2018 07:49:25 Добавил: Андрей Ковальчук

История о том, как достучаться до данных, которые находятся в application.ini

Часто возникает необходимость в том, чтобы извлечь данные из конфигурационного файла application.ini. Используйте Zend_Application для того, чтобы считать данные из application.ini и в дальнейшем извлечь при помощи метода getOptions().

Теперь у вас есть несколько способов, которыми вы можете воспользоваться для того, чтобы извлечь данные.

Внутри контроллера вы можете получить доступ к данным application.ini вот так:

public function someAction()
{
    $bootstrap = $this->getInvokeArg('bootstrap');
    $options = $bootstrap->getOptions();
}

Извне контроллера вы можете доставать данные следующим образом:
$bootstrap = Zend_Controller_Front::getInstance()->getParam('bootstrap');
$options = $bootstrap->getOptions();

Фишка в том, что теперь у нас в $options содержится самый настоящий массив, а не объект Zend_Config. Для того чтобы взаимодействовать непосредственно с объектом Zend_Config, то вам самим придётся его проинициализировать. Одним из самых простых способов будет создание метода в bootstrap, который создаст объект Zend_Config и размещать в хранилище.
protected function _initConfig()
{
    $config = new Zend_Config($this->getOptions());
    Zend_Registry::set('config', $config);
    return $config;
}

Теперь вы можете получить конфигурационные данные из любой части программы. Однако на Zend_Registry не следует сильно налегать.

Добавлено: 09 Мая 2018 19:40:25 Добавил: Андрей Ковальчук

Валидируем даты

Недавно я узнал, что Zend_Date может использоваться в двух модах iso и php, причём iso используется по умолчанию.

При использовании валидатора Zend_Validate_Date в формах, мне больше нравится использовать php формат, к которому я привык за долгое время разработки web приложений.

Вот код, который создаёт элемент:

$subForm->addElement('text', 'start_date', array(
            'filters' => array('StringTrim', 'StripTags'),
            'required' => true,
            'label' => 'Start date',
            'validators' => array(
                array('Date', true, array('format'=>'j F Y')),
            ),
        ));

Как вы уже заметили, я хочу чтобы дата была в формате "8 November 2010".

Этого добиться довольно таки просто, добавив код в Bootstrap.php:
function _initDateFormat()
{
    Zend_Date::setOptions(array('format_type' => 'php'));
}

Обратите внимание на то, что это статический вызов, так что он распространяется на все инстанции Zend_Date.

Так же я обнаружил, что при использовании формата php многие выражения Zend_Date, такие как Zend_Date::MONTH не работают.

У нас есть несколько дорог, по которым мы можем пойти.

Менять формат отображения там, где это необходимо. Примерно так:
$currentOptions = Zend_Date::setOptions();
$currentFormatType = $currentOptions['format_type'];
Zend_Date::setOptions(array('format_type' => 'iso'));
 
// Теперь вы можете использовать Zend_Date::MONTH, ZEND_DATE::ISO и т.д.
 
// После этого вренуть всё как было
Zend_Date::setOptions(array('format_type' => $currentFormatType));

Так же мы можем перезаписать Zend_Validate_Date:
class App_Validate_Date extends Zend_Validate_Date
{
    public function isValid ($value)
    {
        $currentOptions = Zend_Date::setOptions();
        $currentFormatType = $currentOptions['format_type'];
        Zend_Date::setOptions(array('format_type' => 'php'));
 
        $valid = parent::isValid($value);
 
        Zend_Date::setOptions(array('format_type' => $currentFormatType));
     }
}

Так же у меня имеется несколько требований к валидации:

Определение пустого $value;
Формат Y-m-d так же должен проходить валидацию.
class App_Validate_Date extends Zend_Validate_Date
{
    public function isValid ($value)
    {
        $this->_setValue($value);
         
        if (empty($value)) {
            return true;
        }
 
        $valid = $this->_testDateAgainstFormat($value, $this->getFormat());
        if (!$valid) {
            // проверяем на формат
            $valid = $this->_testDateAgainstFormat($value, 'Y-m-d');
        }
 
        if ($valid) {
            return true;
        }
        $this->_error(self::INVALID_DATE);
        return false;
    }
 
    protected function _testDateAgainstFormat($value, $format)
    {
        $ts = strtotime($value);
        if ($ts !== false) {
            $testValue = date($format, $ts);
            if ($testValue == $value) {
                return true;
            }
        }
        return false;
    }
}

Этот код не будет работать если вы будете иметь дело с локализованными датами! Но вы всегда можете подстроить его под себя!

Добавлено: 08 Мая 2018 17:51:11 Добавил: Андрей Ковальчук

Помощники видов в Zend Framework

Настало время немного разъяснить, что представляют из себя помощники видов в Zend Framework. Zend Framework предоставляет компоненту Zend_View, которая позволяет создавать помощники видов. Их использование выглядит примерно так:

<?php echo $this->myHelper('myParam1'); ?>

А сам помощник вида примерно вот так:
<?php
 
class Zend_View_Helper_MyHelper extends Zend_View_Helper_Abstract
{
    public function myHelper($myParam1)
    {
        $html = '';
        // some logic that fills in $html.
        return $html;
    }
}

Тут необходимо отметить две детали. Первая это то, что любой помощник выводит строку. Вторая, то, что Zend_View_Helper_ это префикс.

В стандартной комплектации проекта на Zend Framework данные помощники видов размещаются в специальном каталоге под названием helpers->views. Так же каталог helpers может находиться в папке layouts. Если вы помещаете помощник видов в один из этих каталогов, то префикс будет следующим Zend_View_Helper_.

Но это не строгое ограничение - мы можем располагать наших помощников в любой папке. Обычно разработчики хранят их в папке library/App/View/Helper/, так что префикс App_View_Helper_ тут уже не сработает. Для того, чтобы всё было пучком следует добавить запись в application.ini:

resources.view.helperPath.App_View_Helper_ = "App/View/Helper/"
Теперь помощники видов располагаются в App/View/Helper/MyHelper.php и выглядят следующим образом:
<?php
 
class App_View_Helper_MyHelper extends Zend_View_Helper_Abstract
{
    public function myHelper($myParam1)
    {
        $html = '';
        // some logic that fills in $html.
        return $html;
    }
}

После того, как вы наследуете от класса Zend_View_Helper_Abstract для создания собственного хелпера, то вам надо получить доступ к объекту вида. Это необходимо для того, чтобы получать доступ к другим помощникам вида, таким как к примеру escape.

Допустим, мы хотим создать таблицу из массива, который выглядит следующим образом:
$data = array();
$data[] = array('Name', 'Email');
$data[] = array('Alison', 'alison@example.com');
$data[] = array('Bert', 'bert@example.com');
$data[] = array('Charlie', 'charlie@example.com');

Вывод:
<?php echo $this->tabulate($this->data, array('class'=>'tabulated')); ?>

А сам помощник вида будет выглядеть так:
<?php
 
class App_View_Helper_Tabulate extends Zend_View_Helper_Abstract
{
    public function tabulate ($data, $attribs = array())
    {
        $attribString = '';
        foreach ($attribs as $key => $value) {
            $attribString .= ' ' . $key .'="' . $value . '"';
        }
 
        $header = array_shift($data);
        $html = "<table $attribString>\n<tr>\n";
        foreach ($header as $cell) {
            $escapedCell = $this->view->escape($cell);
            $html .= "<th>$escapedCell</th>\n";
        }
        $html .= "</tr>\n";
        foreach ($data as $row) {
            $html .= "<tr>\n";
            foreach ($row as $cell) {
                $escapedCell = $this->view->escape($cell);
                $html .= "<td>$escapedCell</td>\n";
            }
            $html .= "</tr>\n";
        }
 
        $html .= '</table>';
        return $html;
    }
}

Напоследок хочу напомнить, что префиксы можно задавать какие угодно:
resources.view.helperPath.Akrabat_View_Helper_ = "Akrabat/View/Helper/"
resources.view.helperPath.App_View_Helper_ = "App/View/Helper/"

Так же старайтесь давать уникальные имена вашим помощникам иначе они могут быть перезаписаны и не вызываться там, где вы этого ожидаете. Ну и наоборот. Если вас не устраивает встроенные хелперы в Zend Framework, то вы всегда можете заместить их своими собственными.

Добавлено: 08 Мая 2018 12:28:43 Добавил: Андрей Ковальчук