Shared rss feeds:

Servare Mentem - blog

Мой предыдущий блог

вторник, 20 мая 2008 г.

PHP vs C#

Когда со мной начали спорить что PHP - вообще никакой язык, я молчать не смог.
Утверждалось, что в PHP вообще нет никаких преимуществ перед великим и могучим C#. Ну как тут было не поспорить? Лично я считаю, что PHP в любом случае более гибкий язык (в силу того что он интерпретируем) и всё что можно реализовать на C# - можно несложно реализовать и на PHP, а вот обратное во множестве случаев практически невозможно!
Мне говорили "но ведь PHP - не объектно-ориентированный язык! в C# - есть полноценная объектная модель, наследование, интерфейсы, абстрактные классы, виртуальные методы, свойства, и большая коллекция паттернов!", "Позвольте, позвольте, но ведь всё это так же есть и в PHP!" - отвечал я. В PHP5 есть нормальная объектная модель со всеми этими необходимыми фичами.
"А есть там делегаты?" - спросили меня. "Они там попросту не нужны" - отвечал я. - "Язык скриптовый, динамический и нестрого типизированный, поэтому нечего говорить про generics, delegates и прочие приблуды - зачем они здесь? Я больше чем уверен, что если мне потребуется делегат, я смогу его реализовать на PHP в 2 счёта!". Вообще, я быстро устаю от подобных нападок. Они, как правило, абсолютно беспочвенны. Ну почему мало кто воспринимает скриптовые языки всерьёз? Один лишь Ruby как-то выделяется на фоне остальных. Sun даже собирается включить его в следующую версию JDK. А PHP, мол, какашка - вчерашний день, не для серьёзных проектов... Ну что за бред, извините меня? Так считают люди которые с ним либо плохо знакомы, либо вообще не имеют представления о том, что такое PHP. Спросите у Oracle и IBM - почему они уделяют столько внимания именно PHP.
У Ruby очень грамотная реклама и хороший подход к обучению - "как писать правильно". И пожалуйста - Ruby On Rails лучший фреймворк в Интернете. Может быть, так оно и есть, я спорить не буду. Мне нравится идеология Rails, но то что Ruby - намного продвинутее PHP как язык, я сказать не могу. Думаю, что они скорее сравнимы. Просто почему-то никто не учится писать на PHP правильно.
Почему-то люди чаще начинают на нём писать плохо, копируют дурацкие примеры, не обращают внимания на абстракцию и декомпозицию... В итоге - в Интернете очень много плохого PHP кода. На PHP гораздо больше фреймворков чем на Ruby, но далеко не все они сравнимы с ROR по удобству. Думаю, что по той же причине, которую я обозначил выше. PHP, видимо, располагает к тому чтобы писать на нём плохо. Он расслабляет? Позволяет не обращать внимания на мелочи? Я в замешательстве.
Что же до сравнения PHP и C# давайте пройдём по наиболее существенным различиям, сравнивая лишь сам язык. Я ни в коей мере не собираюсь сравнивать скажем .NET и PHP - говорю только за язык. Итак:
- ООП? Что сказать - необходимый минимум в PHP есть. Интерфейсы, абстрактные классы, наследование, паттерны - это всё легко реализуемо. Ну да, нет в нём парциальных и вложенных классов. Но, по-моему, это не так существенно. Вот то что нет пространств имён - уже минус, здесь спорить не буду. Хотя, при грамотном подходе можно обойтись и без них. В конце концов их можно реализовать самостоятельно так же как они реализуются в JavaScript(есть прекрасные примеры - ZendFramework, PRADO... etc). Ну а кого это не устраивает - можно подождать выхода 5.3, где namespaces уже будут включены в язык.
- Строгая типизация? Такой же плюс как и минус, объяснять, надеюсь, смысла нет.
- Generics? В PHP от них никакого толка поскольку опять же типизация нестрогая...
- Delegates? - Ну не нужны они здесь! В крайнем случае я могу вызвать create_function(). Это похоже на создание анонимного делегата в C#. Например, сортировка массива:
// PHP 5.x code:
usort($arr, create_function('$a,$b','return ($a==$b)?0:($a<$b)? 1 : -1;'));
- Closures? - Вот это уже интересно. В PHP их нет. Думаю, что это довольно существенный недостаток. Однако, нет ничего невозможного. Можно реализовать и такую приблуду, если захотеть. Один из вариантов реализации - в SMPHPToolkit. Для сравнения:
C# 3.0 code:
var ary = { 1, 2, 3 };
var x = 2;
ary.Select(elem => elem * x;);

То же самое на PHP:
$arr = arr( 1, 2, 3 );
$x = 2;
$arr->select(eval(${new closure('$el')}->{'$el*=$x'}));
В принципе получилось не сильно больше кода. А по сути - почти то же самое. То что это можно реализовать средствами самого языка а не встроенной поддержкой - уже говорит о большей гибкости языка.
Возможно, это не лучшая эмуляция closure на PHP. Очень надеюсь что полноценные замыкания войдут в 6-ю версию.
Но, признаться, я не считаю это таким уж существенным преимуществом. В конце концов в Java тоже нет нормальных замыканий, но самые лучшие фреймворки и паттерны написаны как раз на нём.
Всегда можно воспользоваться классическими интерфейсами.
А теперь давайте рассмотрим преимущества PHP, которых уж точно нет в C# и которые так просто не реализовать.
- Буферизованный вывод. Считаю что это существенное преимущество PHP перед конкурентами. Например, для создания простейшего темплейтного движка:
 function apply_template($filename, $context)
 {
  ob_start();
  extract($context);
  require($filename);
  return ob_get_clean();
 }

 $content = apply_template("template.tpl.php", array('title'=>'Мой заголовок'));
Что делает эта фунцкия? Элементарно - включает файл шаблона в текст текущей страницы и при этом извлекает переменные из массива $context в текущий контекст. Таким образом, в шаблоне будет доступна переменная $title со значением 'Мой заголовок'. А весь текст шаблона (с любым PHP/HTML кодом) будет интерпретирован с текущим контекстом и результат будет возвращён функцией в виде строки.
- Eval. Элементарно - я могу в любой строке написать php/html код и сделать eval($code). Этот код будет интерпретирован с текущим контекстом. Таким образом, можно сделать так чтобы код генерировал код. Очень удобная возможность, отсутствующая в C#.
- Autoload. В PHP есть прекрасный механизм - можно подключать файлы с классами лишь тогда, когда они запрашиваются в первый раз. Например:
function __autoload($class_name) {
    require_once $class_name . '.inc.php';
}
$obj  = new MyClass1();
В данном примере при создании объекта класса MyClass1 будет подключён файл MyClass1.inc.php.
- "Magic methods". Суть "магических" методов в том, что можно реализовать динамически изменяемые классы меняющие своё поведение при обращении к определённым свойствам, методам или при преобразовании их к строке. С помощью __get и __set можно реализовать добавление новых свойств и методов для объекта при первом обращении к ним:
class Setter {
  private $_props = array();
  public function __get($prop) {
    if (isset($this->_props[$prop]))
   return $this->_props[$prop]
    return null;
  }
  function __set($prop, $val) {
      $this->_props[$prop] = $val;
  }
}
$foo = new Setter();
$foo->MyProperty1 = 1;
$foo->MyProperty2 = new MyClass();
Такое поведение похоже на поведение объекта в Ruby, где каждый класс является объектом с изменяемой в процессе работы сигнатурой.
- ${generate_id()}. Под этим я имею ввиду возможность обращаться к переменным или методам с именем, которое является просто строкой. Мне необязательно писать $var. Я могу написать ${'var'} или ${gen_var()}. Это всё будет обращением к переменной $var. На мой взгляд очень полезная возможность.

Conclusion Я перечислил далеко не всё, что, по-моему, легко реализуется в PHP, а в C# почти нереализуемо. Но и этих примеров должно хватить, чтобы понять что PHP всё-таки динамический и гибкий язык и не стоит к нему обращаться как к вчерашнему дню. У PHP свои преимущества и недостатки. Он не хуже Ruby или C#, но и не лучше их. Каждый язык предназначен для своей цели. PHP изначально затачивался под WEB. И он является в настоящее время лидирующей технологией, которая используется в Интернете. Не думаю, что это простая случайность и недоразумение. Да, в нём нет того язящества которое есть в Ruby. Да, его не сравнить по мощи с .NET или Java. Он делает лишь то, для чего создан и справляется с этим на ура. Чтобы найти на нём хороший фреймворк для веб-разработки, долго искать не нужно - их сотни, что так же касается и CMS. Примеров крупномасштабных проектов так же предостаточно. Это заблуждение, что PHP не для них, а только для персональных сайтов. Так было когда-то давно, когда он только появился. Сейчас же всё совсем по-другому.
Так что всё зависит от подхода и от программиста. То, что PHP расслабляет при программировании вовсе не значит, что нужно отказаться от написания хорошего кода и писать ad hoc. Нужно просто выбрать тот framework, который не позволяет этого делать. Вот Ruby On Rails во многом ограничивает. И это правильный подход, поскольку свобода всегда оставляет возможность для написания хардкода. А такой возможности программиста нужно безжалостно лишать.

Ссылки по теме:

Ярлыки: , , , ,

четверг, 15 мая 2008 г.

SM PHP Toolkit : part2

Сегодня я поведаю о втором довольно удобном приспособлении, вошедшем в SMPHPToolkit. Это грид вообще (и ajax-грид в частности). К примеру, необходимо реализовать на странице грид с пэйджингом, сортировками и без перегрузки страницы. Для начала, предположим что у нас есть класс уровня доступа к данным:
// Данные для отображения
class mydata{
 private static $mydata = array(
     array(0,'Название 1'),
     array(1,'Название 2'),
     array(2,'Название 3'),
     array(3,'Название 4'),
     array(4,'Название 5'),
     array(5,'Название 6'),
     array(6,'Название 7'),
     array(7,'Название 8'),
     array(8,'Название 9'),
     );
 // функция получения общего числа записей
 public static function get_total(){return count(self::$mydata);}
 // функция получения определённой страницы отсортированных данных
 // cортировка указывается третьим параметром (true=ASC,false=DESC)
 public static function get_page($pagesize, $page, $asc = true){
  return array_slice(
    ($asc)?self::$mydata:array_reverse(self::$mydata),
    ($page-1)*$pagesize, $pagesize);
 }
 // так же допустим есть функция удаления строки
 public static function delete($num){unset(self::$mydata[$num]);}
}
Начнём создавать грид. Сперва инициализируем AJAX-буфер, а так же AJAX-EntitiesManager - класс, который позволит упростить работу с действиями над данными в гриде. Для буфера зададим отображение прогресса (будет отображаться модальное окно с надписью "wait please"). В качестве второго аргумента для $emanager зададим созданный буфер.
// Инициализируем буфер и менеджер сущностей
$ajaxbuffer = new ajax_buffer("ajax_buffer");
$ajaxbuffer->show_progress(true);
$emanager = new ajax_entities_manager('entities_manager', $ajaxbuffer);
Теперь посмотрим на код инициализации грида:
$grid = new ajax_grid('my_grid',$datasource,$ajaxbuffer,$grid_pager);
В качестве аргументов он принимает datasource (объект данных для отрисовки), ajax-буфер и используемый пейджер. Буфер и пейджер - аргументы необязательные. При этом если не задать первый - он будет создан внутри грида автоматически. Пейджер представляет собой набор кнопок для переключения страниц а так же алгоритм пересчёта общего числа страниц и вычисления текущей страницы. В качестве аргументов он принимает общее число записей которые будут отображаться в гриде и размер страницы.
$grid_pager = new ajax_grid_pager('my_grid_pager',mydata::get_total(),5);
Объект-источник данных (DataSource) для грида описывает его заголовок и задаёт данные. Однако, при создании грида достаточно того, чтобы источник данных содержал только описание колонок. Каждая колонка принимает несколько параметров: имя поля, отображаемое имя поля, тип поля и флаг указывающий на то возможна ли сортировка по данному полю. Дополнительным параметром может служить объект расширяющий объект grid_formatter. Он нужен для кастомизированного отображения данных.
$datasource = new grid_data_source(new grid_header_item_array(
  new grid_header_item('id','Id',type::STRING, true),
  new grid_header_item('title','Заголовок',type::STRING, true),
  new grid_header_item('actions','Действия',null, false, new my_grid_formatter('actions', $emanager->client_id()))
  ));
А вот так можно перегрузить форматтер для того чтобы отрисовать кастом-поле "Действия":
class my_grid_formatter extends grid_formatter {
 protected $_field = '';
 protected $_client_id = '';
 public function __construct($field, $client_id){
  $this->_field = $field;
  $this->_client_id = $client_id;
 }
 public function format($data, $type, $number = 0, $columns = null)
 {
  switch($this->_field){
  case 'actions':
   return '<a href="javascript:'.$this->_client_id.
   '.deleteItem('.$data[0].');">Удалить</a>';
  default:
   return parent::format($data,$type); 
  }
 }
};
Прежде чем выбирать данные и передавать их источнику, нужно их изменить (ведь, возможно данная страница уже запрошена по ajax и пользователь нажал, к примеру "удалить" какую-то запись. Проверим.
// Если нужно выполнить какие-то действия над данными по постбэку
if($ajaxbuffer->is_post_back() &&  $emanager->isAnyAction())
{
 switch($emanager->getAction())
 {
  // необходимо удалить строку
  case $emanager->action->DEL:
   mydata::delete($emanager->getItem());
  break;
 }
// сбрасываем действие (в противном случае оно будет активно при каждом ajax запросе)
 $emanager->eraseAction();
}
Вот и всё. Все основные инициализации сделаны. Теперь нужно выбрать данные и передать их источнику.
// выбираем текущую страницу отсортированных данных
$mydata = mydata::get_page($grid_pager->get_pagesize(),$grid_pager->get_curpage(), $grid->get_sort_direction() != sorting::SORT_DIR_DESC);
// добавляем данные в DataSource
foreach($mydata as $data)
{
 $datasource->add_row(array($data[0],$data[1],$data)); 
}
Остаётся только отрендерить страницу. Для этого удобно воспользоваться шаблонизатором - классом templater, так же входящим в SMPHPToolkit. Ему передаётся путь к шаблону и ассоциативный массив переменных, которые будут доступны в шаблоне.
// Выводим результат
$templater  = new templater(dirname(__FILE__).'/templates/main.tpl.php');
die($templater->render(array('grid'=>$grid,
   'ajaxbuffer'=>$ajaxbuffer,
   'title'=>'Тестовая страница',
   )));
Ниже приведён код шаблона.
<html>
<head>
 <title><?=$title ?></title>
 <link href="css/grid.css" rel="stylesheet" type="text/css"/>
 <link href="css/ajax.css" rel="stylesheet" type="text/css"/>
 <script type="text/javascript" src="js/common/prototype.js"></script>
 <script type="text/javascript" src="js/common/scriptaculous.js"></script> 
 <script type="text/javascript" src="js/common/window/window.js"></script> 
 <script type="text/javascript" src="js/smphptoolkit/ajaxbuffer.js"></script>
 <script type="text/javascript" src="js/smphptoolkit/entitymanager.js"></script>
</head>
<body>
<? $ajaxbuffer->start(); ?>
 <?=$grid->render(); ?><br/>
 Updated at: <?=date("H:i:s") ?>
<? $ajaxbuffer->end(); ?>
</body>
</html>

В итоге получили симпатичный грид с пейджингом, сортировкой и возможностью удалить строку. И всё это работает через ассинхронные запросы к серверу. Изменив mydata, к примеру, на обращение к mysql, можно элементарно поменять данные на данные из базы, а чтобы добавить колонку, достаточно добавить элемент в массивы которые передаются источнику данных. Чтобы поменять шаблон - достаточно исправить строку, передаваемую шаблонизатору. Всё строится примитивно, интуитивно понятно и в то же время достаточно гибко.
Код данного примера и последнюю версию smphptoolkit можно забрать в моём svn.
В следующем посте про SMPHPToolkit я расскажу про класс storage, который позволяет оптимально хранить данные в текстовых файлах и удобно работать с выборками данных.

Ярлыки: , , , , ,

пятница, 2 мая 2008 г.

SM PHP Toolkit : part1

Однажды, после того как я попробовал ASP.NET Ajax и UpdatePanel в частности, мне понравилась идея того как работает этот контрол - удобно и просто для понимания и использования.
Потом мне как-то пришлось снова писать на PHP и захотелось создать нечто подобное в нём - просто так, интереса ради. Ну и я достаточно легко и быстро воссоздал подобную функциональность, используя Ajax от prototypejs. Вообще же, загоревшись этой идеей я даже написал целый набор классов, который потом получил название SMPHPToolkit. В него помимо этого самого AjaxBuffer'а, вошёл контрол grid с пейджингом и сортировкой, класс быстрого и удобного управления файловым хранилищем (хранение любых кастомных объектов в файлах, оптимизированная скорость выборки, поддержка сортировок, условных выборок и т.д.)... Но об этом потом.
Пользоваться же AjaxBuffer достаточно удобно и уж точно не сложнее чем UpdatePanel в asp.net.
Сначала нужно создать объект класса ajax_buffer. Сделать это нужно до любого вывода данных (то есть примерно там же, где запускается сессия), т.к. в конструкторе запускается буферизованный вывод (это нужно при постбэке).
$ajaxbuf = new ajax_buffer("update_buffer");
Так же нужно обязательно подключить prototype и клиентский API для буфера (автоматическое подключение библиотек было решено не использовать во избежание различных конфликтов).
  
  
Затем можно в любом месте html/php кода написать приблизительно следующее:
<? $ajaxbuf->start(); ?>
  Today is <?=date("Y/m/d H:i:s")?> (updated by ajax)
<? $ajaxbuf->end(); ?>
Остаётся только вызвать update с клиента, чтобы область, заключённая между "$ajaxbuf->start()" и "$ajaxbuf->end()" обновилась через AJAX. Добавляем в конец html кода:
<script type="text/javascript">
  setInterval('<?=$ajaxbuf->client_id() ?>.update()', 1000);
</script>
Вот и всё! Посмотрим что получилось:
<?php
require_once("SMPHPToolkit/SMAjax.php");
$ajaxbuf = new ajax_buffer("update_buffer");
?>
<html>
<head>
  <script type="text/javascript" src="js/common/prototype.js"></script>
  <script type="text/javascript" src="js/ajax/engine.js"></script>
</head>
<body>
<? $ajaxbuf->start(); ?>
  Today is <?=date("Y/m/d H:i:s")?> (updated by ajax)
<? $ajaxbuf->end(); ?>

<script type="text/javascript"> 
  setInterval('<?=$ajaxbuf->client_id() ?>.update()', 1000);
</script>
</body>
</html>
В данном примере каждую секунду по ajax-запросу просто обновляется строка "Today is...", это можно легко заменить на обновление любых объектов, которые генерятся php-кодом.
На одной странице можно создать неограниченное количество буферов, каждый из которых будет перегружаться по своему событию. Нужно всего-лишь вызвать
<?=$ajaxbuf->client_id() ?>.update()
например, по клику на кнопке, и область, заключённая в start-end будет обновлена. Этот механизм очень похож по принципу на UpdatePanel.
Кроме этого простого примера, можно так же создавать переменные для обмена данными между серверной и клиентской частями приложения:
$ind_count = new ajax_var('ind_count',0);
$ajaxbuf->register_var($ind_count);
Все переменные, которые были таким образом зарегистрированы в буфере, будут доступны в javascript-коде через:
var ind_count = <?=$ajaxbuf->client_id() ?>.get_var('<?=$ind_count->client_id() ?>');
и в серверном коде:
 $ind_count->get_value();
Для буфера так же существует метод проверки того запрошена ли страница через Ajax или же она запрошена впервые пользователем: $ajaxbuf->is_post_back().
Дополнительно можно настроить метод обновления данных в области, окружённой start-end, то есть, например, можно указать метод обновления ajax_buffer_method::APPEND_AFTER и данные будут просто дописываться к уже имеющимся. По-умолчанию же область полностью переписывается обновлёнными данными.
На основе AjaxBuffer можно реализовать простые и понятные ajax-приложения, лёгкие в использовании и кастомизации.
Забрать последнюю версию SMPHPToolkit можно в моём svn-репозитории.

[To be continued...]

Ярлыки: , , , ,

понедельник, 21 апреля 2008 г.

Как установить Eclipse + PHP + Apache + PDT + ZendDebugger + Subclipse

полезный линг
Остальные полезные линги:
  • http://www.php.net/downloads.php
  • http://www.zend.com/en/community/pdt
  • http://httpd.apache.org/download.cgi
  • Для начала надо всё это скачать (со ссылок приведённых выше). Лучше всего создать директорию WWW где-нибудь на диске (пусть будет C). Дальше в WWW создать директории /bin и /home.
    • Поставить Apache в директорию C:\www\bin\apache (внимательно, чтобы не получилось C:\www\bin\apache\apache2.2 или что-то подобное)
    • Поставить PHP в директорию C:\www\bin\php (то же самое). Отметить при установке "поставить как модуль для Apache 2.2 or higher".
    • Открыть файл C:\www\bin\apache\conf\httpd.conf и исправить в нём строчку
    • DocumentRoot "C:/www/bin/apache/htdocs"
      
    на
      DocumentRoot "C:/www/home"
    

    И заменить все вхождения "C:/www/bin/apache/htdocs" в этом файле на "C:/www/home" так же найти строчку

    DirectoryIndex index.html
    

    добавить туда index.php так:

    DirectoryIndex index.html index.php
    
    • Создать в папке C:/www/home файл index.php с содержимым:
    •  <?php phpinfo(); ?>
      
    • Проверить как работает PHP на Apache. Для этого - перезапустить Apache и набрать в адресной строке браузера http://localhost

    Внимание Если Apache не запускается по какой-то причине, возможно, неверно отредактирован файл httpd.conf или же 80 порт занят. Для этого в файле httpd.conf исправить строчку

    Listen 80
    
    на
    Listen 8080
    

    или любой другой свободный порт. (тогда проверка работоспособности будет заключаться в заходе на URL http://localhost:8080).

    • Скопировать ZendDebugger.dll в папку C:\www\bin\php\ext (если нет - создать её).
    • Посмотреть через phpinfo где находится файл php.ini (Loaded Configuration File). Открыть его в редакторе. Далее найти строчку "extension_dir = ...". Под ней вставить следующие строки:
    • extension=ZendDebugger.dll
      extension=php_mysql.dll
      

    Далее в конец файла добавить строки:

    [Zend]
    zend_extension_ts="C:/www/bin/php/ext/ZendDebugger.dll"
    zend_debugger.allow_hosts=localhost,127.0.0.1,[твой IP адрес]
    zend_debugger.expose_remotely=always 
    

    Так же заменить implicit_flush = Off на implicit_flush = On и заменить output_buffering = 4096 на output_buffering = 0 Ещё заменить short_open_tag = Off на short_open_tag = On Так же заменить display_errors = Off на display_errors = On

    • Перезапустить Apache и убедиться по phpinfo, что в группе "Powered by" появилась надпись
    • with Zend Debugger v5.2.12, Copyright (c) 1999-2007, by Zend Technologies
      
    • Скопировать PDT All in one в папку C:\Eclipse (так чтобы eclipse.exe находился в корне этой папки)
    • Запустить Eclipse.
    • Нажать Help -> Software Update -> Find And Install -> Next -> New Remote Site. Далее ввести "Subclipse" и "http://subclipse.tigris.org/update_1.2.x" и нажать Finish. Дальше следовать инструкциям визарда (отметить галочками Subclipse в нужном месте) и после установки перезапустить Eclipse.
    • Далее нажать File -> New -> Project... Выбрать Checkout projects from SVN и нажать Next. Потом задать параметры репозитория (url). Подождать когда появится список проектов и выбрать нужный. Потом нажать Next -> и Finish.
    • Теперь у вас есть полноценный PHP-дебаггер, мощная среда разработки, которую вы можете связать с сорс-контролом и самый лучший веб-сервер в мире :) Enjoy

    Ярлыки: , , ,

    -->