Kuzma's PHP Look

Home page: http://kuzma.russofile.ru

Как отказаться от стиля PHP4 и перейти к стилю PHP5

Наделите свои приложения на PHP4 преимуществами объектно-ориентированного подхода PHP5.



Автор: Джек D. Herrington (jherr@pobox.com), Senior Software Engineer, Leverage Software
Оригинал: http://www-128.ibm.com/developerworks/opensource/library/os-php-v5migr/?ca=dgr-lnxw02PHP4-to-PHP5
Перевод: Кузьма Феськов (kuzma@russofile.ru, http://php.russofile.ru)

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

PHP5 – это большой шаг относительно PHP4. Новые возможности делают его значительно более расширяемым и надежным, а новые библиотеки значительно расширяют и упрощают работу с ним. Кроме того, значительная переработка ядра позволила приблизить его к родственным языкам, например к Java TM. И так, отправимся в путешествия через новые объектно-ориентированные возможности PHP5, и узнаем, как перейти с PHP4 на PHP5.

Сначала немного о новых возможностях PHP и о том, как создатели изменили объектную модель языка. Главная идея PHP5 – это создание языка промышленной силы для написания WEB-приложений. К этому подтолкнула ограниченность языка PHP4.

Первым, и наиболее важным, изменением было введение защиты доступа к переменным, свойствам и методам. Модификаторы: public (публичные), protected (защищенные), private (частные). Это дополнение позволяет разработчику более гибко управлять внутренней структурой своих классов позволяя предоставлять клиенту только требуемую функциональность и скрывая от него то, что он не должен видеть.

В PHP4 все было общедоступным, то есть public (публичным). В PHP5 разработчик может сам определять что будут видеть все (public), что будет видно только внутри класса (private), а что будет видно внутри класса и его наследниках (protected). Без подобных возможностей работа с классами в больших группах вызывала проблемы, потому что разработчики могли легко использовать неправильные методы или могли изменить какое-то внутреннее свойство.

Другое значительное улучшение связано с введением в язык двух ключевых слов interface (интерфейс) и abstract (абстрактный), обеспечивая контрактное программирование (contract programming). Контрактное программирование означает, что один класс предоставляет контракт для другого, другими словами: «Здесь вы видите то, с чем вам придется работать и вам не нужно знать, как это было реализовано». Любой класс, который реализует интерфейс, соглашается с контрактом, который ему этот интерфейс навязывает. Любой потребитель интерфейса соглашается использовать только те методы, которые этот интерфейс предоставляет. Ключевое слово abstract (абстрактный) делает использование интерфейсов на много проще, далее мы в этом убедимся.

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

Контроль доступа

Чтобы показать вам новые языковые возможности, я использую класс под названием Configuration (конфигурация). Этот простой класс содержит в себе настройки для какого-то WEB-приложения, например, путь к директории с картинками. В идеале, эта информация записана в файле или базе данных. Листинг 1 показывает упрощенную версию:

Листинг 1. access.PHP4

<?php
class Configuration
{
    var $_items = array();

    function Configuration() {
        $this->_items[ 'imgpath' ] = 'images';
    }

    function get( $key ) {
        return $this->_items[ $key ];
      }
}

$c = new Configuration();
echo( $c->get( 'imgpath' )."
" );
?>

Это вполне имеющий право на жизнь класс PHP4. Свойство класса содержит в себе массив пунктов конфигурации, конструктор загружает эти пункты и метод get() возвращает значение нужного параметра.

Когда я запускаю этот код, на экране выводится следующее:

% php access.PHP4
images
%

Хорошо. Это означает, что мы все написали правильно и значение настройки imgpath установлено и прочитано правильно.

Первый шаг в преобразовании этого класса в класс PHP5 сводится к тому, что мы изменим название конструктора. В PHP5 метод, который инициализирует объект (конструктор), называется __construct (для обратной совместимости вы можете называть конструктор прежним образом, прим. пер.). Давайте посмотрим на пример:

Листинг 2. access1.PHP5

<?php
class Configuration
{
  var $_items = array();

  function __construct() {
    $this->_items[ 'imgpath' ] = 'images';
  }

  function get( $key ) {
    return $this->_items[ $key ];
  }
}

$c = new Configuration();
echo( $c->get( 'imgpath' )."
" );
?>

Это изменение не является великой переделкой, оно просто плавно перемещает нас на синтаксис PHP5. Следующим шагом будет добавление элементов управления доступом к классу. Первым делом, сделаем так, чтобы пользователи класса не имели доступа к свойству $_items, то есть не могли бы читать и писать туда. Это изменение показано ниже:

Листинг 3. access2.PHP5

<?php
class Configuration
{
  private $_items = array();

  public function __construct() {
    $this->_items[ 'imgpath' ] = 'images';
  }

  public function get( $key ) {
    return $this->_items[ $key ];
  }
}

$c = new Configuration();
echo( $c->get( 'imgpath' )."
" );
?>

Если пользователь класса попытается обратиться напрямую к свойству $_items, он получит ошибку, потому что свойство помечено как private (приватное). Пользователь должен понять, что доступ к свойству можно осуществить только по средствам метода get().

Чтобы показать, как работает модификатор protected (защищенный), необходимо создать второй класс, который наследовал бы первый. Я назову этот класс DBConfiguration. Он содержит в себе параметры доступа к базе данных. Этот класс описан ниже:

Листинг 4. access3.php

<?php
class Configuration
{
  protected $_items = array();

  public function __construct() {
    $this->load();
  }
  protected function load() { }
  public function get( $key ) {
    return $this->_items[ $key ];
  }
}

class DBConfiguration extends Configuration
{
  protected function load() {
    $this->_items[ 'imgpath' ] = 'images';
  }
}

$c = new DBConfiguration();
echo( $c->get( 'imgpath' )."
" );
?>

Этот листинг демонстрирует использование модификатора protected (защищенный). Базовый класс определяет метод load(). Потомок базового класса переопределяет метод load(), добавляя к нему возможность загрузки параметра в базу. Метод load() является внутренним для наследника и не видим извне. Если бы модификатор был private (приватный), то метод load() не мог бы быть переопределен.

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

Листинг 5. access4.PHP5

<?php
class Configuration
{
  private $_items = array();

  public function __construct() {
    $this->load();
  }
  protected function load() { }
  protected function add( $key, $value ) {
    $this->_items[ $key ] = $value;
  }
  public function get( $key ) {
    return $this->_items[ $key ];
  }
}

class DBConfiguration extends Configuration
{
  protected function load() {
    $this->add( 'imgpath', 'images' );
  }
}

$c = new DBConfiguration();
echo( $c->get( 'imgpath' )."
" );
?>

И так, теперь массив параметров $_items может быть приватным (private), поскольку классы наследники могут добавлять в него параметры используя protected (защищенный) метод add(). Класс Configuration может изменить поведение классов наследников при сохранении своих параметров.

Для меня введение возможность указания видимости для элементов явилась одной из главных, чтобы перейти к стилю PHP5. Почему? Да потому что без применения модификаторов доступа, невозможно проследить, какие объекты используют друг друга. Также не всегда возможно произвести качественные изменения, потому что вы не можете предсказать, что при этом сломается.

Контрактное программирование

Следующее значительное нововведение в PHP5 относительно PHP4 – это поддержка контрактного программирования посредством интерфейсов и абстрактных классов. Листинг 6 показывает версию класса Configuration, в котором программист PHP4 попытался создать элементарный интерфейс без ключевого слова Interface.

Листинг 6. interface.PHP4

<?php
class IConfiguration
{
  function get( $key ) { }
}

class Configuration extends IConfiguration
{
  var $_items = array();

  function Configuration() {
    $this->load();
  }
  function load() { }
  function get( $key ) {
    return $this->_items[ $key ];
  }
}

class DBConfiguration extends Configuration
{
  function load() {
    $this->_items[ 'imgpath' ] = 'images';
  }
}

$c = new DBConfiguration();
echo( $c->get( 'imgpath' )."
" );
?>

Листинг начинается с небольшого класса IConfiguration, который определяет интерфейс, который должен присутствовать в любом классе Configuration или его наследнике. Этот интерфейс определяет контракт между классом и любым из своих «клиентов». Контракт указывает, что все классы, которые наследуют IConfiguration, должны иметь метод get(), а любые клиенты IConfiguration должны привыкнуть использовать только этот метод для получения параметров. В данном случае, под клиентом понимается любой код, который заинтересован в получении настроек (прим. переводчика).

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

Листинг 7. interface1.PHP5

<?php
interface IConfiguration
{
  function get( $key );
}

class Configuration implements IConfiguration
{
  ...
}

class DBConfiguration extends Configuration
{
  ...
}

$c = new DBConfiguration();
echo( $c->get( 'imgpath' )."
" );
?>

Чтобы было понятно все, поясню: один и тот же класс может имплементировать сразу несколько интерфейсов. В Листинге 8 показано, как добавить поддержку интерфейса итератора, который является внутренним для PHP.

Листинг 8. interface2.PHP5

<?php
interface IConfiguration {
  ...
}

class Configuration implements IConfiguration, Iterator
{
  private $_items = array();

  public function __construct() {
    $this->load();
  }
  protected function load() { }
  protected function add( $key, $value ) {
    $this->_items[ $key ] = $value;
  }
  public function get( $key ) {
    return $this->_items[ $key ];
  }

  public function rewind() { reset($this->_items); }
  public function current() { return current($this->_items); }
  public function key() { return key($this->_items); }
  public function next() { return next($this->_items); }
  public function valid() { return ( $this->current() !== false ); }
}

class DBConfiguration extends Configuration {
  ...
}

$c = new DBConfiguration();
foreach( $c as $k => $v ) { echo( $k." = ".$v."
" ); }
?>

Интерфейс итератор позволяет классу внешне выглядеть как массив для своих клиентов. Как вы можете заметить в конце листинга, можно использовать команду foreach для обхода всех настроек проекта в объекте Configuration. Подобный функционал невозможен в PHP4, однако, это может быть очень удобно.

Преимущества интерфейса еще и в том, что вы можете сформировать контракт довольно быстро, ведь для этого не надо реализовывать указанные в интерфейсе методы. Чтобы «заключить» контракт, вам достаточно реализовать указанные в нем методы. Другое полезное дополнение в PHP5 – это абстрактные классы, которые позволяют реализовать базовый класс, так сказать – сердцевину интерфейса, из которого потом выйдут другие, конкретные, классы.

Другой вариант использования абстрактных классов – это сознание единого базового класса для других производных классов, произошедших от этого единого, и который нельзя инициализировать. Например, DBConfiguration и Configuration классы присутствуют, однако использоваться должен только один класс – DBConfiguration. Класс Configuration является просто базовым классом – абстрактным. Вы можете использовать ключевое слово abstract (абстрактный), чтобы обозначить такое поведение.

Листинг 9. abstract.PHP5

<?php
abstract class Configuration
{
  protected $_items = array();

  public function __construct() {
    $this->load();
  }
  abstract protected function load();
  public function get( $key ) {
    return $this->_items[ $key ];
  }
}

class DBConfiguration extends Configuration
{
  protected function load() {
    $this->_items[ 'imgpath' ] = 'images';
  }
}

$c = new DBConfiguration();
echo( $c->get( 'imgpath' )."
" );
?>

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

Статические методы и свойства

Другим важным дополнением PHP5 является поддержка статических методов и свойств класса. Используя данную функциональность, вы можете реализовать классический пример паттерна синглтон (singleton). Этот паттерн идеален для класса Configuration, поскольку объект такого класса должен быть всегда один.

Листинг 10 показывает, каким образом вы можете сделать класс Configuration синглтоном.

Listing 10. static.PHP5

<?php
class Configuration
{
  private $_items = array();

  static private $_instance = null;
  static public function get() {
    if ( self::$_instance == null ) 
       self::$_instance = new Configuration();
    return self::$_instance;
  }

  private function __construct() {
    $this->_items[ 'imgpath' ] = 'images';
  }
  public function __get( $key ) {
    return $this->_items[ $key ];
  }
}

echo( Configuration::get()->{ 'imgpath' }."
" );
?>

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

Магические методы

Другое значительное нововведение PHP5 – это введение «магических» методов, которые позволяют объекту менять свой интерфейс налету. Например, для получения или добавления конфигурационных параметров в объект Configuration, вы, вместо использования метода get(), можете затребовать просто конкретный пункт конфигурации, как будто это массив.

Листинг 11. magic.PHP5

<?php
class Configuration
{
  private $_items = array();

  function __construct() {
    $this->_items[ 'imgpath' ] = 'images';
  }
    function __get( $key ) {
    return $this->_items[ $key ];
  }
}

$c = new Configuration();
echo( $c->{ 'imgpath' }."
" );
?>

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

При переходе к стилю PHP5 очень важно внимательно ознакомиться со списком всех нововведений, чтобы потом иметь четкое представление о том, как вы можете применить их в старом, PHP4, коде.

Исключения

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

Листинг 12. file.PHP4

<?php
function parseLine( $l )
{
   // ...
   return array( 'error' => 0,
     data => array() // data here
   );
}

function readConfig( $path )
{
  if ( $path == null ) return -1;
  $fh = fopen( $path, 'r' );
  if ( $fh == null ) return -2;

  while( !feof( $fh ) ) {
    $l = fgets( $fh );
    $ec = parseLine( $l );
        if ( $ec['error'] != 0 ) return $ec['error'];
  }

  fclose( $fh );
  return 0;
}

$e = readConfig( 'myconfig.txt' );
if ( $e != 0 )
  echo( "Возникла ошибка (".$e.")
" );
?>

Этот стандартный I/O (ввод/вывод) код читает с диска данные и, в случае ошибки, возвращает ее код. У меня есть два вопроса к этому коду. Во-первых, коды ошибок, что они означают? Чтобы понять это, вы должны написать вторую структуру – оболочку, которая преобразует ваши коды ошибок к нормальному виду. Во-вторых, возвращаемые методом данные неоднозначны. Я был бы рад возвращать только данные, но я вынужден возвращать также и коды ошибок. Очень часто программисты (включая и меня) ленятся возвращать не только данные, но и код ошибки, просто их игнорируя.

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

Листинг 13. file.PHP5

<?php
function parseLine( $l )
{
   // Производит какие-то действия со строкой
   return array(); // data
}

function readConfig( $path )
{
  if ( $path == null )
    throw new Exception( 'неправильный аргумент' );

  $fh = fopen( $path, 'r' );
  if ( $fh == null )
    throw new Exception( 'не могу открыть файл );

  while( !feof( $fh ) ) {
    $l = fgets( $fh );
    $ec = parseLine( $l );
  }

  fclose( $fh );
}

try { 
  readConfig( 'myconfig.txt' );
} catch( Exception $e ) {
  echo( $e );
}
?>

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

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

Итоги

Новые возможности PHP5 являются серьезной причиной, чтобы отказаться от стиля PHP4. А переработка ваших старых классов, как вы могли заметить, не сложна. Эти возможности расширяют диапазон PHP, делая его конкурентно способным на уровне больших предприятий и проектов.



Яндекс цитирования