PHP

Настраиваем Zend Application

Posted in Development, PHP, ZendFramework on August 13th, 2009 by Yaroslav Vorozhko – 2 Comments

С выходом Zend Framework 1.8 связано много новых изменений и дополнений. Самые значительные это добавление Zend_Application и Zend_Tool.

Zend_Tool представляет собой консольную программу управления проектами на Zend Framework.

Zend_Application это новая система загрузки приложения и конфигурирования проекта.

Рассмотрим как работать с Zend_Application подробнее.

Управление загрузкой приложения занимается Zend_Application_Bootstrap, он выполняет загрузку конфига, инициализацию компонентов, front контроллера, базы данных и объекта вида view.

Большинство настроек этих компонентов может храниться в конфигурационном файле, что обеспечивает дополнительную гибкость и модульность.

Я не буду подробно описывать все файлы и параметры в деталях, так как они уже имеют хорошее описание в manual и руководстве quickstart. Вместо этого, создадим простое рабочее приложение с минимальным количеством настроек.

Вы можете просто использовать copy-past приведенных скриптов, чтоб сэкономить время.

Давате создадим файловую структуру.

PHP:
  1. mkdir -p public/css
  2. mkdir -p public/img
  3. mkdir -p public/js
  4. mkdir -p application/configs
  5. mkdir -p application/controllers
  6. mkdir -p application/layouts/scripts
  7. mkdir -p application/models
  8. mkdir -p application/views/helpers
  9. mkdir -p application/views/scripts/index
  10. mkdir -p application/views/scripts/error
  11. mkdir -p data/session
  12. mkdir -p bin
  13. mkdir -p library/App
  14. mkdir -p tests/application
  15. mkdir -p tests/application/controllers

Теперь скачайте последний релиз ZF и скопируйте директорию Zend в library.

Public каталог

Создайте файл public/.htaccess

PHP:
  1. SetEnv APPLICATION_ENV development
  2.  
  3. RewriteEngine On
  4. RewriteCond %{REQUEST_FILENAME} -s [OR]
  5. RewriteCond %{REQUEST_FILENAME} -l [OR]
  6. RewriteCond %{REQUEST_FILENAME} -d
  7. RewriteRule ^.*$ - [NC,L]
  8. RewriteRule ^.*$ index.php [NC,L]

В файле public/index.php мы просто установим переменные окружения, а все остальное сделает Zend_Application. Как я упоминал раньше, большинство настроек хранится в конфиграционном файле.

public/index.php

PHP:
  1. <?php
  2. define('BASE_PATH', realpath(dirname(__FILE__) . '/../'));
  3. define('APPLICATION_PATH', BASE_PATH . '/application');
  4.  
  5. // Include path
  6.     BASE_PATH . '/library'
  7.     . PATH_SEPARATOR . get_include_path()
  8. );
  9.  
  10. // Define application environment
  11. defined('APPLICATION_ENV')
  12.     || define('APPLICATION_ENV',
  13.               (getenv('APPLICATION_ENV') ? getenv('APPLICATION_ENV')
  14.                                          : 'production'));
  15.  
  16. // Zend_Application
  17. require_once 'Zend/Application.php';
  18.  
  19. $application = new Zend_Application(
  20.     APPLICATION_ENV,
  21.     APPLICATION_PATH . '/configs/application.ini'
  22. );
  23.  
  24. $application->bootstrap();
  25. $application->run();

Конфигурационный файл

application/configs/application.ini: В моем приложении используется 3 базы данных: основная база, база для разработки и база для тестирования, поэтому определение настроек баз данных разделено на 3 разных части.

PHP:
  1. [production]
  2.  
  3. # Debug output
  4. phpSettings.display_startup_errors = 0
  5. phpSettings.display_errors = 0
  6.  
  7. # Include path
  8. includePaths.library = APPLICATION_PATH "/../library"
  9.  
  10. # Bootstrap
  11. bootstrap.path = APPLICATION_PATH "/Bootstrap.php"
  12. bootstrap.class = "Bootstrap"
  13.  
  14. # Front Controller
  15. resources.frontController.controllerDirectory = APPLICATION_PATH "/controllers"
  16. resources.frontController.env = APPLICATION_ENV
  17.  
  18. # Layout
  19. resources.layout.layout = "layout"
  20. resources.layout.layoutPath = APPLICATION_PATH "/layouts/scripts"
  21.  
  22. # Views
  23. resources.view.encoding = "UTF-8"
  24. resources.view.basePath = APPLICATION_PATH "/views/"
  25.  
  26. # Database
  27. resources.db.adapter = "pdo_mysql"
  28. resources.db.params.host = "localhost"
  29. resources.db.params.username = "myproject"
  30. resources.db.params.password = "myproject"
  31. resources.db.params.dbname = "myproject_production"
  32. resources.db.isDefaultTableAdapter = true
  33.  
  34. # Session
  35. resources.session.save_path = APPLICATION_PATH "/../data/session"
  36. resources.session.remember_me_seconds = 864000
  37.  
  38. [testing : production]
  39.  
  40. # Debug output
  41. phpSettings.display_startup_errors = 1
  42. phpSettings.display_errors = 1
  43.  
  44. # Database
  45. resources.db.params.dbname = "myproject_testing"
  46.  
  47. [development : production]
  48.  
  49. # Debug output
  50. phpSettings.display_startup_errors = 1
  51. phpSettings.display_errors = 1
  52.  
  53. # Database
  54. resources.db.params.dbname = "myproject_development"

Загрузчик

application/Bootstrap.php: как определено в конфигурационном файле, приложение использует этот файл для процесса загрузки. Я предоставил Zend_Application выполнить всю инициализацию самостоятельно за исключение вида view. В view я хочу установить особый doctype XHTML.

PHP:
  1. <?php
  2. class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
  3. {
  4.     protected function _initView()
  5.     {
  6.         // Initialize view
  7.         $view = new Zend_View();
  8.         $view->doctype('XHTML1_STRICT');
  9.         $view->headTitle('My Project');
  10.         $view->env = APPLICATION_ENV;
  11.  
  12.         // Add it to the ViewRenderer
  13.         $viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper(
  14.             'ViewRenderer'
  15.         );
  16.         $viewRenderer->setView($view);
  17.  
  18.         // Return it, so that it can be stored by the bootstrap
  19.         return $view;
  20.     }
  21. }

Готово. Это и есть процесс загрузки. Ваше приложение теперь готово использовать ZF MVC и базу данных. Поэтому, давайте продолжим настроку специфических элементов приложения.

Добавим layout

Давайте добвим layout по умолчанию applicaiton/layouts/scripts/layout.phtml:

PHP:
  1. <?php echo $this->doctype() ?>
  2. <html>
  3. <head>
  4.     <?php echo $this->headTitle() ?>
  5.     <?php echo $this->headLink() ?>
  6.     <?php echo $this->headStyle() ?>
  7.     <?php echo $this->headScript() ?>
  8. </head>
  9. <body>
  10.     <?php echo $this->layout()->content ?>
  11. </body>
  12. </html>

Добавим контроллер и вид view

application/controllers/IndexController.php ничего особенного в нем:

PHP:
  1. <?php
  2. class IndexController extends Zend_Controller_Action
  3. {
  4.     public function indexAction()
  5.     {
  6.     }
  7. }

Дабавим view для index/index action в application/view/scripts/index/index.phtml

PHP:
  1. <h1 id="welcome-to-the-zend-framework">Welcome to the Zend Framework!</h1>

application/controllers/ErrorController.php вызывается в том случае, если запрашиваемый контроллер не был найден. В зависимости от переменных окружения он вызывает исключение и вывод трассировки стека.

application/controllers/ErrorController.php:

PHP:
  1. <?php
  2. class ErrorController extends Zend_Controller_Action
  3. {
  4.     public function errorAction()
  5.     {
  6.         $errors = $this->_getParam('error_handler');
  7.  
  8.         switch ($errors->type) {
  9.             case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER:
  10.             case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ACTION:
  11.  
  12.                 // 404 error -- controller or action not found
  13.                 $this->getResponse()->setHttpResponseCode(404);
  14.                 $this->view->message = 'Page not found';
  15.                 break;
  16.             default:
  17.                 // application error
  18.                 $this->getResponse()->setHttpResponseCode(500);
  19.                 $this->view->message = 'Application error';
  20.                 break;
  21.         }
  22.  
  23.         $this->view->exception = $errors->exception;
  24.         $this->view->request   = $errors->request;
  25.     }
  26. }

И принадлежащий ему вид:

PHP:
  1. <h1 id="an-error-occurred">An error occurred</h1>
  2. <h3 id=""><?php echo $this->message ?></h3>
  3.  
  4. <? if ('development' == $this->env): ?>
  5.  
  6.     <h4 id="exception-information">Exception information:</h4>
  7.     <p>
  8.       <strong>Message:</strong> <?php echo $this->exception->getMessage(); ?>
  9.     </p>
  10.  
  11.     <h4 id="stack-trace">Stack trace:</h4>
  12.     <pre><?php echo $this->exception->getTraceAsString() ?></pre>
  13.  
  14.     <h4 id="request-parameters">Request Parameters:</h4>
  15.     <? var_dump($this->request->getParams()); ?>
  16.  
  17. <? endif; ?>

Теперь ваше приложение должно работать. Попробуйте открыть / и /foo. Вы должны будете увидеть index/index и error/error actions.

Сокращено и переведено из mafflog.

Список полезных классов и библиотек для PHP разработчиков

Posted in Development, PHP, Tips And Tricks on August 12th, 2009 by Yaroslav Vorozhko – Be the first to comment

Saud Khan из w3avenue собрал хороший список полезных библиотек и классов для PHP разработчиков.

Особенно полезным список будет для начинающих.

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

Список включает в себя следующие разделы:

  • Базы данных
  • Разработка
  • Документация
  • Email
  • Формы
  • HTML/CSS
  • Image/Media/File
  • Javascript/Ajax
  • Microformats
  • RSS & Atom Feeds
  • Безопасность
  • Тестирование и отладка
  • Веб сервисы

В общем хорошая подборка практически на все нужды разработки.

Не поленитесь изучить! ;-)

Прикручиваем свои сообщения к Zend Validate Email

Posted in Development, PHP, ZendFramework on August 11th, 2009 by Yaroslav Vorozhko – Be the first to comment

Валидацией email адресов в ZF занимается Zend_Validate_Email. Кто не знает, email состоит из двух частей, имени и хоста. Хост проверяется с помощью Zend_Validate_Hostname и все эти вещи выполняются в background, т.е. мы не получем каких либо нотификаций о происходящих там процессах.

Так, вот Thomas Weidner разобрался с этой проблемой и предлагает свое решение валидиции email.

Мифы PHP оптимизации

Posted in PHP, Performance on August 11th, 2009 by Yaroslav Vorozhko – Be the first to comment

Одни оптимизации полезны, другие просто пустая трата времени.
Вот пример наиболее частых заблуждений:

а. echo быстрее чем print
Echo может быть быстрее, так как оно не возвращает значение. Но, в моем бенчмарке преимущество было очень мало. А, в некоторых ситуациях print будет быстрее echo, например когда ob_start включен.

б. меньше комментариев ускоряет код
Если вы используете кеширование opcodes, то комментарии уже игнорируются. Этот миф идет от PHP3, когда каждая строка PHP интерпретировалась во время исполнения.

в. 'var='.$var быстрее чем, "var=$var"
Так было до версии 4.2 и было исправлено в версии 4.3.

Ускоряет ли код использование ссылок?
Ссылки не дают преимущества строковым, целым и другим базовым типам данных.
Например:

PHP:
  1. function TestRef(&$a)
  2. {
  3. $b = $a;
  4. $c = $a;
  5. }
  6. $one = 1;
  7.  
  8. ProcessArrayRef($one);

И тот же самый код без ссылки.

PHP:
  1. function TestNoRef($a)
  2. {
  3. $b = $a;
  4. $c = $a;
  5. }
  6. $one = 1;
  7.  
  8. ProcessArrayNoRef($one);

PHP не создает дубликат переменной "отправленной по значению", вместо этого он использует внутренних высокоскоростной подсчет ссылок. Поэтому в TestRef(), $b и $c будут дольше устанавливаться, так как надо вести "трэкинг" ссылок, в то время как в TestNoRef(), $b и $c сразу будут ссылаться на исходное значение $a, а значение счетчика ссылок будет инкрементировано.
В сравнении, функции которые принимают массивы и объекты, работают быстрее тех, которых принимают ссылки. Потому что, массивы и объекты не используют подсчет ссылок, а используется оригинальное значение переданное в параметре.
Например:

PHP:
  1. function ObjRef(&$o)
  2. {
  3. $a =$o->name;
  4. }

медленнее чем:

PHP:
  1. function ObjRef($o)
  2. {
  3. $a = $o->name;
  4. }

Примечание: в PHP5 все объекты передаются по ссылке, и нет необходимости устанавливать знак '&' в списке параметров. Производительность работы с объектами в PHP5 значительно выше, чем в php4.

PHP скрипт анализа логов

Posted in PHP, Tips And Tricks on July 31st, 2009 by Yaroslav Vorozhko – Be the first to comment

Задача была написать скрипт анализа логов, включая:

  • обработку повторяющихся ошибок
  • подсчетом количества повторов ошибок
  • хранение времени появления первого и последнего повтора ошибки
  • отправка каждой ошибки на email
  • хранение последней позиции курсора файла в первой строке лога

Формат лога представлял собой следующее:

PHP:
  1. 2009-07-23T10:25:11+00:00  DEBUG (7): Resource 'demo' not found
  2. 2009-07-24T17:10:20+00:00  DEBUG (7): Resource 'photos' not found
  3. 2009-07-24T17:11:22+00:00  DEBUG (7): Resource 'photos' not found

Т.е. одна ошибка в одну строку.

После обработки их скриптом, мы получали email с сообщением следующего формата, одна строка это одно сообщение:

PHP:
  1. DEBUG (7): Resource 'photos' not found - 2 times, first time - 2009-07-24 17:10:20 GMT, last time - 2009-07-24 17:11:22 GMT
  2. DEBUG (7): Resource 'demo' not found - 1 time, 2009-07-23 10:25:11 GMT

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

Вот собственно и сам log_analyzer.php:

PHP:
  1. define('ERROR_LOG_PATH', BASE_PATH.'/logs/error.log');
  2.  
  3. $fp = fopen(ERROR_LOG_PATH, 'r+');
  4. $position = fgets($fp);
  5. $position = intval($position);
  6.  
  7. fseek($fp, $position);
  8.  
  9. $time = strtotime("-7 minutes");
  10. $grabLog = false;
  11. $sphinxErrors = $sphinxWarnings = array();
  12. $errors = array();
  13. while(!feof($fp)){
  14.     $line = fgets($fp);
  15.     if( preg_match("/^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[+-]\d{2}:\d{2})/",$line, $matches) === 1 ){
  16.         $datetime = $matches[1];
  17.     } else {
  18.          continue;
  19.     }
  20.     $errorLine = str_replace($datetime, '', $line);
  21.     $formattedTime = date("Y-m-d H:i:s T", strtotime($datetime));
  22.     if (!isset($errors[$errorLine])){               
  23.         $errors[$errorLine]['firsttime'] = $formattedTime;
  24.         $errors[$errorLine]['lasttime'] = $formattedTime;
  25.         $errors[$errorLine]['times'] = 1;
  26.     } else {
  27.         $errors[$errorLine]['lasttime'] = $formattedTime;
  28.         $errors[$errorLine]['times']++;
  29.     }
  30.    
  31. }
  32. $newPosition = ftell($fp);
  33.  
  34.  
  35. if ( !rewind($fp) ) {
  36.     echo 'not working';
  37. }
  38. fwrite($fp, $newPosition."        \n");
  39. fclose($fp);
  40.  
  41.  
  42. if (!empty($errors)){
  43.     foreach ($errors as $text => $error){
  44.         if ($error['times']> 1){
  45.             $body = sprintf("%s - %s times, first time - %s, last time - %s",
  46.                 $text, $error['times'], $error['firsttime'], $error['lasttime']);
  47.         } else {
  48.             $body = sprintf("%s - %s time, %s",
  49.                 $text, $error['times'], $error['firsttime']);
  50.         }
  51.         Mail_Notifications::notifyError($body);
  52.     }
  53. }

Скрипт я запускаю с помощью cron каждые 7 минут.

Комментарии

Авто редирект из http в https в Zend Framework

Posted in Development, PHP, Tips And Tricks, ZendFramework on July 1st, 2009 by Yaroslav Vorozhko – Be the first to comment

Привет,
Следующий Helper решает проблему редиректа из http страници на https.
Скажем надо перейти от http://mysite.com/action/param/ на https://mysite.com/action/param/

PHP:
  1. class My_Helper_SslSwitch extends Zend_Controller_Action_Helper_Abstract
  2.     {
  3.         public function direct()
  4.         {
  5.             if (!isset($_SERVER['HTTPS']) || !$_SERVER['HTTPS']) {
  6.                 $request    = $this->getRequest();
  7.                 $url        = 'https://'
  8.                             . $_SERVER['HTTP_HOST']
  9.                             . $request->getRequestUri();
  10.                 $redirector = Zend_Controller_Action_HelperBroker::getStaticHelper('redirector');
  11.                 $redirector->gotoUrl($url);
  12.             }
  13.         }
  14.     }

Автор хелпера Matthew Weier O'Phinney

Далее в bootstrap добавляем путь к Helper в include_path
и вызываем его, таким образом мы переводим весь сайт на использование https.

PHP:
  1. Zend_Controller_Action_HelperBroker::addPrefix('My_Helper');
  2. $ssl = new My_Helper_SslSwitch();
  3. $ssl->direct();

Если же вам надо включить SSL только для определенных контроллеров, то вы можете поместить следующий код в preDispath() метод контроллеров.

PHP:
  1. $this->_helper->sslSwith();

В данном случае мы используем объект _helper для создания объекта sslSwith и вызова метода direct().
Note: Метод direct() вызыватеся автоматически для всех Action Helpers.

Скрипт верификации сайта

Posted in Development, PHP, Tips And Tricks on June 21st, 2009 by Yaroslav Vorozhko – 8 Comments

Приветствую!
Предлагаю на обсуждения простой скрипт проверки принадлежности (верификации) сайта, его владельцу.

Предполагается, что владелец сайта поместил специальный meta тег в head секцию на корневой странице сайта, т.е. на home page.
Формат meta тега следующий:
<meta name="vf_key" content="29693426cfd334d8287783b49217e64f"/>

Задача определить наличие заданного meta тэга в заголовке документа, исключая весь остальной документ.

Функция верификации следующая:

PHP:
  1. /**
  2. * Function return true if verfication is successed
  3. *@param $url string - it is valid url address
  4. *@param $metaValue - string it is special code which placed as content for verification meta tag
  5. **/
  6.         public function verifyMetaTag($url, $metaValue)
  7.     {   
  8.         // we know what $url is valid url
  9.  
  10.                 //get html of the page with Zend http client
  11.         $client = new Zend_Http_Client($url, array(
  12.             'maxredirects' => 0,
  13.             'timeout'      => 30)
  14.         );
  15.         $response = $client->request();
  16.                 if (!$response){
  17.                        return false;
  18.                 }
  19.        
  20.         $html = $response->getBody();
  21.        
  22.         $headPos = strpos($html, '</head>');
  23.         $metaPos = strpos($html, $metaValue);
  24.  
  25.                 //close head section must be below our meta tag
  26.         if ($metaPos> $headPos){
  27.             return false;
  28.         }
  29.        
  30.         $htmlHead = substr($html, 0, $headPos);
  31.  
  32.                 //grab all meta tags in head section into array
  33.         preg_match_all("#<meta(.*)>#", $htmlHead, $matches);
  34.        
  35.                 //checking all meta tags on special key and value pairs
  36.         foreach ($matches[1] as $meta){
  37.                      //meta tag must have key "vf_key" and value "$metaValue"
  38.             if (strpos($meta, 'vf_key') && strpos($meta, $metaValue)){
  39.                 return true;
  40.             }
  41.         }
  42.         return false;
  43.     }

Есть ли у этого метода недостатки или может есть более простой вариант?