Showing posts with label php. Show all posts
Showing posts with label php. Show all posts

Tuesday, March 29, 2011

yii, облегчаем прототипирование.

Введение



Я давно работаю с yii framework. И пока это единственный framework в мире php. Из минусов для меня то, что он слишком низкоуровневый, отсутствует много плюшек, которые я с успехом возмещаю разумным использованием Zend компонент. Как набор компонент Zend хорош, чего не скажешь о его framework'е.

Ну так вот, возвращаясь к низкоуровневому yii. Его CRUD хорош, хорош для прототипирования, но плодит кучу файлов. Многие из которых со временем вырезаются и сильно модифицируются. Моим кошмаром была работа на небольшим по объему backend проектом. Где вся работа сводилась к администрированию кучи таблиц с текстовыми и буленовскими полями. При помощи CRUD все было сделано быстро, но большую часть времени я потратил на украшение админки, checkboxes вместо input, фильтры вида true|false вместо 0,1 и тому подобной ерунды. Таблиц было много, view еще больше. Я с нежностью вспоминал django и жалел что проект на PHP.



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



Краткая суть. CRUD очень похожи, и их можно объединить. Второй задачей является способ указания системе типов данных в моделях, для того чтобы CRUD мог на основе этих типов создавать фильтры, приводить к строковому виду автоматически.

Это получилось, пока начало, но проект не закончен. Но уже можно пользоваться.

Что получилось? Получился модуль который позволяет не запуская CRUD получить CRUD странички над любой моделью при условии что она отнаследована от особого ActiveRecord + в ней определен один единственный метод. В качестве небольшого бонуса есть простая возможность слегка менять список полей и их порядок на полученных страницах.


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



Инициализация yii проекта


Можно пропустить, если хотите воспользоваться существующим.
Описание как создать yii проект с нуля. Для того чтобы потом использовать модуль.


Создание директории проекта.



Я буду использовать локальный вебсервер, с дефолтными настройками. Проект создам в папке AdminTest внутри моего document root /var/www/

 
claymore:$ mkdir /var/www/AdminTest
claymore:$ cd /var/www/AdminTest/
claymore:$


Папка создана, скачиваем yii-framework
 
Среда 2011-03-30 11:03 box:/var/www/AdminTest
claymore:$ wget http://yii.googlecode.com/files/yii-1.1.7.r3135.tar.gz
--2011-03-30 11:12:58-- http://yii.googlecode.com/files/yii-1.1.7.r3135.tar.gz
Преобразование адреса yii.googlecode.com... 74.125.87.82
Устанавливается соединение с yii.googlecode.com|74.125.87.82|:80... соединились.
Запрос HTTP послан, ожидание ответа... 200 OK
Длина: 2550331 (2,4M) [application/x-gzip]
Saving to: «yii-1.1.7.r3135.tar.gz»

100%[=============================================================================================================================>] 2 550 331 780K/s в 3,2s

2011-03-30 11:13:01 (780 KB/s) - «yii-1.1.7.r3135.tar.gz» saved [2550331/2550331]

Распаковываем

claymore:$ tar xf yii-1.1.7.r3135.tar.gz

Переименовываем, или делаем символическую ссылку( кому как удобнее), я переименовываю, т.к. работаю в рамках демонстрации
   
claymore:$ mv yii-1.1.7.r3135 yii

Удаляю скаченный архив

claymore:$ rm yii-1.1.7.r3135.tar.gz

Проверяем что у нас есть распаковааная папка с yii framework

claymore:$ ls
yii

Переходим в папку со скриптами yii framework
 
claymore:$ cd yii/framework/

Запускаем скрипт создания проекта

claymore:$ ./yiic webapp /var/www/AdminTest/
Create a Web application under '/var/www/AdminTest'? [Yes|No] Yes
mkdir /var/www/AdminTest/css
generate css/form.css
generate css/main.css
generate css/bg.gif
generate css/ie.css
generate css/screen.css
generate css/print.css
mkdir /var/www/AdminTest/assets
mkdir /var/www/AdminTest/images
generate index.php
generate index-test.php
mkdir /var/www/AdminTest/themes
mkdir /var/www/AdminTest/themes/classic
mkdir /var/www/AdminTest/themes/classic/views
mkdir /var/www/AdminTest/themes/classic/views/site
mkdir /var/www/AdminTest/themes/classic/views/layouts
generate themes/classic/views/.htaccess
mkdir /var/www/AdminTest/themes/classic/views/system
mkdir /var/www/AdminTest/protected
mkdir /var/www/AdminTest/protected/data
generate protected/data/schema.sqlite.sql
generate protected/data/testdrive.db
generate protected/data/schema.mysql.sql
generate protected/yiic
mkdir /var/www/AdminTest/protected/messages
mkdir /var/www/AdminTest/protected/tests
mkdir /var/www/AdminTest/protected/tests/unit
mkdir /var/www/AdminTest/protected/tests/functional
generate protected/tests/functional/SiteTest.php
generate protected/tests/phpunit.xml
generate protected/tests/bootstrap.php
generate protected/tests/WebTestCase.php
mkdir /var/www/AdminTest/protected/tests/fixtures
mkdir /var/www/AdminTest/protected/tests/report
mkdir /var/www/AdminTest/protected/views
mkdir /var/www/AdminTest/protected/views/site
mkdir /var/www/AdminTest/protected/views/site/pages
generate protected/views/site/pages/about.php
generate protected/views/site/login.php
generate protected/views/site/index.php
generate protected/views/site/contact.php
generate protected/views/site/error.php
mkdir /var/www/AdminTest/protected/views/layouts
generate protected/views/layouts/column1.php
generate protected/views/layouts/column2.php
generate protected/views/layouts/main.php
mkdir /var/www/AdminTest/protected/runtime
generate protected/.htaccess
mkdir /var/www/AdminTest/protected/components
generate protected/components/Controller.php
generate protected/components/UserIdentity.php
mkdir /var/www/AdminTest/protected/config
generate protected/config/main.php
generate protected/config/test.php
generate protected/config/console.php
mkdir /var/www/AdminTest/protected/commands
mkdir /var/www/AdminTest/protected/commands/shell
mkdir /var/www/AdminTest/protected/controllers
generate protected/controllers/SiteController.php
mkdir /var/www/AdminTest/protected/models
generate protected/models/ContactForm.php
generate protected/models/LoginForm.php
mkdir /var/www/AdminTest/protected/extensions
generate protected/yiic.bat
generate protected/yiic.php
mkdir /var/www/AdminTest/protected/migrations

Your application has been created successfully under /var/www/AdminTest.
Среда 2011-03-30 11:03 box:/var/www/AdminTest/yii/framework
claymore:$

Проверяем
 
claymore:$ wget http://127.0.0.1/AdminTest/index.php -q -O - | grep Yii
....
Congratulations! You have successfully created your Yii application.
...

Создаем базу данных admintest. При помощи вашего любимого средства.
 
mysql> create database admintest;
Query OK, 1 row affected (0.02 sec)


Прописываем настройки базы данных в protected/config/main.php


Теперь у нас есть болванка для веб сайта на основе шаблона yii.



Установка модуля.



Переходим в директорию расширений. (Если пропустили первый шаг, замените /var/www/AdminTest/ на путь до папки вашего проекта, содержащей protected)


 
claymore:$ cd /var/www/AdminTest/protected/extensions/

Скачиваем расширение.


 
claymore:$ hg clone -q https://yii-rextensions.googlecode.com/hg/ YiisyCrudAdmin -r YiisyCrudAdmin

[Опционально] удаляем информацию о репозитории, особенно ваш проект уже под системой контроля версий.


 
claymore:$ rm -rf YiisyCrudAdmin/.hg
Среда 2011-03-30 11:03 box:/var/www/AdminTest/protected/extensions

Модуль установлен.



Пример создания crud административной страницы.


Для примера нам понадобится любая модель.


Создаем таблицу в созданной базе данных.


  
mysql> create table example(id int primary key auto_increment, name char(20), is_good_record tinyint(1) default 0 , long_description text);
Query OK, 0 rows affected (0.08 sec)

Переходим в папку protected


  
claymore:$ cd /var/www/AdminTest/protected

Создаем модель.


  
claymore:$ ./yiic shell config/main.php
Yii Interactive Tool v1.1 (based on Yii v1.1.7)
Please type 'help' for help. Type 'exit' to quit.
>> model Example example
generate models/Example.php
generate fixtures/example.php
generate unit/ExampleTest.php

The following model classes are successfully generated:
Example

If you have a 'db' database connection, you can test these models now with:
$model=Example::model()->find();
print_r($model);
>>

Модифицируем модель.


Цель. Отнаследоваться от расширения CActiveRecord объявленному в YiisyCrudAdmin.RActiveRecord. И реализовать абстрактный метод getFieldsDescription


Открываем /var/www/AdminTest/protected/models/Example.php в любимом редакторе


   
Yii::import("ext.YiisyCrudAdmin.RActiveRecord"); //Добавляем эту строчку импорта в верх файла.

В модель Example добавляем новый метод


   
Yii::import("ext.YiisyCrudAdmin.RActiveRecord");
class Example extends RActiveRecord
{
//...
// в последней версии данный метод не требуется в модели, может создаваться по желанию в классе ExampleAdmin
public function getFieldsDescription()
{
return array(
'is_good_record' => 'RDbBoolean',
'long_description' => 'RDbText',
);
}
//...
}

Этот метод предоствляет расширенную информацию о полях модели. На данный момент реализованы пока Bool и Text.


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


Создаем в вашем любимом редакторе controllers/ExampleController.php


    
Yii::import('ext.YiisyCrudAdmin.RController');
class ExampleController extends RController
{
protected $_modelName = 'Example';
}

Тестируем


Открываем в браузере http://127.0.0.1/AdminTest/index.php?r=example/admin


Получаем типичный yii crud с небольшими улучшениями.



  • Поля, отмеченные как boolean, отображаются в виде чекбоксов при редактировании. Также в наличии встроенный фильтр для этих полей на странице actionAdmin.

  • Текстовые поля при редактировании редактируются в textarea. При просмотре в griidview обрезаются. (Пока и в view обрезаются, но это уже решаемый вопрос :) )


Из плюсов такого подхода, все сделано стандартными средствами yii. Т.е. после этапа прототипирования, вы уже можете использовать существующую базу кода, достаточно заглянуть в RController


Гибкость, например в методе getFieldsDescription модели, элеметом массива может быть массив, формирующий CGridColumn, в этом случае все обрабатывается в обычном для yii ключе.


Улучшаем внешний вид


Как поменять порядок полей в actionAdmin ?


Прямо в файле модели создаем класс


    
Yii::import('ext.YiisyCrudAdmin.RModelAdmin');
class ExampleAdmin extends RModelAdmin
{
public function getAdminFields()
{
return array(
'name', 'long_description', 'is_good_record', 'id'
);

}
}

После этого в actionAdmin наблюдаем поля в указанном порядке

.

Как исключить поле из отображения в actionAdmin ?


   
Yii::import('ext.YiisyCrudAdmin.RModelAdmin');
class ExampleAdmin extends RModelAdmin
{
public function getAdminExcludedFields()
{
return array(
'id',
);
}
}

И это все. Оба метода можно комбинировать.



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


  
public function getViewExcludedFields();// список полей для исключения в actionView

public function getAdminExcludedFields();//список полей для исключения в actionAdmin

public function getViewFields();// список полей для отображения в actionView

public function getSearchFields();//список полей для отображения в расширенной форме поиска actionAdmin

public function getAdminFields();// список полей для отображения в actionAdmin

public function getFormFields();// список полей для создания/редактирования.


Почему методы а не массивы? Например для динамической генерации в зависимости от условий.




UPDATED:


В последней версии метод getFieldsDescription перенесен из модели в ModelAdmin класс.

Thursday, August 26, 2010

Немного об оптимизации веб-сайтов. часть 1.

Большую часть работы я трачу на поддержку и развитие уже написанных сайтов. Немалую долю времени при поддержке приходится тратить на оптимизацию. Именное ей и будет посвящен набор статей виде отдельных историй об одном абстрактном программисте.


Этап 1 - становление.


Опыт 1.


Начинающий веб программист получает заказ на небольшой сайт. На удивление сайт завершен за 2 недели до сроков, даже с учетом затребованных изменений. Программист понимает что код его далек от идеала, многие вещи реализованы не оптимально. Принято решение заняться оптимизацией сайта. Сказал - сделал. Время генерации сайта до оптимизации 0.02 секунды, время генерации сайта после оптимизации 0.015 секунды. Т.е. страница генерируется на 25% быстрее. Итак по мнению программиста сайт обслужит на 25% больше пользователей.


Проверка.


Пусть следующий скрипт эмулирует сайт до оптимизации.



$echo “<?php usleep(20000);” > /var/www/site_before.php

А следующий после:



$echo “<?php usleep(15000);?>” > /var/www/site_after.php

Запускаем проверку.



$ ab -q -n 1000 -c 150 http://127.0.0.1/site_before.php | grep "Requests per second"
Requests per second: 64, 3[#/sec] (mean) (усреднено по трем замерам)


$ ab -q -n 1000 -c 150 http://127.0.0.1/site_after.php | grep "Requests per second"
Requests per second: 64 [#/sec] (mean) (усреднено по трем замерам)

Результат.


Прироста в 25% нет. Есть падение производительности в рамках погрешности.


Предположение.


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


Подтверждение



$echo “<?php ” > /var/www/site_null.php
$ ab -q -n 1000 -c 150 http://127.0.0.1/site_null.php | grep "Requests per second"
Requests per second: 67 [#/sec] (mean) (усреднено по трем замерам)

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


Объяснение.


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


Резюме.


При выполнении оптимизации вы должны быть четко уверены в том что она нужна.
Перед оптимизацией кода будьте уверены что проблема именно в нем. Есть много способов оптимизации сайта, которые не затрагивают исходные коды.


Мое мнение.


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

Thursday, May 27, 2010

php type hinting

Навеяно http://habrahabr.ru/blogs/php/94714/


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



Идеология. Проверка типов должна быть легко отключаема, или поведение на несоотвестие типов должно легко подменяться.



Набросок использования предполагаемого решения для проверки типов данных на примере Yii framework. (В принципе от yii тут только component, что например в Zend может быть сделано на Registry, если там ничего не появилось нового, или на Toolkit в limb, ну или уж в крайнем случае singleton, глобальные переменные)



class AnyClass
{
/**
* anyFunction description
*
* @param integer $arg1
* @param string $arg2
*/
public function anyFunction($arg1, $arg2)
{
Yii::app()->typeHintingComponent->assertFunctionArgs($this,'anyFunction' get_funciton_args());
//orYii::app()->typeHintingComponent->assertFunctionArgs(__CLASS__,__METHOD__, get_funciton_args());
}


Ожидаемое поведение : в случае если тип $arg1 не integer или тип $arg2 не string или полученное количество аргументов не совпадает, мы получаем например RuntimeException.



Реализация :


  • По классу и имени метода получаем Reflection Method

  • У Reflection Method получаем docsting

  • Из dostring вытаскиваем типы данных

  • Проверяем

  • Делаем что душа пожелает, тут можно использовать либо стратегию (оно же вызывать call_back) , либо наследников с переопределением обработки ошибок, чтобы менять поведение.




В моем примере поведение меняется заданием component в конфиге.



В результате получаем отключаемую проверку типов, и можем управлять поведением.



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

Мое решение это написать функцию обертку что берет эти данные из debug_backtrace


//docstring
public function func($arg1, $arg2)
{
Yii::app()->typeHintingComponent->assertFunctionArgs();
}

//...
public function assertFunctionArgs()
{

$backTrace = debug_backtrace();
if ( ! isset($backTrace[1]['object'], $backTrace[1]['function'], $backtrace[1]['args']))
throw new RCAssertException(/* Details */);

$obj = $backTrace[1]['object'];
$function = $backTrace[1]['function'];
$args = $backTrace[1]['args'];
//далее по предыдущему алгоритму

}



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



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

Зато самое большое преимущество это возможность проверять даже не существующие типы

/**
* @param mixed $var ну и нафига проверка типов если используются такие переменные? :)
* @param string|integer $var2 тоже смешно, но на промежуточном этапе рефакторинга может пригодится
*/



На практике у меня это не прижилось т.к. пытался насадить это вместе с кучей других требований и это не было приоритетным, возможно теперь перейдя на yii framework и реализовав это в виде компоненты у меня получится это сделать со второй попытки. Но тем не менее мне пришлось при рефакторинге одного запутанного кода использовать эту наработку активно, просто в том проекте одни и те же функции/методы использовались для разных целей, и соотвественно передаваемые типы данных были непредсказуемыми.

Friday, March 26, 2010

Забавный php5.3

В debian testing неожиданно для меня появился 5.3, спустя час после этого "счастья", LAMP заработал как и раньше. Захотелось экспериментов, вот что в итоге получилось, на мой взгляд забавно для php.

<?php

class BaseObject extends stdClass
{
    public function 
__call($method$args = array())
    {
        return 
call_user_func_array($this->$method$args); //call closure
    
}
}

/**
 * Create className instance add getIncrementedAge, getName behaviours
 * 
 * @param string $className
 * @param integer $age
 * @param string $name
 * @return object same type as $className 
 */
function Type() 
{
    
    
$props func_get_args();
    
$className array_shift($props);//classname should be first 
    
    
$refFunction = new ReflectionFunction('Type');
    
$props array_combine(// make dictionary from function arg names (from phpdoc) and function arguments
        
array_map(//extract variable names from phpdoc @param line
            
function ($docString) { //search variable name and return it 
                
preg_match('/\$(\w+)/',$docString$data);
                return 
$data[1]; 
            },
            
array_filter//make lines array from phpdoc, filtering all except strings with @param and skip string with $className 
                
explode("\n"$refFunction->getDocComment()), 
                function (
$line
                { 
                        return 
strpos($line'param') > 0  && strpos($line'$className' ) < 1  
                } 
            ) 
        ), 
        
$props 
    
);
    
    
$ob = new $className(); 

    foreach ( 
$props as $name => $value
        
$ob->$name $value
        
    
$ob->getName = function () use($ob)  
    {
        return 
$ob->name
    };
    
    
$ob->getIncrementedAge = function ($inc =1) use($ob)  
    {
        return 
$ob->age $inc
    };
    
    return 
$ob;  
}

$cd Type('BaseObject'10,'cd');
$stas Type('BaseObject'20'stas');

var_dump($cd->getName());
var_dump($cd->getIncrementedAge(2));
var_dump($stas->getName());
var_dump($stas->getIncrementedAge(3));





Результат

php test.php
string(2) "cd"
int(12)
string(4) "stas"
int(23)

Wednesday, August 12, 2009

php и циклические ссылки

После отпуска пришлось оптимизировать небольшое консольное приложение, работающее в рамках нашего проекта, исходное время выполнения было 2.5 часа.

После беглого просмотра кода и сопутствущих классов, время было оптимизировано до минуты. Проблема была в том, что первоначальная версия была написана не очень хорошим программистом, а остальные ленились кардинально переписать приложение. Также для генерации отчета в циклах использовались классы, которые на многие действия совершали запросы к базе, а вызывались эти методы внутри цикла. Использовав упреждающее чтение всех необходимых данных в память и минимальную модификацию классов для использования получанных данных в случае их наличия, приложение стало работать гораздо быстрее (1 минута из которой 40 секунд происходит чтение всех необхомых данных в память, 20 секунд непосредственно обработка и запись данных). Ну а количество запросов сократилось до 3-х вместо 3*количество записей в обрабатываемых таблицах.

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

Вторая проблема которую я увидел это то, что это приложение было по сути копипастом из одного часто используемого метода, в итоге переписав и то, и другое, я получил скрипт, который исправно работал, но в 10 раз медленнее минутной версии, но обладал замечательным свойством, что был готов для повторного использования, стал более гибким, и читать его стало проще. Однако возникла следующая проблема, уже на 200.000 записях он стал отжирать 4 гб памяти и операционной системе это очень не нравилось, впрочем как и мне. Почистив код, я убедился что происходит утечка памяти.

Вот код, который вольно воспроизводит сложившуюся ситуацию.

 

class DataSource // в моем случае это был итератор по таблице базы данных
{
protected $_counter = 1;
public function next()
{
return array('xxxxxxxxxxxxxxxxxxxxxxxx' => 'yyyyyyyyyyyyyyyyyyy');
}
}

class UserHelper // род классов, для получения данных связаных с пользователями, но выходящих за рамки этого объекта
{
protected $_u = null;
public function __construct(User $u)
{
$this->_user = $u;
}
public function getSomeData()
{
return array('x' => 10, 'y' => 20);
}

public function __desctruct()
{
$this->_u = null;
}
}

class User
{
protected $_dbRow = array();
protected $_helpers = array();
public function __construct(array $data )
{
$this->_dbRow = $data;
}

public function getHelper()// получения хелпера, в данном случае ситуация урезана до одного
{
if ( isset($this->_helpers['x'] ) )
return $this->_helpers['x'];
return $this->_helpers['x'] = new UserHelper($this); // автор посчитал это полезным
}

public function __destruct()
{
unset($this->_dbRow);
$this->_helpers['x'] = null;
}
}

// кусок кода вызвавший проблему.
$ds = new DataSource();
for($i= 1; $i++; $i< 1000000)
{
$u = new User($ds->next()); // загружаем пользвоателя напрямую по данным
$helper = $u->getHelper();
$helperData = $helper->getSomeData();// получаем данные хелпера
//делаем то что нужно с данными
unset($helperData);
unset($helper);
unset($u); // думаем что убили пользователя
}


При запуске этого скрипта и запущенном топе, видим как память отжирается без остановки.

Проблема в циклических ссылках. Не охота это описывать можно почитать ТУТ

После того как я нашел утечку. Код переписан в таком виде


$ds = new DataSource(); // получаем итератор
for($i= 1; $i++; $i< 1000000)
{
$u = new User($ds->next()); // загружаем пользвоателя напрямую по данным
//$helper = $u->getHelper(); вот тут и была проблема.
$helper = new UserHelper($u);
$helperData = $helper->getSomeData();// получаем данные хелпера
//делаем то что нужно с данными
unset($helper);
unset($u); // думаем что убили пользователя.
}



После этого, время выполнения приложения стало чуть больше 20 % от первоначальной минуты, ну а раход памяти стал фиксированным ~300-400 MB

Вывод: Консольные приложения обычно пишутся для больших объемов данных, и то что прощается при выполнении маложивущей веб-страницы, выходит боком при долгой работе приложения.

Sunday, May 3, 2009

Неоднозначности PHP

За три месяца молчания накопились "прелести" PHP с которыми можно и поделиться.
Меньше слов больше консоли.

Для начала кусок докуметации о сравнении массивов


Для массивов $a и $b
$a == $b Равно TRUE в случае, если $a и $b содержат одни и те же элементы



php -a
php > $a = array(1,2);
php > $b = array(2,1);
php > var_dump($a == $b);
bool(false)


Для меня это оказалось несколько неожиданным изходя из документации, однако следущий код неожиданно вносит ясность.


php > class X {};
php > $x1 = new X();
php > $x2 = new X();
php > $a = array($x1, $x2);
php > $b = array($x2, $x1);
php > var_dump($a == $b);
bool(true)


В принципе все это объяснимо, во втором примере используются ссылки, и объекты (суть элементы) действительно одни и теже, в первом же используются числа и думаю во внутреннем представлении это все таки разные эелементы, но отсуствие концепции в целом все таки настораживает.

Еще замечательный пример с которым я столкнулся в практике.

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


php > $x = $object->getResult();
php > $x->value = 'smth';
php > $x->callSmth();

Возвращал Fatal error: Call to undefined method stdClass::callSmth() in php ... некоторое время я тупил удивляясь откуда взялся stdClass, (и сразу пошел в неверном направлении, греша на serialize при работе с memcache), однако быстро понял что дело не в этом.

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

php > $a = null
php > ;
php > $a->x = 10;
php > var_dump($a);
object(stdClass)#1 (1) {
["x"]=>
int(10)
}


Если провести тесты не с null, а скажем с boolean значением, получем тот же результат, по идее все логично, тогда код со строкой, должен отрабатывать эквивалентно.


php > $b = 'sdf';
php > $b->x = 10;

Warning: Attempt to assign property of non-object in php shell code on line 1


Все страньше и страньше. Ну и на последок забавный код.


php > class null{};#я думал так нельзя
php > class false extends null{};# можно и так
php > class true extends false { function __toString() { return 'false'; } } ; #это уже понятно
php > $false = 'false';
php > $true = new True();
php > var_dump($$true);#ну это уже стеб.
string(5) "false"


Велики возможности PHP !!!

Friday, February 20, 2009

Размышения о php, его применении

Статья о моих метаниях в процессе разработки и правильном использовании PHP.

Для затравки желательно прочитать статью 10 принципов PHP специалистов.

В принципе ничего нового. Теперь что думаю я.

1. PHP медленный язык. И не надо рассказывать о том, что большую часть времени занимает работа с базой, мемкэшем, файлами и т.д.
Даже вызов PHP фукнции достаточно дорогая операция. А уж подключения библиотеки - кошмар.

2. Фреймворки - монструозны, а если это сложить с пунктом один, получаем, что они еще и тормозны по определению.

Исходя из этих требования, а также под угрозами бешеных нагрузок я участвовал в написании проекта, который был минималистичен в плане порождения сущностей. На удобстве и скорости разработки это мало сказалось, т..к была изобретен минимальный фундамент (велосипед), который не давал скатиться в написание портянок. Зато время генерации при небольшой и средней нагрузках было 0.01-0.03 сек. Но т.к. администратор выбрал распределённую фс, это выразилось в подскакивании времени генерации до 0.1 сек.

В ходе этого я выяснил.

3. Проблемы описанные в пп 1,2 неприятны, но решаемы, в основном кэшированием и настройками серверов, или отказом от PHP.

Скажем так, оптимизация веб-сервера (отключение модулей апача, extensions php) дало прирост 10-20%, (смена апача на lighttpd +fcgi) дало прирост еще на 30%. Я бы не смог настолько оптимизировать код по всему проекту.

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

В итоге я ударяюсь (контролируемо) в другую крайность, в правильную разработку. Т.е. играемся с проектированием на всю катушку. Начинаем смотреть в сторону ОРМ уже в качестве необходимости.


Прошло время я наигрался и тем, и с другим. Сразу условлюсь что проект я предлагал делать на java, но руководство выбрало php по понятным всем причинам.

А теперь пример, который просто меня убил.

Задача : написать ладдерную систему для неких игр. Грубо говоря чем с более крутым человеком ты играешь, тем больше очков ты получишь, тем выше будет твой рейтинг.

Итак, есть игрушки, которые потенциально кидают много запросов. Есть рейтинг игроков который нужно пересчитывать по определенным формулам.

Реализация на php:

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


Реализация на python:

Twisted server, который в памяти хранит пользователей, отсортированных по результатам. При новых результатах, пользователь удаляется из отсортированного листа, ему начисляется новый скоринг, и он вставляется в список. Сам сервер по xmlrpc умеет отдавать результаты - веб серверу.
Веб сервер работает на старом движке, вместо запросов к базе - запросы к xmlrpc-server.

Реализация - быстрее чем на php(по времени разработки и отладки), скорость работы - выше, удобство использования - выше.

Самый большой плюс, система работает online!

Из недостатков - новая сложность системы в виде нового сервера.

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


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

Sunday, February 8, 2009

PHPUnit, DBUnit, тестирование или Class PHPUnit_Extensions_Database_TestCase could not be found

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

Вместе с внедрением тестирования, пытаюсь при необходимости покрывать тестами более старые участки кода. В качестве framework был выбран PHPUnit за интеграцию с Zend Studio + некоторые приятные вещи.


В процессе написания тестов возникла необходимость в инструменте для работы с fixtures + тестирования изменений в базе данных, DBUnit как extension к PHPUnit подошел замечательно.

Далее грустная история о нескольких часах дебага. И об ошибке "Class PHPUnit_Extensions_Database_TestCase could not be found"

1. Изначально был тест XXX_Test


require_once 'PHPUnit/Framework/TestCase.php';
class XXX_Test extends PHPUnit_Framework_TestCase
{

public function testSmth()
{
$this->assertTrue(true);
}
}


Файл назван xxxTest.php

За некоторое время работы с phpunit я отвык пользоваться формой запуска

$phpunit TestClassName path/to/test/file.php


И пишу просто


$phpunit path/xxxTest.php
PHPUnit 3.3.10 by Sebastian Bergmann.

..

Time: 0 seconds

OK (1 test, 1 assertion)


Т.е. все хорошо, мой класс несколько шире, и мне хочется тестировать его на основе PHPUnit_Extensions_Database_TestCase, для чего пользуюсь туториалом отсюда

В итоге класс становится таким.


require_once 'PHPUnit/Extensions/Database/TestCase.php';
class XXX_Test extends PHPUnit_Extensions_Database_TestCase
{

protected function getConnection()
{
return null;// $this->createDefaultDBConnection($this->_dbObject,'sqlite');
}

protected function getDataSet()
{
return '';//$this->createFlatXMLDataSet('path/to/fixtures/dummy.xml');
}

public function testSmth()
{
$this->assertTrue(true);
}
}


Функции коннекта и загрузки данных, преднамеренно реализованы криво.

Запускаем.

$phpunit path/xxxTest.php

О***ваем

PHPUnit 3.3.10 by Sebastian Bergmann.

Class PHPUnit_Extensions_Database_TestCase could not be found in /var/www/toox.com.lottery/core/classes/db/query/Rapid_Db_Query_IteratorTest.php


Я повяз в дебаге на пару часов, т.к. это был мой первый опыт работы с DBUnit + также приведенный пример сильно упрощен по сравнению с тем файлом, с которым я работал.


В итоге оказалось, что при работе с DBUnit обязательно использовать полную форму запуска phpunit

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


$phpunit XXX_Test path/xxxTest.php

PHPUnit 3.3.10 by Sebastian Bergmann.

E

Time: 0 seconds

There was 1 error:

1) testSmth(XXX_Test)
Argument 1 passed to PHPUnit_Extensions_Database_DefaultTester::__construct() must implement interface PHPUnit_Extensions_Database_DB_IDatabaseConnection, null given, called in /usr/share/php/PHPUnit/Extensions/Database/TestCase.php on line 146 and defined



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

Tuesday, October 28, 2008

PHP, Excetion облегчают обработку форм. Ностальгия блин.

Теряя время в интернете на поиск интересных блогов связанных или с php или с веб программированием так, чтобы я мог увидеть в этом что то интересное для себя, набрел на блог http://alt-f4.ru. Поковырял статьи, особо сказать ничего не могу, кто-то делиться своим опытом, просто времена когда мне бы его опыт пригодился бы остались в прошлом.

В итоге набрел на статью "Авторизация посетителей на PHP", в которой по шагам, потихонечку написано как кому чего делать чтобы у него было счастье.

Ну а теперь самое интересное, наткнулся на код обработки формы.


if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$rLogin = trim($_POST['rLogin']);
$rPass = trim($_POST['rPass']);
$rPass2 = trim($_POST['rPass2']);
$rEmail = trim($_POST['rEmail']);
if ($rLogin == '') {
die("Поле 'Логин' не заполнено\n");
// Логин может состоять из букв, цифр и подчеркивания
}elseif (!preg_match("/^\w{3,}$/", $rLogin)) {
die("В поле 'Логин' введены недопустимые символы\n");
}
if ($rEmail == '') {
die("Поле 'E-mail' не заполнено\n");
// Проверяем e-mail на корректность
}elseif (!preg_match("/^[a-zA-Z0-9_\.\-]+@([a-zA-Z0-9\-]+\.)+[a-zA-Z]{2,6}$/", $rEmail)) {
die("Указанный 'E-mail' имеет недопустимый формат\n");
}
if ($rPass == '' || $rPass2 == '') {
die("Поле 'Пароль' не заполнено\n");
}elseif($rPass !== $rPass2) {
die("Поля 'Пароль' и 'Повтор пароля' не совпадают\n");
// Пароль может состоять из букв, цифр и подчеркивания
}elseif(!preg_match("/^\w{3,}$/", $rPass)) {
die("В поле 'Пароль' введены недопустимые символы\n");
}
// В базе данных у нас будет храниться md5-хеш пароля
$mdPassword = md5($rPass);
// А также временная метка (зачем - позже)
$time = time();
// Устанавливаем соединение с бд(не забудьте подставить ваши значения сервер-логин-пароль)
$link = mysql_connect('localhost', $dbuser, $dbpass);
if (!$link) {
die("Не могу соединиться с базой данных");
}else {
// Выбираем базу данных
mysql_select_db('authorize', $link);
// Записываем в базу (не используем addslashes - экранировать нечего)
mysql_query("INSERT INTO users (login, pass, email, timestamp)
VALUES ('$rLogin','$mdPassword','$rEmail',$time)",$link);
if (mysql_error($link) != "") {
die("Пользователь с таким логином уже существует, выберите другой\n");
}
echo "Юзер добавлен\n";
mysql_close($link);
}
}
?>


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

Самое что главное, если заменить способы валидации, то к своему стыду получается что я писал подобный код не так давно. И только натолкнувшись на совсем сумасшедшую логику, прикрутил Exceptions и чудо
1. Объем кода уменьшился в два раза.
2. Количество вложенных условий сократилось до 1-го
3. Ошибки стало проще выводить.

Ну и применительно к данному коду, применя преобразовния не меняющие логику получим что то вроде. .


class DbException extends Exception {};

if ($_SERVER['REQUEST_METHOD'] != 'POST')
{
try
{
foreach ( array('rLogin' => 'Логин' , 'rPass' => 'Пароль' , 'rPass2' => 'Повторный пароль', 'rEmail' => 'E-mail' ) as $val => $fieldName)
{
if ( isset($_POST[$val] ) )
$val = trim($_POST[$val]); //create rLogin, rPass, rPass2, rEmail variables
else
throw new Exception("Поле $fieldName не заполнено\n");
}

if (!preg_match("/^\w{3,}$/", $rLogin))
throw new Exception("В поле 'Логин' введены недопустимые символы\n");

if (!preg_match("/^[a-zA-Z0-9_\.\-]+@([a-zA-Z0-9\-]+\.)+[a-zA-Z]{2,6}$/", $rEmail))
throw new Exception("В поле 'Логин' введены недопустимые символы\n");

if($rPass !== $rPass2)
throw new Exception("Поля 'Пароль' и 'Повтор пароля' не совпадаютn");

if(!preg_match("/^\w{3,}$/", $rPass))
throw new Exception("В поле 'Пароль' введены недопустимые символы\n");

$mdPassword = md5($rPass);
// А также временная метка (зачем - позже)
$time = time();
// Устанавливаем соединение с бд(не забудьте подставить ваши значения сервер-логин-пароль)
$link = mysql_connect('localhost', $dbuser, $dbpass);
if (!$link)
throw new DbException("Не могу соединиться с базой данных");
// Выбираем базу данных
mysql_select_db('authorize', $link);
// Записываем в базу (не используем addslashes - экранировать нечего)
mysql_query("INSERT INTO users (login, pass, email, timestamp) VALUES ('$rLogin','$mdPassword','$rEmail',$time)",$link);// поле timestamp можно сделать чтобы по умолчанию подставлялось базой, такие вещи лучше перекладывать на базу.
if (mysql_error($link) != "")
DbException("Пользователь с таким логином уже существует, выберите другой\n");
echo "Юзер добавлен\n";
mysql_close($link);
}
catch (DbException $e)
{
die("DB ERROR: " . $e->getMEssage());
}
catch ( Exception $e)
{
die( "Form validation error: ". $e->getMessage());
}
}
?>

В итоге.

  • Кода меньше.

  • Читается проще.

  • Вывод ошибок в одном месте.

  • При необходимости разные виды ошибок ловим в нужных нам местах.



Monday, October 27, 2008

Как я люблю php, или танцы с false

Еще одна из кривостей моего любимого php. Писать не буду, приведу пример интерактивного шела.
Cюрприз #2

pcd@cd-laptop:~$ php -a
php > error_reporting(E_ALL);// устанавливаем уровень ошибок,

php > $ar = array(); // создаем массив
php > var_dump($ar[1]); // обращаемся к несуществующему индексу
Notice: Undefined offset: 1 in php shell code on line 1//логично
NULL

php > $null = null;// создаем переменну инициализированную нулом
php > var_dump($null[1]);// обращаемся к нему как к массиву
NULL// ошибки нет, это уже не совсем логично, т.к.

php > var_dump( (array) $null ) ; // при преобразовании нула в массив
array(0) {
}// получается пустой массив без индекса 1.
//ну хотя можно как то объяснить, т.к. null это особый тип.

php > $false = false;// boolean переменная, инициализированная ложью
php > var_dump($false[1]);// обращение как к массиву при этом к несуществующиему индексу
NULL// а как объяснить это????

php > var_dump((array)$false);// смотрим как ложь приводится к массиву.
array(1) {
[0]=>
bool(false)
}// этого я не ожидал
// но тогда получается что ...

php > echo (array) false ? 'true' : 'false';
true // ну а это просто п***ц, сколько человек могло попасть так!


Ну и навскидку надуманный пример, похожий на правду.



cd@cd-laptop:~$ cat ex1.php
class ExampleDb
{

/**
* This function return list of users as array,
* empty array on failure
*/
public function findAll($testValue = true)
{
return (array ) $this->_fetch_all($testValue);
}
protected function _fetch_all($testValue = true )
{
$goodData = array(
0 => array(
'name' => 'vasya',
'pass' => 'oyamd5withsolt',
),
1 => array(
'name' => 'vasay',
'pass' => 'oyamd5withsolt2',
),
);
try
{
;//do smth
}
catch (Exception $e)
{
return false; //strange architector
}

$retValue = $testValue ? $goodData : false;
var_dump($retValue);
return $retValue;

}
}

$ob = new ExampleDb();
$users = $ob->findAll(false);// error happens !

//strange dev
if ( isset($users[0]) )
echo "You win! Your prize 10000k euros from dev salary!";


?>
cd@cd-laptop:~$ php ex1.php
(bool)false // мы видим что была ошибка
You win! Your prize 10000k euros from dev salary!cd@cd-laptop:~$
// но тем не менее кто то выйграл.
//Ну что теперь 10 лет работаем на еду?

Friday, October 24, 2008

PHP, PDO, веселье при наследовании и включенной опции persistent connect

За что я люблю PHP?

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

Этой статьей открою растянутый список статей в которых опишу подводные камни удобно предоставленные PHP нашей команде.

Cюрприз #1

В проекте есть класс DB, отнаследованный от PDO. Было обнаружено неадекватное поведение объектов. 3 часа ломания мозга дали следующий тестовой код воспроизводящий проблему.


КОму интересно, попробуйте угадать результат

ini_set ( 'display_errors', 'on' );
error_reporting ( E_ALL );

class MyDB extends PDO {
public $connectionId = 0;
}

class MyFactory {

protected static $_storage = array ();

static public function get($id) {
if ( isset ( self::$_storage[$id] ))
return self::$_storage[$id];

self::$_storage[$id] = new MyDB ( 'mysql:host=127.0.0.1;dbname=toox2;', 'root', '123', array (PDO::ATTR_PERSISTENT => true ) );
self::$_storage[$id]->connectionId = $id;
return self::$_storage[$id];
}
}

$db1 = MyFactory::get( 1 );
$db2 = MyFactory::get( 2 );

var_dump( $db1 );
var_dump( $db2 );




Ну а для тех кто думал ровно как и я что в результате $dbq->connectionId и $db2->connectionId буду разными привожу код


cd@cd-laptop:~/tmp/php-tests$ php test.php
object(MyDB)#1 (1) {
["connectionId"]=>
int(2)
}
object(MyDB)#2 (1) {
["connectionId"]=>
int(2)
}



Проблема в

array (PDO::ATTR_PERSISTENT => true )


Если его убрать, то поведение становится ожидаемым, но однако, почему persistent connect ломает ооп в пхп :)

А ну и на закуску, хотя из приведенного кода выше это видно

var_dump($db1 === $db2) === false

Кому интересно

cd@cd-laptop:~/tmp/php-tests$ php -v
PHP 5.2.4-2ubuntu5.3 with Suhosin-Patch 0.9.6.2 (cli) (built: Jul 23 2008 06:44:49)
Copyright (c) 1997-2007 The PHP Group
Zend Engine v2.2.0, Copyright (c) 1998-2007 Zend Technologies


На slackware товарищ воспроизводил проблему также.

Friday, October 17, 2008

Php, не все так плохо как кажется на первый взгляд.

   С полгода назад, пришлось реализовать для удобства IArrayAccess. И только после реализации решил замерить насколько оно медленнее массива, и запомнилась мне следующая удивительная цифра. Что то на 10.000 итераций Объект реализовавший IArrayAccess был медленнее на десяток секунд. (На не сильно слабой машине).


   Решил сегодня это печальное событие задокументировать с цифрами.  Результат приятно удивил.

Исходники
array.php

error_reporting(E_ALL);
$array = array();

for ( $k = 1; $k < 10; $k++)
{
unset($array);
$array = array();

for ($i = 0 ; $i < 100000; $i++)
{
$array[$i] = $i + $k;
}
}
?>




spl-array.php

class SplArrayTest implements ArrayAccess
{
protected $_data;

public function __construct()
{
$this->_data = array();
}

public function offsetExists($offset)
{
return isset($this->_data[$offset]);
}

public function offsetGet($offset)
{
return $this->_data[$offset];
}

public function offsetSet($offset, $value)
{
$this->_data[$offset] = $value;
}
public function offsetUnset($offset)
{
unset($this->_data[$offset]);
}

public function __destruct()
{
unset($this->_data);
}
}


$array = new SplArrayTest();

for ( $k = 1; $k < 10; $k++)
{
unset($array);
$array = new SplArrayTest();

for ($i = 0 ; $i < 100000; $i++)
{
$array[$i] = $i + $k;
}
}

?>



spl2-array.php

class SplArrayTest implements ArrayAccess
{
public function __construct()
{
}

public function offsetExists($offset)
{
return isset($this->$offset);
}

public function offsetGet($offset)
{
return $this->$offset;
}

public function offsetSet($offset, $value)
{
$this->$offset = $value;
}
public function offsetUnset($offset)
{
$this->$offset;
}

public function __destruct()
{
}
}


$array = new SplArrayTest();

for ( $k = 1; $k < 10; $k++)
{
unset($array);
$array = new SplArrayTest();

for ($i = 0 ; $i < 100000; $i++)
{
$array[$i] = $i + $k;
}
}
?>



PHP 5.2.4-2ubuntu5.3  (2 ядра, системя с запущенными иксам, не сильно загружена)


Запуск через time php array.php
Среднее время выполнения - 1.6 секунды
Среднее время выполнения при включенном xcache  - 1.3
На нашем прод сервере + apc = 0.5 секунды

Запуск через time php spl-array.php
Среднее время выполнения - 3.9 секунды
Среднее время выполнения при включенном xcache  - 3.72
На нашем прод сервере + apc = 1.2 секунды

Запуск через time php spl2-array.php
Время выполнения 4.7 секунды
Среднее время выполнения при включенном xcache  - 4.2


Выводы.

На 1.000.000 элементарных операций разница всего на 2-3 секунды. На моем ноуте.
Разница в полсекунды на прод сервере.

Для примера в zend framework по умолчанию  порядка 2.000-4.000 таких и не очень элементарных вызовов.
В простейшей реализации страницы на limb
10.000 и выше. (за счет их любимых тулкитов).

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

В итоге бояться использовать SPL  я перестаю, хотя все равно на душе будет не спокойно.



Tuesday, September 23, 2008

Конвертирование ошибок в exceptions.

Задача
Преобразовать все ошибки в исключения (exceptions) для более удобной их обработки.
Решение подсмотрено на php.net в комментариях.

class My_Exception extends Exception {
public static function errorHandlerCallback($code, $string, $file, $line, $context)
{
$e = new self($string, $code);
$e->line = $line;
$e->file = $file;
$e->context = $context;
throw $e;
}

}

set_error_handler(array("My_Exception", "errorHandlerCallback"), E_ALL);



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



// bla bla bla
try
{
$controller->dosmth();
}
catch( Controller_Wrong_Data_Exeption $e)
{
// do smth
}
catch ( My_Exception $e)
{
// we know it's php error
}



Такой метод не ловит фаталы, а очень хочется. Где то встречал решение с использованием особенности ob_start, ob_flush, если найду завтра добавлю.

Monday, September 22, 2008

Php, агрегация, псевдонаследование

Задача на примере.
 Есть класс View. Хочется создать класс Cache_View который будет агрегировать внутри себя объект класса $view, а внешне иметь такой же интерфейс как и класс view.

Понятно что на это многие покрутят пальцем у виска, отправят учить ооп, и скажут что нибудь про наследование. Для остальных продолжу мысль.

Простейшая реализация (наследование не подходит, нужно агрегировать)- агрегировать, и определить кучу методов вида


public function xxx($param1,$param2)
{
return $this->agregatedObject->xxx($param1, $param2);
}
?>



Реализация где не придется реализовывать эту кучу методов.


class A
{
public function f1()
{
}
}

class B
{
protected $_a = null;
public function __construct(A $a)
{
$this->_a = $a;
}
public function __call($function, $params)
{
return call_user_func_array(array(&$this->_a, "A::$function"), $params );
}
}

//как работает
$a = A();
$b = B($a);

$a->f1();
$b->f1();// тоже самое что и строчкой выше. Произойдет вызов метода f1 объекта $a
?>


Если кому то еще интересно, ниже пример использования.

class View
{
public function render($template)
{
//return html
}
public function f1()
{
}
//...
public function f120()
{
}
}

class Cache_View
{
protected $_view = null;
protected $_cache = null
public function setCache($cache)
{
$this->_cache = $cache;
}
public function __construct(View $v)
{
$this->_view = $v;
}
public function __call($function, $params)
{
return call_user_func_array(array(&$this->_view, "View::$function"), $params );
}

public function render($template)
{
if ( $this->_cache != null && ! $this->_cache->mis($this->makeKey($template))// если объект кэша есть и в нем чего то лежит
return $this->_cache->get($this->makeKey($template));// то вернем закэшированные данные

$out = $this->_view->render($template);
if ( $this->_cache != null)
$this->_cache->set($this->makeKey($template), $out);
return $out;
}

public function makeKey($str)
{
retirm md5($str);
}
}

//примеры исползования

$view = View();
$cache = Cache::create(Cache::File);
$cachedView = Cached_View($view);
$cachedView->setCache($cache):

$view->test();
$cachedView->test();// тоже самое что и строчкой выше. Вызов метода агрегированного класса
$cachedView->render('splash.php'); // а вот здесь будет предприняты попытка заглянуть в кэш и вернуть закэшированные результаты, если их нет, то произойдет отработка обычного $view->render и результат положится в темплейт
?>


Пример реализации чего то похожего внешне на множественное наследование



class Base()
{
public function base_function() {};
}

class A
{
public function f1()
{
}
}

class B extends Base
{
protected $_a = null;
public function __construct(A $a)
{
$this->_a = $a;
}
public function __call($function, $params)
{
return call_user_func_array(array(&$this->_a, "A::$function"), $params );// понятно что тут можно проверять наличичие метода поочереди у пула объектов, и вызывать их
}
}

$a = new A();
$b = new B($a);
$b->base_function(); //ok child Base
$b->f1(); // looks like child A

Wednesday, September 17, 2008

Простые вещи, php

При работе с проектом хотелось не слишком много, просто отслеживать время выполнения скриптов, а также количество вызвов скриптов.
   Простое решение - в конце каждого скрипта ( у нас правда это один index.php) пишется в файл uri + и время выполнения скрипта.

  Однако выяснилось, что из за кусков кода вроде этого

header("Location: bla bla bla");
exit();

Некоторые вещи просто не ловятся. А очень хочется понять не в них ли вся проблема :)

Решение оказалось простым. Использовать

register_shutdown_function('callback');

Например в нашем проекте есть класс Rapid в котором лежат жизненно необходимые нам вещи.

class Rapid
{
//...
  public static function quit()
  {
    //write to log
  }
//..
}
// тут как раз регистрируем статическую функцию, как функцию по завершению
register_shutdown_function(array('Rapid', 'quit'));

Ньюанс : эта функция вызвается даже после php fatal error!

cd@cd-laptop:~/tmp$ cat 2.php


register_shutdown_function('quit');
echo 'start';
require_once('none.php'); // у меня нет файла, будет фатал.
echo 'end';
function quit()
{
    echo 'quit';
}


cd@cd-laptop:~/tmp$ php 2.php
start
Warning: require_once(none.php): failed to open stream: No such file or directory in /home/cd/tmp/2.php on line 7
Fatal error: require_once(): Failed opening required 'none.php' (include_path='.:/usr/share/php:/usr/share/pear') in /home/cd/tmp/2.php on line 7
quit
cd@cd-laptop:~/tmp$

Sunday, November 4, 2007

Тест

Забавно http://habrahabr.ru/blog/php/29229.html#habracut
updated
ссылко закрыто, смысл такой : мамбо разработчики выложили тест, пройдя который можно к ним устроицо на работу.
Тому кто приведет программиста к ним приз 500 баксов.
Подробнее http://www.corp.mamba.ru/test/promo.phtml


Учиться, учиться и еще раз учиться.
Имхо завалился на битовых операциях, немножко на sql, шеле, svn.

Updated
Уточнил в мане svn annotate, blame


На мамба программиста не тяну :)

updated

Простой скрипт который выводит все результаты до 2518 попытки


 <?php
 
for ($i 1$i 2518$i++)
 {    
     echo 
'<img border="0" src="http://www.corp.mamba.ru/test/widget.phtml?id='.$i.'" />';
 } 
?>




Т.к. айдишники открытые, то соотвественно можно подобрать себе изображение супер крутого, например : http://www.corp.mamba.ru/test/widget.phtml?id=118

Thursday, November 1, 2007

Транзакции в mysql + pdo

Не работал PDO::rollBack();

Решение.

1. Таблица обязательно innoDb или DBD (мануал mysql)

После этого ничего не изменилось.

2. Перед транзакцией нужно set autocommit = 0; после set autocommit = 1 (mysql manual)
В итоге

<?php
/**
* @var PDO $db
*/
/


$db->exec('set autocommit=0');
try
{
    if ( 
false === $db->beginTransaction() )
    throw new 
Exception('bla bla bla ');
    
// do smth
    
$db->commit();
}
catch ( 
Exception $e)
{
    
$db->rollBack();
    
$logger->error$e->getMessage ) ;
}
$db->exec('set autocommit=1');
?>


Monday, October 15, 2007

eclipse pdt vs ZendStudio Neon

Вначале об eclipse pdt.
Пересел на него сразу после релиза с редактора vim.
Недостатки.

  • Не интуитивные настройки, особенно после zend studio <= 5.5. Или по сравнению с .vimrc

  • Медленный, особенно перерисовка окон ( со временем сгладилось)

  • Не смог настроить Database Development

  • Нет удаленного сайта, sshfs которым я восторгался - не решение, т.к. eclipse сканит все файлы, и все просто тормозит.

  • Subversion plugin кривой, часть видов выдает exception, не смог сделать проект из репозитария

  • Очень долго настраивал дебагер (xdebug) к сайту с modrewrite, через 3 дня заработало, больше похоже на танцы с бубном.

  • Стремный редактор js,css

  • Хотел поставить aptana плагином, все поломалось.


Понравилось

  • Project outline ( не работает если проект из репозитория)

  • Показ ошибок прямо в броузере проекта.

  • Поддержка дебагера

  • Бесплатный


А все остальное стандартно вроде.

Поставил ZendStuio Neon (beta)

Блин вот это то, чего я так долго ждал.
По сути тот же pdt, только доведенный до ума, работает по сути из коробки.
Особенно понравилась поддержка PhpUnit + Автоматическое создаение TestSuite на основе всех тестов в проекте.

Из минусов пока видел только поддержку зендовского дебагера, а также проблема при работе с database development перспективой ( неправильная кодировка символов, ломается на части таблиц)

Резюме:
В общем этот редактор это шаг в перед, остальные это ковыряние лопаткой в песочнице, по сравнению с экскаватором. Судя по всему он стоит денег, вопрос только сколько, когда я в последний раз смотрел информации не было.

Monday, September 24, 2007

Себе, почитать.

Увидел на хостинге - угарно.
http://www.sitepoint.com/print/php5-standard-library

Tuesday, September 11, 2007

ctags && vim

Т.к. я уже четвертый месяц пытаюсь использовать в качестве IDE vim, постоянно наталкиваюсь на некоторые трудности.
Давным давно использовал плагин TList + Project, достаточно удобная связка, но много ручной работы сводят преимущества на нет, особенно при создании нового проекта.
Со временем я нашел замену, TagExplore (File explorer + tag list view в одном флаконе ),
все было хорошо особенно нравилась возможность прямо в окне file exporer открыть закрыть список функций, переменных. , Но после окончательного перехода на 5-й php в коде стало появляться все больше и больше классов, и сейчас уже весь проект на них, а у ctags который используется этим плагином, был большой недостаток - не показывал он методы классов и все. Нашел патч на исходные коды исправляющий эти проблемы, но я люблю пакетные системы и ненавижу собирать софт. К тому же, на удаленных серверах где и стоит вим, не очень то и пособираешь пакеты - к тож мне рута даст?
В очердной раз пошукав в гугле обнаружил что ctags обновился и в новой 5.7 версии появилось

* Added support for interfaces and static/public/protected/private functions [PHP].

Обновился, и все заработало! (правда .deb пакета не нашел, но скачал rpm пакет и сконвертировал db при помощи alien -d ctags-.5.7-i386.rpm

Ниже скрин.



Но не обошлось без ложки дегтя
1. Методы показываются не в классе, а в функциях.
2. Появляются совершенно непонятные методы, (некоретно парсятся phpdoc таги)

Но все равно я доволен.
 
Каталог сайтов, Добавить сайт