ООП (Статей: 27)

Введение
Данная статья рассчитана на программистов, имеющих общие знания в области PHP, его ОО модели и практикующих модульную архитектуру. Я постараюсь не углубляться в особенности тех или иных элементов ООП, чтобы не смешивать теорию с практикой, а постараюсь описать только практические части реализации классической MVC архитектуры и некоторых ее доработок.

Совсем немного теории
MVC архитектура является подмножеством более общей модели, называемой "Архитектурой с расслоением". Данная архитектура регламентирует создание таких систем, каждая функционально-завершенная область, которых будет не просто взаимодействовать с другими, но и образовывать отдельный модуль (или слой) со своим уровнем абстракции. В общем смысле это означает, что система расслаивается на следующие основные компоненты (в порядке обобщения):
- Слой источника данных - данный слой включает механизмы непосредственного взаимодействия с наиболее нижними уровнями системы, на пример с файловой системой, базой данных и т.д. В данный слой может входить множество небольших классов, каждый из которых решает строго определенные задачи;
- Слой служб (сервисов) - данный слой включает множество классов из шаблона "Фассад", которые предоставляют более абстрактный доступ к слою источника данных, на пример это службы взаимодействия с БД. В отличии от слоя источника данных, данная служба практически не содержит логики взаимодействия с БД, а только предоставляет более удобный интерфейс для этого взаимодействия, а так же позволяет легко изменять классы слоя испточника данных без необходимости переписывания кода в остальных слоях;
- Модель домена - данный слой содержит классы, реализующие логику самой программы. Если речь идет о гостевой книге, то данный слой будет содержать классы, представляющие предметную область, API системы и логику взаимодействия с хранилищами (сессии, БД и т.д.);
- Пользовательский интерфейс - данный слой содержит элементы взаимодействия с пользователем (GUI, консоль и т.д.).
Архитектура MVC затрагивает два последних слоя: Модель домена и Пользовательский интерфейс - позволяя разделить эти два слоя через добавление еще одного компонента - Контроллера.
В общем смысле, MVC использует модульную структуру, так как регламентирует создание множества триад - Модель, Вид, Контроллер. Для примера, гостевая книга с использованием MVC может выглядеть следующим образом:
Модель
<?php
interface Message{
  function __construct($autor, $content);
  public function edit($newContent);
  public function remove();
}

interface Book{
  // Возвращает объект класса Message по его id
  public function getMessage($idMessage);
  // Возвращает объекты класса Message по их id
  public function getMessagesFromIds(array $idsMessages);
  // Возвращает объекты класса Message из временного интервала
  public function getMessagesFromInterval(\DateInterval $interval);
  // Добавляет новое сообщение в БД
  public function createMessage(Message $message);
}
Модель будет использовать сервисы для записи сообщений в БД, их чтение и редактирования.
Вид
<?php
interface Tape{
  public function printMessage(Message $message);
}
Как правило, вид использует сервисы шаблонизации и шаблон "Издатель/подписчик".
Контроллер
<?php
class ControllerBook{
  // Функция, обрабатывающая все данные от вида (пример)
  public static function main(){
    $message = $_REQUEST;
    switch($message['Active']){
      case 'CreateMessage':
        // Никакой логики, только передача управления модели!
        $newMess = new Message($message['Autor'], $message['Content']);
        $book = new Book;
        $book->createMessage($newMessage);
      break;
      case 'DeleteMessage':
	// Никакой логики, только передача управления модели!
	$book = new Book;
        $book->getMessage($message['Id'])->remove();
      break;
      case 'EditMessage':
	// Никакой логики, только передача управления модели!
	$book = new Book;
        $book->getMessage($message['Id'])->edit($message['Content']);
      break;
    }
  } 
}
// При выполнении этого скрипта, управление передается в метод main контроллера, потому этот скрипт отлично подходит для обработки всех GET и POST запросов от клиента
// Его задачей является направить эти запросы в модель
ControllerBook::main();

Как видно, модель MVC всего лишь добавляет дополнительный контроллер, который принимает GET и POST запросы от клиента (в данном случае) и перенаправляет их нужным объектам модели.
Небольшие дополнения
Я решил пойти дальше и дополнить мою систему следующими механизмами:
- Единая точка входа - все GET и POST запросы всех видов передаются в одиный контроллер, который передает их ответственному контроллеру, а тот, в свою очередь, модели. Этот подход позволяет вынести некоторую общую логику приема данных (на пример фильтрацию входящих данных) в один класс, а не дублировать ее в каждом контроллере;
- Роутер модулей - для автоматизации поиска нужного контроллера, я использовал роутер, который позволит быстро находить требуемый контроллер;
- DataMapper с архитектурой "Отдельная таблица для каждого листа" - сложный механизм, позволяющий записывать объекты в реляционный БД с учетом наследования.
Все эти дополнения будут описаны далее, но они не являются частью классического MVC, пример которого был приведен выше.
Инструменты
Прежде чем описывать реализацию системы с использованием MVC, приведу несколько интерфейсов, необходимых для работы системы. Данные инструменты относятся к слою служб, так как доступны всем модулям модели домена, с другой стороны модули модели домена доступны только своим пользовательским интерфейсам и могут взаимодействовать только через заранее определенный механизм.
Роутер
Роутер позволяет Единой точки входа находить нужный для данного запроса контроллер. Роутер имеет следующий интерфейс:
<?php
/**
 * Интерфейс управления роутингом модулей
 */
interface ModulesRouterInterface{
  /**
   * Метод возвращает контроллер для данного модуля
   * @abstract
   * @param string $moduleName Имя модуля
   * @return mixed
   */
  public function getController($moduleName);

  /**
   * Метод определяет, определен ли данный модуль в системе
   * @abstract
   * @param string $moduleName Имя модуля
   * @return boolean true - если модуль установлен, иначе - false
   */
  public function isModuleExists($moduleName);

  /**
   * Метод добавляет новой путь в роутер
   * @abstract
   * @param string $moduleName Имя модуля
   * @param \PPHP\tools\patterns\metadata\reflection\ReflectionClass $controller Отображение класса контроллера для данного модуля
   */
  public function setController($moduleName, \PPHP\tools\patterns\metadata\reflection\ReflectionClass $controller);

  /**
   * Метод удаляет данные модуля из роутера
   * @abstract
   * @param string $moduleName Имя модуля
   * @return boolean true - если модуль был успешно удален из роутера, иначе - false
   */
  public function removeController($moduleName);
}
Он очень прост в реализации, для работы он использует ini файл со следующей структурой:
GuestBook=\PPHP\model\modules\guestBook\Controller
При запросе у центрального контроллера модуля GuestBook, он передает запрос к контроллеру model\modules\guestBook\Controller
Приемник
Данный класс включает механизмы получения данных из массива $_REQUEST и имеет следующий вид:
<?php
/**
 * Класс предоставляет доступ к виду и позволяет обмениваться с ним сообщениями.
 */
class ViewProvider implements \PPHP\tools\patterns\singleton\Singleton{
use \PPHP\tools\patterns\singleton\TSingleton;

  /**
   * Метод возвращает сообщение, полученное от вида.
   * @return mixed|null Содержимое сообщения или null - если сообщение не было передано.
   */
  public function getMessage(){
    if(!isset($_REQUEST['ViewMessage'])){
      return null;
    }
    return json_decode($_REQUEST['ViewMessage']);
  }

  /**
   * Метод передает сообщение виду.
   * @param mixed $message Содержимое сообщения.
   */
  public function sendMessage($message){
    echo json_encode($message);
  }
}
Как можно заметить, это очень простой класс, инкапсулирующий механизмы получения данных от клиента.
Центральный контроллер (единая точка входа)
Данный класс вызывается всегда, при передаче команд от вида в модель. Он реализован следующим образом:
<?php
/**
 * Класс является центральным контроллером системы и отвечает за вызов и передачу модулю сообщений вида, а так же за возврат ему ответа модуля.
 */
class CentralController{
  /**
   * Данный ментод отвечает за передачу модулю сообщения и возврат ответа.
   * @static
   * @throws ModuleNotFoundException Выбрасывается в случае, если требуемый модуль не зарегистрирован в системе.
   */
  public static function main(){
    // Используем приемник для получения текущей команды
    $viewProvider = \PPHP\services\view\ViewProvider::getInstance();
    $viewMessage = $viewProvider->getMessage();

    // Используем роутер для получения ответственного контроллера
    $modulesRouter = \PPHP\services\modules\ModulesRouter::getInstance();
    if(!$modulesRouter->isModuleExists($viewMessage->module)){
      throw new ModuleNotFoundException('Требуемого модуля "' . $viewMessage->module . '" не существует.');
    }
    $controller = $modulesRouter->getController($viewMessage->module);
    $controller = $controller::getInstance();

    // Запускаем в ответственном контроллере его метод-обработчик. Ответ передаем обратно в вид.
    $send = new \stdClass();
    try{
      $send->answer = $controller->main(is_null($viewMessage->message)? null : $viewMessage->message);
    }
    catch(\Exception $e){
      $send->exception = $e;
    }
    // Возврат ответа модели в вид с помощью Приемника
    $viewProvider->sendMessage($send);
  }
}
// Любой запуск скрипта приведет к запуску центрального контроллера
CentralController::main();
Интерфейс контроллеров модуля
Как можно заметить, центральный контроллер всегда использует однин и тот же метод ответственного контроллера - main - значит нужно чтобы все контроллеры реализовали
этот метод, для этого определим интерфейс контроллеров модуля:
<?php
/**
 * Все конкретные контроллеры модулей должны реализовывать данный интерфейс.
 */
abstract class ModuleController implements \PPHP\tools\patterns\metadata\reflection\Reflect, \PPHP\tools\patterns\singleton\Singleton{
// Все контроллеры модуля являются Singletom и могут быть аннотированы
use \PPHP\tools\patterns\metadata\reflection\TReflect;
use \PPHP\tools\patterns\singleton\TSingleton;

  /**
   * Точка входа в контроллер модуля.
   * @abstract
   * @param mixed|null $message Сообщение, переданное от вида.
   * @return mixed Возвращаемые виду данные.
   */
  abstract public function main($message = null); // Данный метод отвечает за перенаправления запросов от вида в модель
}

Пример контроллера
Простейший контроллер:
<?php
namespace PPHP\model\modules\guestBook;

class Controller extends \PPHP\model\classes\ModuleController{
  public function main($message = null){
    return 0;
  }
}
Данный контроллер ничего не обрабатывает и всегда возвращает ответ 0, но он показывает как реализовать контроллер модуля. Достаточно создать класс, реализующий интерфейс ModuleController и инициализировать контроллер в роутере добавив запись в ini файл:
GuestBook=\PPHP\model\modules\guestBook\Controller
Вы наверно уже заметили, что данная запись как раз указывает на нужный контроллер: namespace PPHP\model\modules\guestBook; class Controller ... Так роутер и находит требуемые контроллеры. Все просто.
Примеры
Выкладываю мою незаконченную систему управления с использованием изложенных выше принципов. Если будут вопросы, прошу в приват, или, если администрация разрешит, можно создать отдельную тему. Надеюсь, будет актуально smile
Архив: http://upwap.ru/2607316
Пароль: 3129
Введение
Раньше мне приходилось довольно часто сталкиваться с проблемой непонимания начинающими программистами основ такого важного механизма ООП, как наследование. Задачи, поставленные мной, часто приводили к созданию такого необычного решения, что приходилось переписывать добрую часть кода, не смотря на то, что частично решение уже было реализовано ранее. Мне удалось решить эту проблему в своем коллективе и сейчас я хочу поделиться с вами секретом "наследования".
Целью данной статьи является демонстрация использования наследования на реальных примерах. Возможно, пример окажется слишком узким, но, как мне кажется, он достаточен для внимательного читателя.

Поставленная задача
Как-то раз мне потребовалась реализация механизма аннотирования классов и их членов как в Java. Если кто не знает, это возможность присвоения дополнительных данных классам, их свойствам, методам, аргументам методов и т.д. К примеру, если нам требуется записать объект в базу данных, то необходимо связать свойства объекта с полями таблицы, а так же указать, в какую таблицу необходимо записать данный класс. В моем случае это должно быть реализовано следующим образом:
<?php
/*
TableName=PeopleTable
*/
class People{
/*
FieldName=firstNameField
*/
private $firstName;

/*
FieldName=nameField
*/
private $name;

/*
FieldName=midleNameField
*/
private $midleName;

/*
FieldName=dateBirthField
*/
private $dateBirth;
...

publid getFirstName(){
return $this->firstName;
}
...

}
Класс, аннотированный данным образом, использует таблицу PeopleTable для записи своих объектов, а его свойства записываются в поля firstNameField, nameField, midleNameField, dateBirthField. Другими словами, аннотация позволяет добавлять метаданные (данные описывающие данные) в код.
Данная задача была поставлена мной одному из моих программистов и дана неделя для разработки, реализации и тестирования.

Неудачное решение
Решение, предложенное моим программистом, меня несколько удивило. Оно выглядело следующим образом: был разработан интерфейс Described который позволял добавлять метаданные к любому, реализующему его классу
<?php
interface Described{
  // Метод возвращает все метаданные данного элемента.
  public function getAllMetadata();

  // Метод возвращает значение конкретных метаданных элемента.
  public function getMetadata($metadataName);

  // Метод устанавливает значение метаданных.
  public function setMetadata($metadataName, $metadataValue);

  // Метод проверяет, существуют ли заданные метаданные в вызываемом представлении.
  public function isMetadataExists($metadataName);
}
Так же были реализованы представления всех описываемых метаданными элементов класса:
- Type - представление класса;
- Property - представление свойства;
- Method - представление метода.
Все эти классы являлись подклассами класса ClassMember который реализовывал интерфейс Described, следовательно, все эти классы могли быть описаны с помощью метаданных.
Мой программист так же создал интерфейс Reflect, реализация которого позволяла получить соответствующие отображения:
<?php
interface Reflect{
  // Метод возвращает представление свойства класса.
  static public function &getReflectionProperty($propertyName);

  // Метод возвращает представление метода класса.
  static public function &getReflectionMethod($methodName);

  // Метод возвращает представление класса.
  static public function &getReflectionClass();

  // Метод возвращает отображения всех свойств класса.
  static public function getAllReflectionProperties();

  // Метод возвращает отображения всех методов класса.
  static public function getAllReflectionMethods();
}
Для ясности приведу пример кода, использующий аннотирование с решением, предложенным моим программистом:
<?php
// Анотируемый класс
class TestMetadata implement Reflect{
use TReflect;

private $var1;
private $var2;

public function method(){}
}

// Получаем представление переменной. Представление хранит только информацию о переменной, но не ее значение, потому инкапсуляция не нарушается.
$var1Reflect = TestMetadata::getReflectionProperty('var1');
// Добавляем аннотацию к свойству
$var1Reflect->setMetadata('FieldName', 'var1Field');
/* Теперь свойство var1 класса TestMetadata имеет аннотацию FieldName со значением var1Field, это позволит нам в будущем записать значение данного свойства в требуемое поле таблицы. Если изменится имя поля таблицы, достаточно будет изменить аннотацию */

Разбор полетов
На первый взгляд может показаться, что код вполне приличен, в нем есть и ООП, и инкапсуляция, и полиморфизм и даже наследование, но на деле это не так. В действительности представления, написанные моим программистом: Type, Method, Property - уже существуют в стандартной библиотеке PHP и называются ReflectionClass, ReflectionMethod и ReflectionProperty. Существуют и дополнительные представления, реализованные в стандартной библиотеке и отсутствующие в данной реализации, а так же эти стандартные решения имеют дополнительные методы, которые придется реализовать нам самостоятельно. Пропадает повторное использование кода!
Если вы хорошо знакомы с ОО моделью в PHP, то знаете сколько полезных классов она включает, потому попробуем использовать существующее решение и решить задачу с помощью наследования.

Более хорошее решение
Мы не отказались от некоторых решений, предложенных моим программистом, в частности мы оставили интерфейс Described, его Trait, а так же Reflect и его Trait, так как аналога в стандартных решениях у нас нет. Мы выбросили классы Type, Method, Property и воспользовались следующими подклассами:
<?php
class ReflectionClass extends \ReflectionClass implements \PPHP\patterns\metadata\Described{
  use \PPHP\patterns\metadata\TDescribed;
}

class ReflectionMethod extends \ReflectionMethod implements \PPHP\patterns\metadata\Described{
  use \PPHP\patterns\metadata\TDescribed;
}

class ReflectionProperty extends \ReflectionProperty implements \PPHP\patterns\metadata\Described{
  use \PPHP\patterns\metadata\TDescribed;
}
Эти три класса расширяют стандартные классы PHP для работы с отображениями и дополняются новым функционалом - возможностью аннотирования.
Посмотрите, как сейчас реализован механизм получения отображений:
<?php

trait TReflect{
  // Отражение класса.  
  static private $reflectionClass;

  // Множество отражений свойств класса.
  static private $reflectionProperties;

  // Множество отражений методов класса.
  static private $reflectionMethods;

  // Метод возвращает представление свойства класса.
  static public function &getReflectionProperty($propertyName){
    if(!is_string($propertyName) || empty($propertyName) || !property_exists(get_class(), $propertyName)){
      throw new \InvalidArgumentException();
    }
    if(empty(self::$reflectionProperties)){
      self::$reflectionProperties = [];
    }
    if(!array_key_exists($propertyName, self::$reflectionProperties)){
      self::$reflectionProperties[$propertyName] = new ReflectionProperty(get_class(), $propertyName);
    }
    return self::$reflectionProperties[$propertyName];
  }

  // Метод возвращает представление метода класса.
  static public function &getReflectionMethod($methodName){
    if(!is_string($methodName) || empty($methodName) || !method_exists(get_class(), $methodName)){
      throw new \InvalidArgumentException();
    }
    if(empty(self::$reflectionMethods)){
      self::$reflectionMethods = [];
    }
    if(!array_key_exists($methodName, self::$reflectionMethods)){
      self::$reflectionMethods[$methodName] = new ReflectionMethod(get_class(), $methodName);
    }
    return self::$reflectionMethods[$methodName];
  }

  // Метод возвращает представление класса.
  static public function &getReflectionClass(){
    if(empty(self::$reflectionClass)){
      self::$reflectionClass = new ReflectionClass(get_class());
    }
    return self::$reflectionClass;
  }

  // Метод возвращает отображения всех свойств класса.
  static public function getAllReflectionProperties(){
    $reflectionProperties = new \SplObjectStorage();
    $namesAllProperties = get_class_vars(get_class());
    foreach($namesAllProperties as $k => $v){
      $reflectionProperties->attach(self::getReflectionProperty($k));
    }
    return $reflectionProperties;
  }

  // Метод возвращает отображения всех методов класса.
  static public function getAllReflectionMethods(){
    $reflectionMethods = new \SplObjectStorage();
    $namesAllMethods = get_class_methods(get_class());
    foreach($namesAllMethods as $v){
      $reflectionMethods->attach(self::getReflectionMethod($v));
    }
    return $reflectionMethods;
  }
}
Как можно заметить, класс будет возвращать одно и то же отображения для одного свойства, а не несколько. Это позволяет сохранить аннотации.
Посмотрите, как изменится код:
<?php
// Аннотируемый класс
class TestMetadata implement Reflect{
use TReflect;

private $var1;
private $var2;

public function method(){}
}

// Получаем представление переменной. Представление хранит только информацию о переменной, но не ее значение, потому инкапсуляция не нарушается.
$var1Reflect = TestMetadata::getReflectionProperty('var1');
// Добавляем аннотацию к свойству
$var1Reflect->setMetadata('FieldName', 'var1Field');
/* Теперь свойство var1 класса TestMetadata имеет анотацию FieldName со значением var1Field, это позволит нам в будущем записать значение данного свойства в требуемое поле таблицы. Если изменится имя поля таблицы, достаточно будет изменить аннотацию */
Как можно заметить, код не изменился вообще. Дело в том, что правильная архитектура пакета позволила нам инкапсулировать изменения в классах, потому код, использующий данные классы не претерпел изменений, но зато у нас отпала необходимость реализовывать три дополнительных класса и один абстрактный класс. Все благодаря наследованию!

Заключение
Запомните, если ваша задача требует использование новых классов, то окиньте взглядом уже существующие решения, возможно решить проблему можно использую наследование.
Старайтесь писать код не просто рабочим, а удобным в сопровождении. Удачи в программировании!
Общие сведения об исключениях
До появления ООП существовало два подхода к обработке ошибок в программе:
1. Возврат кода ошибки из вызываемой функции или программы;
2. Возврат имени ошибки из вызываемой функции или программы.
Как видно, оба подхода страдают от того, что имя или код ошибки малоинформативны без описания. Запись ошибки типа: Ошибка 1234 – мало о чем говорит отладчику и требует постоянного обращения к документации.
ООП предоставило другой способ описания ошибок. Сегодня ошибка, ее код, описание, даже причина, инкапсулируется в объект, называемый Исключением. Данное исключение создается и передается вверх по стеку вызова, «убивая» все на своем пути, пока оно не будет обработано.
Сегодня исключения позволяют работать с ошибками как с объектами, что позволяет быстро и эффективно отлаживать программные продукты.

Иерархия исключений
Из того, что исключения являются объектами, можно сделать вполне обоснованный вывод, что классы исключений могут быть выстроены в иерархии наследования, что и реализуется во всех современных языках, поддерживающих ОО парадигму, в том числе и PHP.
В PHP можно выделить следующую иерархию:
• Исключения времени компиляции – данный вид исключений используется для информирования об ошибках, обнаруженных на этапе компиляции PHP скриптов;
• Исключения времени выполнения – данный вид исключений используется для информирования об ошибках, обнаруженных на этапе выполнения (интерпретации) PHP скриптов. Это наиболее распространенные, часто используемые и важные исключения ;
• Логические исключения – данный вид исключений используется для информирования об ошибках, связанных с логикой программы. На пример обращение к несуществующему элементу массива или попытка записи в несуществующий файл.
Если взглянуть на сторонние программные продукты, то можно заметить что практически любой программный пакет включает собственный набор, специфичных именно для него, исключений. На пример пакет для работы с файловой системой чаще использует исключения типа: Отсутствие файла, Блокировка файла, Ошибка адреса в файловой системе – и так далее. Это очень важное решение, создавая новые пакеты, вам так же придется и определять новые типы исключений. Для чего это нужно? На собственном опыте скажу, что использование на протяжении всей программы одного вида исключений, на пример
<?php
throw new Exception(‘Описание ошибки’)
рано или поздно приведет к тому, что выбрасывание исключения заставит вас перерывать кучу кода, чтобы понять смысл и содержания описываемой ошибки. С другой стороны, если вы увидите исключения типа: FileNotExistsException, то сразу будет понятно, что ошибка связана с отсутствием требуемого файла, а если проследить стек, то легко локализовать и исправить ошибку.
Запомните! Используйте собственные типы исключений для описания различных видов ошибок. Не стоит полагаться только на стандартные исключения.
Чтобы определить собственный тип исключения достаточно создать подкласс одного из классов исключений. На пример если вам нужно создать обозначенное ранее исключение FileNotExistsException, то можно реализовать его с помощью следующего класса:
<?php
class FileNotExistsException extends \RuntimeException{
}
Заметьте, что я наследую не от класса Exception, являющегося корневым в иерархии исключений, а от класса RuntimeException, который так же является дочерним по отношению к классу Exception, но при этом выделяет такие исключения, которые выявляются на этапе выполнения. Это означает, что исключение типа FileNotExistsException может быть выброшено только на этапе выполнения, но не компиляции.
Запомните! Чтобы правильно строить собственные иерархии исключений, вам необходимо изучить существующие в PHP иерархии и выбирать, к какой ветке будут относиться ваши иерархии.

Когда и как следует использовать исключения
• Используйте выброс исключения тогда, когда метод получает неожиданные данные («мусор») на вход. На пример метод получает в качестве аргумента массив, когда ожидается число;
• Используйте выброс исключения тогда, когда метод не может продолжить выполнения в связи с недостатком данных. На пример метод не может произвести запись в файл, так как файла не существует;
• Используйте выброс исключения тогда, когда вы запрещаете использование родительского метода в дочернем классе;
• Используйте выброс исключения тогда, когда вы хотите сообщить, что некоторый метод устарел и использование его запрещается. Это позволит выявить использование устаревших методов в коде при переходе на новую версию продукта;
• Создавайте стек исключений передавая в качестве третьего аргумента конструктора исключений то исключение, которое явилось причиной создаваемому исключению. На пример если метод не может создать файл по причине синтаксической ошибки в имени файла, то можно обернуть исключение SyntaxException в исключение FileException. В этом случае вам будет понятно, что ошибка была вызвана синтаксисом;
• Давайте осмысленные имена классом исключений. Более детализованные конкретным типам исключений, и более абстрактные обобщающим типам. На пример для описания исключения, вызванных файловой системой можно использовать абстрактный класс FileSystemException, а для описания исключений, вызванных отсутствием требуемого файла или каталога в файловой системе, можно использовать класс ComponentNotExistsException который будет являться подклассом класса FileSystemException.

Когда и как не следует использовать исключения
• Не используйте исключения, если возврат метода достаточен для описания результата и никаких исключительных ситуаций не найдено. На пример для метода create, создающего файл с данным именем, можно использовать данные возврата: true – если файл удачно создан и false – если создание файла не выполнено в связи с некоторой ошибкой, как на пример существование файла с тем же именем. Здесь вполне можно обойтись без исключений;
Запомните! Решить когда нужно использовать исключения, а когда можно обойтись возвращаемыми данными вам придется самостоятельно. Придерживайтесь следующего правила, если вам требуется знать причину ошибки в методе, то используйте исключения, если же причина вам не важна, а нужно знать только выполнен ли метод или нет, то используйте данные, возвращаемые методом.
• Не используйте исключения для реализации бизнес-логики приложения. На пример не следует использовать конструкцию try…catch как эквивалент конструкции if;
• Постарайтесь не использовать выброс исключений в конструкторах классов, это может привести к утечкам памяти;
• Наличие сложной, многоуровневой иерархии исключений программного пакета обычно свидетельствует об ошибочной архитектуре. Для программного пакета обычно достаточно одного-трех уровней иерархии исключений.

Классы исключений SPL
• LogicException – свидетельствует об ошибке в логике программы.
o BadFunctionCallException – свидетельствует об обращении к неопределенной функции или об отсутствии требуемых аргументов;
 BadMethodCallException – свидетельствует об обращении к неопределенному методу или об отсутствии требуемых аргументов;
o DomainException – свидетельствует о том, что выполнение программы не придерживается заданной области данных;
o InvalidArgumentException – свидетельствует о том, что функцией или методом был получен аргумент неожиданного типа или формата;
o LengthException – свидетельствует об ошибке длины;
o OutOfRangeException – свидетельствует о том, что была произведена попытка выхода за границы множества.
• RuntimeException – свидетельсвует об ошибке выполнения программы.
o OutOfBoundsException – свидетельствует о том, что была произведена попытка обращения к элементу множества по неверному ключу;
o OverflowException – переполнение контейнера;
o RangeException – ошибки диапазона;
o UnderflowException – свидетельствует о попытке удаления элемента из пустого множества;
o UnexpectedValueException – свидетельсвует о том, что получены данные несоответствующие ожидаемым.
Запомните! Если для описания ошибки можно воспользоваться существующим классом исключений, то следует сделать это.

Пример иерархии исключений
Приведу пример моей иерархии исключений, которые определены в программном пакете взаимодействия с файловой системой:
• ComponentDuplicationException – данное исключение свидетельствует о том, что выполнение метода приведет к созданию двух компонентов с одинаковым именем в контексте одного каталога;
• LockException – данное исключение свидетельствует о том, что невозможно получить поток из-за блокировки;
• NotExistsException – данное исключение свидетельствует о том, что одного из необходимых компонентов файловой системы не существует;
• UpdatingRoodException – данное исключение свидетельствует о том, что предпринята попытка изменения корневого каталога.
Данная статья обещает быть очень емкой, потому постараюсь разбить ее на удобные части. Статья затрагивает многие аспекты объектно-ориентированного подхода в программировании (ООПП), такие как Уровни абстракции, Инкапсуляция, Полиморфизм,Наследование, Шаблоны проектирования и так далее. Чтобы не запутаться во всем этом, я постараюсь дать краткие сведения о том аспекте, который я применяю, при первом его упоминании, а так же аргументирую его использования и плюсы в каждом конкретном случае.
О чем собственно статья? Многие из нас рано или поздно сталкивались с необходимостью хранения данных. Все начинается с файловой базы данных (ФБД), затем перетекает в наиболее простые и удобные системы управления базами данных (СУБД), а в наиболее сложных случаях мы переключаемся на дорогие и запутанные коммерческие СУБД от Microsoft и Oracle. Как ни странно, мне понадобилось реализовать удобную систему хранения данных в ФБД, так как планировалось установить полноценную систему с СУБД уже после установки первоначального ядра, а необходимость в сохранении данных к тому времени уже была.
Задачи и первые наброски
Усевшись перед компьютером, я открыл старый добрый блокнот и быстренько набросал требования к будущей системе. Если вспомнить наставления Крэга Лармана в его книге «Применение UML и шаблонов проектирования», то я подошел к начальному этапу с точки зрения процедурного подхода, а именно выделил функциональные, а не пользовательские требования. Объясню подробнее. При написании крупных систем очень удобно начинать с определения конечных пользователей этих систем, в данном случае конечными пользователями будут я и другие программисты, работающие с моей системой. После этого нужно определить их задачи, которые решаются с помощью разрабатываемой системы. В моем случае это удобный механизм работы с файлами как с реляционными таблицами, а так же простота и гибкость реализации (время поджимало). Почему же я отбросил этот важный шаг? Дело в том, что система довольно проста и ее будущий интерфейс будет позаимствован у существующих на момент проектирования СУБД, потому придаваться сложной проработке зачатков мысли я не стал. Здесь можно заметить важную особенность всех советов и наставлений «Гуру программирования» - ни один совет не является обязательным требованием, и следование ему в той или иной ситуации это умение, приходящее с опытом. Как правило, все советы и наставления требуют больше времени на этапе проектирования и реализации, но экономят его в дальнейшем за счет тех или иных приемов. Важно подробно разобраться в этих приемах и понять когда они будут работать, а когда лучше поступить по старинке. В моем случае ФБД это уже решенная другими программистами задача и заново изобретать ее интерфейс или реализацию, это лишняя трата времени и сил, ведь вряд ли я что-то изменю, не это моя цель. Умейте правильно применять те или иные техники, а не следуйте им безоговорочно.
И так задачи, поставленные мной для данной системы:
1. Возможность записывать, удалять, изменять и получать данные из таблицы;
2. Возможность работы с несколькими таблицами;
3. Приемлемая скорость работы;
4. Возможность расширять полученную систему;
5. Удобный и понятный интерфейс.
Заметьте, задачи никак не содержат в себе ни слово о реализации. Так, задача (3) могла бы быть разделена на несколько подзадач:
3.1. Использование индексации;
3.2. Кэширование запросов;
На этапе первичного осмысления важно отказаться от реализации, и задуматься только о задачах. Другими словами думайте о том, что нужно решить с помощью системы, а не о том, как это решить. Рассматривайте этот этап с позиции заказчика. Вы не знаете, как именно эта система будет работать, вы лишь знаете, что она должна вам дать, какие задачи решать.
Сейчас мы смотрим на систему с наиболее высокого уровня абстракции, и видим только самые общие ее части. Пришло время узнать о наиболее важном инструменте ООПП – абстракцией и ее уровнями.
Абстракция
Абстракция – это способ абстрагирования, ухода от всего лишнего по средствам взгляда на проблему с более «высокого» уровня. Так, мы будем рассматривать в нашей ФБД и конкретные файлы, и механизмы доступа к ним, и способы кэширования запросов, и организации структуры и так далее и так далее. Все это разные уровни абстракции, каждый из которых ограничивает наш взор теми или иными областями. Далее мы рассмотрим абстракцию на конкретных примерах, и при необходимости, возвращайтесь к этому отступлению.
Определение элементов системы
Сейчас мы еще не знаем, из чего будет состоять система, какие классы она будет содержать и как они будут связаны, мы лишь определились с кругом задач, которые должны решить. Ни пора ли нам взяться за структуру системы? Самое время!
Для успешного функционирования нашей файловой базы данных, нам понадобятся следующие знания:
* Файл – это структура, позволяющая хранить данные, связав их именем. Другими словами к данным, записанным в файл, мы сможем получить доступ, если знаем имя и расположение файла.
* Реляционная таблица – это файл, данные которого записываются определенным образом, согласно структуре таблицы.
* База данных (БД) – это совокупность данных, хранящихся определенным образом.
* Реляционная база данных (РБД) – это база данных, в которой данные хранятся в виде совокупности значений столбцов таблицы, которые образуют записи.
* Файловая реляционная база данных – это (в данном контексте) база данных, которая использует любые файлы как реляционные таблицы, если они соответствуют заданной структуре.
Как видите, каждое знание рассматривается в совершенно разных уровнях детализации. Так, файл это наиболее низкий уровень абстракции. Механизм взаимодействия с файлами должен уметь только записывать и считывать их них данные, при этом ни о какой реляционной организации нет речи. Более низкие уровни абстракции не знают о том, как они будут использоваться более высокими уровнями, потому они лишь предоставляют удобный интерфейс и выполняют довольно узкий круг функций. С другой стороны реляционная таблица включает в себя возможность организации данных в определенной структуре, а так же чтения этих данных определенным образом (записями или отдельными полями). Это уже более высокий уровень абстракции, который использует в качестве инструмента более низкие уровни (уровень файлов, для чтения и записи), при этом дополняя их новыми возможностями. Напоминает наследование в ООП, не правда ли? В действительности это агрегация, о которой мы поговорим чуть позже. Важно запомнить, что классы более высокого уровня абстракции могут использовать классы более низкого уровня, но не наоборот! Это очень важно, так как позволяет ограничить сложность системы за счет принципа черного ящика. Представьте, что при написании класса «Файл» нам пришлось бы задумываться о том, как будет реализован класс «Реляционная таблица»? Было бы очень сложно хранить в голове всю эту информацию, с учетом того, что таких связей может быть огромное множество. Запрет использования классами низкого уровня абстракции классов с более высоким уровнем позволяет нам беспокоиться, при реализации первых, только о решении простых, прикладных задач, а не задумываться о том, как этот класс будет использоваться другими. С другой стороны, важно, чтобы классы более низкого уровня абстракции давали исчерпывающий набор инструментов в своем интерфейсе, чтобы не пришлось их дополнять при использовании другими классами. Так, класс «Файл» мог бы включать такие функции, как: ЗаписьВФайл, ЧтениеИзФайла – но не позволять блокировать доступ к файлам с помощью функций flock. Возможно, это было бы достаточно на какое-то время, но в конечном итоге нам все же пришлось бы добавить эту возможность в класс работы с файлами. Не забывайте, на каком уровне абстракции вы находитесь при проектировании классов, это позволит вам забыть о некоторых не нужных на данном уровне деталях.
Теперь пора определить основные классы, которые нам понадобятся для работы. Упорядочу их по уровням абстракции:
1 уровень
1. Класс работы с файлами;
2. Класс работы с каталогами.
2 уровень
1. Класс для работы с файлами как с реляционными таблицами без сложных запросов. Является связывающим звеном всех элементов реляционной таблицы;
2. Класс-структура файловой реляционной таблицы, для вынесения задачи структурирования данных в этот класс;
3 уровень
1. Класс для работы с файлами реляционной таблицы с использованием сложных запросов;
2. Класс для работы с директориями как с хранилищами файлов реляционных таблиц.
Сразу замечу, что это совсем не исчерпывающий список используемых мной для решения поставленных задач классов, но на данном этапе этих классов мне достаточно, потому что, я только начинаю задумываться о реализации.

Работа с файлами
Начнем с реализации класса, позволяющего удобно работать с файлами. Выделим отдельный пакет «fileSystem», который будет включать классы, работающие с файловой системой на низком уровне абстракции. Теперь рассмотрим механизм работы с файлами.
Для начала работы файл необходимо открыть, причем в определенном режиме (r, r+, w, w+, a, a+) . Так же нужно задуматься о трансляции символов переноса строки для различных ОС. Windows записывает перенос строки в два символа, в то время как UNIX использует для этого один символ. Казалось бы, не существенно, но дальше вы поймете, что это очень важный момент. После открытия файла нужно подумать о том, не открыл ли этот файл другой программный поток, и не мешаем ли мы другой части программы, работающей с этим же файлом, своими действиями, другими словами нужно блокировать и вовремя разблокировать файл. Когда мы получаем доступ к файлу нам нужно либо прочитать из него данные, либо записать их туда. Познакомившись с возможностями PHP в этой области, приходим к функциям записи, чтения, а так же выбора текущей позиции, с которой начинается запись или чтение. После завершения мы должны закрыть файл и, соответственно, снять с него блокировку.
В общем, ничего сложного нет, но даже здесь опытный взгляд заметит очень важную особенность механизмов работы с файлами - пока файл не открыт, число возможных с ним действий ограничено. Класс «Файл» имеет различные состояния, которыми определяется его поведения. Что это за состояния?
1. Начальное состояние или «close». Файл закрыт и разблокирован;
2. Состояние чтения или «openRead». Файл открыт и заблокирован разделяемой блокировкой. Возможно только чтение из файла. Указатель перемещен в начало файла. Режим rb;
3. Состояние записи или «openWrite». Файл открыт и заблокирован исключетльной блокировкой. Возможна только запись в файл. Указатель перемещен в конец файла. Режим r+b.
Выделим функциональные возможности для каждого из состояний:
Состояние close
ОткрытьФайл – метод открывает файл в определенном режиме;
Состояние openRead
ОткрытьФайлВРежимеЗаписи
ЗакрытьФайл
ПрочитатьДанныеИзФайла
УстановитьТекущуюПозицию
ПолучитьТекущуюПозицию
ПолучитьРазмерФайла
ЗаблокироватьФайл
РазблокироватьФайл
Состояние openWrite
ОткрытьФайлВРежимеЧтения
ЗакрытьФайл
ЗаписатьДанныеВФайл
ПерезаписатьФайл
ОтчиститьФайл
УстановитьТекущуюПозицию
ПолучитьТекущуюПозицию
ПолучитьРазмерФайла
ЗаблокироватьФайл
РазблокироватьФайл
Как видите, многие методы в состояниях openRead и openWrite повторяются. Как при этом не дублировать код я объясню несколько позже, а сейчас возьмемся за структуру класса «File». Для начала вспомним паттерн «Состояние».
Пока все
Статья оказалась, как и предполагалась, довольно большой и подробной, из за чего пока завершаю ее написание. Если она окажется достаточно полезной для читателей, то обязательно продолжу, если же она будет обделена вниманием, то продолжать ее не вижу смысла. Пока дам исходники класса «File» и «Directory», а так же все, что с ними связанно. Если кому интересно, можете изучить их реализацию.
Архив: http://upwap.ru/1970088
Пароль: fromVisavi
Здравствуйте.
Наверно многие из вас знакомы с такими основополагающими понятиями ОО подхода, как Инкапсуляция, Полиморфизм, Наследование и Абстрагированние. Уверен что любая статья на эту тему многим покажется скучной и устаревшей, но судя по статьям, заполнившим интернет, делаю вывод что ничего кроме сухого копирования материала они не включают. Хотелось бы ответить эти важные грани ОО подхода с практической стороны.
Абстрагированние
Хотелось бы сразу отметить что каждый из этих механизмов отвечает за решение различных задач. Абстрагированние отвечает за упрощение понимания предметной области, то есть то, что мы моделируем в нашей программе. Писали ли вы когда нибудь на партах в школе целые чаты? Думаю хотя бы видели подобное. Представим что мы хотим написать программу, которая избавит школу от вандалов, и переместит столь необычный способ общения в электронную среду школы - компьютерный класс! Предметной областью здесь будет являться своеобразный чат, который нам необходимо изучить и реализовать программно. Для этого нам обязательно надо избавиться от всего ненужного. Какие объекты должны присутствовать в электронном чате? Проведем ассоциации с предметной областью:
конкретная запись на парте - сообщение;
имя человека, подписавшегося под надписью на парте - автор сообщения;
столбик на парте, содержащий сообщения, связанные одной темой - комната чата, или даже лучше, список сообщений.
И так начало положено, теперь можно приступить к абстрагированнию.
Абстрагированние это выделение наиболее важного для решения задачи из всей совокупности. Так, при реализации сообщения, важно ли нам какой ручкой оно было написано, или какой нажим был на парту при написании? Стоит ли включать в класс Сообщение такие свойства как: положение сообщения, скорость записи, площадь используемой части парты; или достаточно одного свойства - текст сообщения? При написани классов, важно отсеить ненужное и оставить только важное (абстрагироваться от шелухи). Как это правильно сделать? Попробуйте начать с осмысления поставленной перед программой задачи, все что по вашему мнению для решения задачи вам не понадобится, можно смело опускать, но нужно помнить - задача должна быть поставлена правильно - иначе абстрагированние только повредит. Если бы в нашем примере было важно сохранить такие особенности предметной области, как цвет пасты, можно было бы добавить в класс Сообщение такое свойство, как - цвет текста.
Инкапсуляция
Используется для упрощения программы и возможности дополнять классы без серьезных изменений.
Представим что мы уже написали часть нашего школьного чата, и вдруг нам понадобилось знать дату написания каждого сообщения, а в нашем классе Сообщение есть только свойства: текст и цвет текста. Думаю многие усмехнуться и подумают - да что тут сложного, написал дополнительное свойство в класс и все дела - а ведь именно в этом прелесть инкапсуляции. Если говорить конкретно, то это свойство системы позволяет скрывать одни данные от других. Другими словами пользоваться своими данными может только объект, другие объекты могут только взаимодействовать с ним, а не изменять работать с его данными напрямую. Нам достаточно перенести свойства класса в приватный список, и после чего добавлять или удалять из этого списка все что угодно, при этом другие объекты даже этого не заметят, потому что изначально им был закрыт доступ к этим данным. Приведу другой пример. Представьте что у вас есть класс, который отвечает за чтение данных из файла построчно. Этот класс имеет внутренний указатель на текущую строку и при вызове метода - получить строку - из файла считывается строка, на которую указывает указатель. Есть также методы: следующая строка и предыдущая строка - которые смещают указатель соответственно вперед и назад при этом проверяю не достигнут ли конец или начало файла. В этом классе свойство - Текущая строка - естественно должно быть защищенным, а за его изменение должены отвечать только методы самого класса, иначе любой объект сможет поменять указатель строки на произвольное значение (на пример -1), что приведет к ошибке. С другой стороны если вам понадобится усовершенствовать ваш класс, заменив свойство Текущая строка на свойство Текущее предложение, вам достаточно будет изменить закрытое свойство и код в соответствующих методах, а внешние файлы даже не заметят изменений, так как изменения происходят внутри самого класса.

Наследование
Средство, позволяющее повторно использовать код и последовательно усложнять классы без копипасты.
Об этом средстве могу сказать очень мало. Дело в том что наследование довольно мощный и в то же время простой механизм. В общем смысле Наследование позволяет реализовать иерархическую структуру ОО модели типа - родитель, потомок. Как этот механизм поможет нам в работе над школьным проектом? Представьте что вам подфортило, и ваш директор заказал у вас (естественно не за бесплатно) полноценный чат с блекджеком и.. то есть с возможностью модерирования сообщений. Любой модератор это тот же пользователь, только с дополнительными возможностями, так нужно ли копипастеть для создания класса Модераторы, или лучше унаследовать все возможности класса Пользователь классом Модератор и добавить этому классу дополнительные возможности.
Другой пример приведу из области биологии. Гусеница превращается в бабочку при этом она приобретает дополнительные возможности, а некоторые из возможностей изменяются, чему в ООП есть эквивалентный механизм переопределения методов при наследовании.
Полиморфизм
На мой взгляд, самый сложный в понимании механизм ОО подхода. Позволяет повторно использовать код и не заботится о изучении используемых в программе классов, а знать только то, что они должны делать.
Давайте представим, что многие из ваших одноклассников готовы оптимизировать методы ваших классов, но что же будет, если они начнут изменять их семантику (имя метода, возвращаемое им значение и необходимые для работы аргументы)? Программа просто перестанет работать, и каждая оптимизация потребует от вас изменять остальной код, чтобы он мог работать с новым, оптимизированным методом класса.
Для решения этой проблемы вы говорите своим одноклассникам - изменяйте методы, но не изменяйте их имен, типа возвращаемых значений и аргументов! Другими словами вы позволяете изменять только внутренний код метода, а не его внешний вид. При этом такие изменения не требуют менять программу!
Приведем другой пример. У вас есть класс для работы с файлом, тот самый, что позволяет считывать строки из файла построчно. Он имеет те же методы: ПрочитатьТекущуюСтроку(): String, СледующаяСтрока(): Boolean, ПредыдущаяСтрока(): Boolean. Вы определили интерфейс класса, то есть семантику его методов. Предположим, что другая функция принимает объекты данного класса и считывает с их помощью все из файла. Эта функция знает интерфейс класса и успешного его использует:
function ПрочитатьФайл(Файл $file){
$str = '';
do{
$str .= $file->ПрочитатьТекущуюСтроку();
}while($file->СледующаяСтрока())
return $str;
}
Теперь вам вдруг понадобилось изменить реализацию метода ПрочитатьТекущуюСтроку. Чтобы это не привело к необходимости изменять код функции ПрочитатьФайл, вам нужно сохранить семантику метода, то есть ПрочитатьТекущуюСтроку(): String - функция сохраняет имя, ее аргументы, и тип возвращаемого значения. Теперь можно без опасения изменять код метода, при этом изменять функцию нам не придется. Удобно, правда?
Полиморфизм позволяет реализовывать функции, методы и классы так, чтобы при изменении их реализации, не приходилось изменять другой код.
Собственно, такая структура как Интерфейс, позволяет определить структуру класса и не заботится о его реализации.
Я не приверженец практики без теории, потому постараюсь попутно сопровождать весь код важной для незнакомых с ОО архитектурой информацией.
И так хочется продемонстрировать вам мое решение тривиальной ситуации с помощью ОО подхода, которое в последующем сильно экономило мое время.
Предыстория
Как то раз возникла задача организовать класс "Хранитель", которые позволяет хранить в себе текущее состояние родительского объекта и восстанавливать его когда это потребуется, при чем раскрывать это состояние позволяется только родительскому объекту, дабы не нарушить инкапсуляции (кто знаком с шаблонами проектирования, мне понадобился обычный Хранитель). Реализовал его за несколько минут и решил пойти дальше: а почему бы не организовать такой Хранитель, который сможет переносить состояния объектов между обращениями к скрипту? Ведь для этого достаточно воспользоваться каким нибудь хранилищем типа сессии или БД. Идея мне очень понравилась (с учетом того, что я несколько месяцев реализую собственную ORM) и я принялся за разработку.
Важные вопросы архитектуры
1. Первым вопросом, который предстояло решить был - придется ли мне переписывать каждый класс Хранитель под отдельное Хранилище? Другими словами стоит ли создать два класса: ХранительСессии и ХранительБД - чтобы каждый тип Хранителя мог переносить состояния в разных Хранилищах? Здесь заметно явное дублирование кода, так как многие методы будут идентичны в этих двух Хранителях, различаются только методы доступа к данным Хранилища.
2. Второй вопрос прямо вытекает из первого - правильно ли использовать алгоритмы доступа к данным сессии, запуск сессии, добавления и удаления в ней данных, через интерфейсы и методы самого Хранителя? Ведь каждый класс должен выполнять строго ограниченные задачи, а любой Хранитель уже выполняет задачу хранения и возврата состояния родителя?!
3. Третий вопрос возник, когда я задумался о вынесении механизмов работы с Хранилищами в отдельный класс - как организовать Хранитель так, чтобы он мог безошибочно найти себя в Хранилище после перезапуска сервиса, т.е. в той ситуации, когда не будет ни родительского объекта, ни самого Хранителя, а останутся только данные в БД или сессии? Хотелось не отходить далеко от принципов ORM и воспользоваться идентификацией по известным атрибутам, на пример, задав объекту его OID или Login и Password, восстановить по ним Хранителя.
Важные решения архитектуры
Возможно, опытные ООПшники уже набросали в голове ответы на все поставленные ранее вопросы, но я распишу все более подробно для остальных (очень жаль, что в статью нельзя встраивать изображения).
1. Для решения первого вопроса мы воспользуемся композицией и делегированием, а так же паттерном Стратегия. Что означают и как применяются эти страшные слова, я объясню чуть позже.
2. Для решения второго вопроса мы используем унифицированный и довольно упрощенный интерфейс работы с Хранилищами. Ведь нам нет необходимости использовать все возможности таких интерфейсов как MySQLi, для решения задач Хранителя достаточно: select, update, remove, input. Причем любое хранилище, будь то сессия, БД или файл может без проблем реализовать эти функции.
3. Не отходя далеко от принципов ORM, я поступил именно так, как сказано в них, а именно использовал известные данные о структуре объекта, для аутентификации его из Хранилища.
Самое интересное
И так реализация и подробное описание всех механизмов. Начну сверху и медленно проберусь в глубь гвнокода.
Что я подразумеваю под Стратегией, композицией и делегированием? Для начала давайте повторим, что такое интерфейс в PHP. Интерфейс это гарантия того, что все классы, его реализующие, будут иметь те методы, что в нем описаны. Интерфейсы так же называют Договором, так как все классы "подписавшиеся" на интерфейс "обязаны" выполнить все его методы ("условия"). Если немного подумать, то любой Хранитель, способный переносить данные между вызовом сервисов, все лишь должен иметь объект, обращающийся к Хранилищам и передавать ему свое состояния для записи и восстановления. Другими словами, зачем вшивать алгоритмы работы с Хранилищами в сам Хранитель, когда можно написать отдельный класс, целью которого будет получение данных и передачу их в Хранилище. Это называется инкапсуляцией методов. Мы выносим метод за пределы класса, и упаковываем его в другой класс. Здесь важно сделать оговорку для слова "упаковываем". Нет! Мы не используем класс как хранилище методов и свойств, это не так. Мы создаем отдельный, полноценный класс, целью которого будет работа с различными Хранилищами. Это очень часто встречающийся подход в ООП. Он позволяет решить еще одну важную задачу - сделать код более гибким. Представьте, что мы реализуем два Хранителя, один хранит данные в Сессии, другой в БД. Для этого одному мы передаем экземпляр класса, работающий с Хранилищем БД, а другому экземпляр класса, работающий с Хранилищем Сессии. Причем тот и другой реализуют упрощенный интерфейс: select, update, remove, input - таким образом, Хранителю не важно, что это за объект и с каким Хранилищем он работает, достаточно знать, что он реализует методы записи и восстановления, которые нужны Хранителю для работы. Такой подход называется композицией - мы передаем объекту другой объект, который будет выполнять некоторые функции за него. Непосредственно использование Хранилища Хранителем называется делегированием задач. Хранитель передает часть работы Хранилищу, делегируя ему некоторые необходимы операции, а Хранилище гарантирует, что эти операции будут выполнены.

Инструменты
Что нам необходимо для воплощения идеи в жизнь?
1. Хранитель, способный получать текущее состояние родителя и делегировать задачу записи состояния в Хранилище, а так же восстанавливать состояние из него.
2. Унифицированный и упрощенный интерфейс Хранилища, дабы не заботится о том, с каким именно Хранилищем работает Хранитель.
3. Полноценный класс (не Хранилище) для работы с сессиями, так как Хранилище лишь унифицирует интерфейс, никаких дополнительных задач обращения, и работы с сессиями у него нет. Интерфейс работы с БД у нас уже имеется.
4. Интерфейс родителя Хранителя, который будет следовать правилам ORM.
Класс работы с сессией
Вот я задумался над классом для работы с сессией. Он должен быть довольно мощным, и при этом не было времени зацикливаться на его разработке, потому он должен оставаться гибким для расширения в будущем. Это будет полноценный класс для работы с сессией как с реляционной таблицей, но не в данном контексте, а когда ни-будь потом.
При работе с сессией стоит уловить важный принцип: зависимость объекта от его состояния. Что это значит? Что будет если попытаться записать данные в еще не открытую сессию? Правильно, ничего не будет. Уловили ход мысли? Наверно нет ). Я хочу сказать что каждый объект, работающий с сессией находится в различных состояниях. Когда сессия не открыта, мы не можем записывать данные или получать их из сессии, это состояние я назвал notConnection (по аналогии с БД). В этом состоянии объект обладает несколько другими возможностями, нежели в состоянии connection, которое он приобретает после соединения с сессией.
Опять-таки опытные ООПшники уже выбрали решения и показывают пальцем в сторону паттерна "Стратегия". Что это такое и с чем его есть?
Стратегия
Стратегия это такой механизм, позволяющий изменять объект в зависимости от его внутреннего состояния так, что будет казаться, будто мы работаем с разными объектами. Нам такое поведение как раз кстати, для нашего класса работы с Сессией.
Мы создаем класс:
<?php
class session implements Isession, state\context{
protected
  $stateConnected,  // Хранит состояние подключенности к сессии
  $stateNotConnected, // Хранит состояние неподключенности к сессии
  $state; // Храним текущее состояние
public
function __construct(){ // Определяет текущее состояние объекта
    if(session_id())
      $this->state = $this->stateConnected = new stateConnected($this);
    else
      $this->state = $this->stateNotConnected = new stateNotConnected($this);
  }
  function setState(state\state $state){ // Устанавливает состояние объекту
    $this->state = $state;
  }
  function getStateConnected(){ // Возвращает состояние соединения с сессией
    if(empty($stateConnected))
      $stateConnected = new stateConnected($this);
    return $stateConnected;
  }
  function getStateNotConnected(){ // Возвращает состояние неподключенности к сессии
    if(empty($stateNotConnected))
      $stateNotConnected = new stateNotConnected($this);
    return $stateNotConnected;
  }
/* Пока код может и непривычный, но такой же тривиальный. 
Мы просто реализовали механизм упаковки состояний и доступа к ним*/
  // Далее ужасно тривиальный код
  function connection($name = 'PHPSESSID'){
    return $this->state->connection($name);
  }
  function createTable($name, array $fieldsNames){
    return $this->state->createTable($name, $fieldsNames);
  }
  function removeTable($name){/*такой же код*/}
  function input($name, array $data){/*такой же код*/}
  // и т.д.
}
?> 
Заметили несколько важных особенностей кода? Класс не реализует алгоритм доступа к данным вообще )). Он просто обращается к своему текущему состоянию (делегирует), а уже то выполняет, необходимы действия по работе с сессией. Причем состояние notConnection не может получить данные из сессии или записать их туда, в то время как состояние connection это делать может. То есть при смене состояний объекта его поведение резко изменятся с "не могу ничего" до "могу все". Продолжим:
<?php
interface Isession{ 
/* Класс работы с сессией, а так же все состояния должны 
реализовать этот интерфейс чтобы быть уверенным в том, 
что класс не будет менять свой интерфейс при переходах 
между состояниями, а изменится только поведение */
  function connection($name = 'PHPSESSID');
  function createTable($name, array $fieldsNames);
  function removeTable($name);
  function input($name, array $data);
  function select($name, array $conditionSelection);
  function remove($name, array $conditionSelection = null);
  function update($name, array $data, array $conditionSelection = null);
  function findLines($name, array $conditionSelection);
  function destroy();
}

class stateNotConnected extends state\state implements Isession{
  function connection($name = 'PHPSESSID'){
    session_name($name);
    session_start();
// Внимательно посмотрите эту строчку
    $this->context->setState($this->context->getStateConnected());
/* Класс состояния самостоятельно изменяет состояние 
родителя при подключении к сессии! Здесь проявляется механизм
автоматической смены поведения при изменении состояния объекта */
    return true;
  }
  function createTable($name, array $fieldsNames){
    echo 'Невозможно выполнить действие, соединение не установленно';
  }
  function removeTable($name){
    echo 'Невозможно выполнить действие, соединение не установленно';
  }
  // Далее в том же духе
}

class stateConnected extends state\state implements Isession{
  /* Классы состояний как и любые другии могут иметь 
собственные методы */
  private function getLines($name, array $conditionSelection = null){
    $name = (string)$name;
    if(empty($_SESSION[$name]))
      return false;

    if($conditionSelection && count($conditionSelection) != 0)
      $lines = $this->findLines($name, $conditionSelection);
    else{
      reset($_SESSION[$name]);
      $lines = $_SESSION[$name][key($_SESSION[$name])];
    }
    return $lines;
  }

  public
/* Повторного соединения не требуется, потому просто 
останавливаем работу метода */
  function connection($name = 'PHPSESSID'){
    return true;
  }
/* Это уже полноценный метод работы с сессией, вынесенный из 
класса в класс состояния */
  function createTable($name, array $fieldsNames){
    $name = (string)$name;
    if(empty($name))
      return false;
    if(empty($_SESSION[$name])){
      $_SESSION[$name] = array();
      foreach($fieldsNames as &$v)
        $_SESSION[$name][$v] = array();
      return true;
    }
    else
      return false;
  }
  function removeTable($name){
    $name = (string)$name;
    if(!empty($_SESSION[$name])){
      unset($_SESSION[$name]);
      return true;
    }
    else
      return false;
  }
  function input($name, array $data){/*Все в том же духе*/}
?>
Давайте подробно разберем этот код. Мы определили интерфейс ISession который реализуется как классом session (класс высокого уровня), так и его состояниями stateNotConnection и stateConnection (классы низкого уровня). Теперь мы уверены что вызывая метод класса session он сможет без затруднений делегировать его тому же методу класса состояния, так как они оба "обязаны" реализовать одинаковый интерфейс. Так же мы определили два класса состояний, первый выполняет очень мало функций, второй очень много. При переходе из состояния в состояние возможности класса будут увеличиваться или уменьшаться. Причем сам переход описан в классе состояния (это не обязательно, есть много других способов организации перехода между состояниями). Так же каждый класс состояния реализует интерфейс state\state (ранее я подключил пакет use PPHP\patterns\state as state; для этого). Это позволяет классу session держать в переменной $state не конкретное состояние notConnection или connection, а любое состояние, реализующее интерфейс state\state. В этом проявляется гибкость ОО подхода. Чтобы расширить класс нам больше нет необходимости лезть в код, достаточно добавить еще одно состояние. Такой код становится более понятным, так как можно искать реализацию по состояниям, а не в конструкциях типа:
<?php
if($this->state = 'notConnection'){
// ...
}elseif($this->state = 'connection'){
// ...
}elseif(//...)
?>
Возможно, пока вам покажется, что такая конструкция очень громоздка и создает кучу ненужных классов. На самом деле здесь проявляется вся прелесть ООП.

Унификация и адаптация
Пока наша практика не касалась поставленной задачи. Сейчас пора приступить к написанию общего для всех хранилищ интерфейса. Мы используем это для того, чтобы хранителю не пришлось заботится о том, с каким хранилищем он работает (MySQLi, Файл, Сессия и т.д.), все что ему известно, это то, что хранилище реализует методы записи, восстановления, обновления и удаления данных.
Если бы мы не унифицировали и не адаптировали эти интерфейсы (интерфейсы доступа к различным хранилищам) нам пришлось бы программировать класс Хранителя под каждый из интерфейсов, что привело бы к дублированию кода.
Для начала определим тот интерфейс, который нам будет необходим для полноценной работы Хранителя. Ничего сложного здесь конечно нет. Что должен уметь Хранитель? Получать состояние объекта и в нужный момент восстанавливать это состояние (можно подробно изучить этот механизм в шаблоне Memento). Хранитель же, который способен переносить состояния между обращениями к серверу (будем называть его Постоянным Хранителем) должен обладать теми же свойствами (имеется в виду не программными свойствами) что и Хранитель, то есть способность получать и возвращать состояние родителя, но кроме того еще и записываться в Хранилище, восстанавливаться из него, а так же удаляться и обновляться:
1) Сохранить Хранитель;
2) Восстановить Хранитель;
3) Обновить состояние Хранителя в Хранилище;
4) Удалить состояние Хранителя из Хранилища.
Для этого нам понадобится от Хранилища всего 4 метода:
1) Input - добавляет новые данные в Хранилище;
2) Select - возвращает существующие в Хранилище данные;
3) Update - обновляет существующие данные в Хранилище;
4) Remove - удаляет данные из Хранилища.
Как можно заметить предложенные операции могут быть реализованы для любого хранилища, будь то конкретная СУБД, файл или сессия.
Приступая к унификации и адаптации
<?php
/*
Унифицированный интерфейс, который должен быть реализован всеми
Хранилищами, используемыми в решении задачи
*/
interface Istorage{
  // Получает имя таблицы и добавляемые данные
  function input($name, array $data); // Метод записи
  // Получает имя таблицы и информацию для поиска
  function select($name, array $conditionSelection = null); // Метод получения
  // Получает имя таблицы и информацию для поиска
  function remove($name, array $conditionSelection = null); // Метод удаления
  // Получает имя таблицы, обновляемые данные и информацию для поиска
  function update($name, array $data, array $conditionSelection = null); // Метод удаления
}
?>
Внимательно посмотрите этот код. Он определяет, какими методами должен обладать класс, чтобы его мог без труда использовать Постоянный Хранитель. Естественно такой упрощенный интерфейс приводит к лишению многих возможностей, предоставляемых конкретными классами работы с СУБД, но нам они и не нужны.
Важно отметить, что получаемые и возвращаемые данные так же имеют определенную структуру помимо типа. Так аргументы data всегда имеют структуру [имя поля => значение, ...], а аргументы conditionSelection [имя поля => искомое значение, ...].
<?php
// Адаптация класса доступа к сессии
class sessionStorage implements Istorage{ // Реализация унифицированного интерфейса
  protected static $storage; 
/* Здесь хранится объект для доступа к сессии. Экземпляры класса
используеют этот объект в своих целях */
  function __construct(){
    if(empty(self::$storage) || !(self::$storage instanceof session\Isession)){ 
// Если объект для работы с сессией еще не существует, создаем его
      self::$storage = new session\session();
      self::$storage->connection();
    }
  }

  function input($name, array $data){
    // Делегирование
    return self::$storage->input($name, $data); 
  }
  function select($name, array $conditionSelection = null){
    /* По сути то же делегирование, но так как класс для работы с
сессией у нас возвращает данные со структурой [имя поля => [номер строки => значение, ...], ...], то мы должны привести его к 
разрешенной в интерфейсе структуре 
[номер строки => [имя поля => значение, ...], ...], это обязательно
в унификации */
    if(!($sample = self::$storage->select($name, $conditionSelection)))
      return array();
    $result = array();
    reset($sample);
    foreach($sample[key($sample)] as $krow => &$varray)
      foreach($sample as $kfield => &$v){
        if(!is_array($result[$krow]))
          $result[$krow] = array();
        $result[$krow][$kfield] = $sample[$kfield][$krow];
      }
    return $result;
  }
  function remove($name, array $conditionSelection = null){
    return self::$storage->remove($name, $conditionSelection);
  }
  function update($name, array $data, array $conditionSelection = null){
    return self::$storage->update($name, $data, $conditionSelection);
  }
}
?>
Код может показаться довольно сложным, потому разберем некоторые важные моменты.
Во-первых, адаптированный класс работы с сессией реализует унифицированный интерфейс для того, чтобы он обязательно имел указанные в интерфейсе методы.
Во-вторых, все получаемые аргументы и возвращаемые значения должны иметь одну и ту же структуру и тип. Нам ведь важно забыть о том, с каким конкретно классом мы работаем, это лишняя для унифицированных классов информация, потому и используемые для работы данные должны быть ожидаемыми. Это напоминает принцип "черного ящика". Мы передаем ему определенные данные с определенной структурой, и получает так же заранее определенные данные в качестве результата.
<?php
class MySQLStorage implements Istorage{
  protected static $storage;
  public
  function __construct(\mysqli $storage=null){
    if(empty(self::$storage) || !(self::$storage instanceof mysqli)){
      if(!empty($storage))
        self::$storage = $storage;
      else
        throw new Exception('Нет активного соединения с БД');
    }
  }

  function input($name, array $data){
    $changes = array();
    foreach($data as $k => &$v)
      $changes[] ='`'.$k.'` = "'.$v.'"';
    //echo 'Запрос:  '.'INSERT INTO `'.$name.'` SET '.implode(' , ', $changes);
    if(self::$storage->query('INSERT INTO `'.$name.'` SET '.implode(' , ', $changes))){
      $queryResult = self::$storage->query('SELECT LAST_INSERT_ID()');
      return array_pop($queryResult->fetch_array());
    }else
      return false;
  }
  function select($name, array $conditionSelection = null){
    $where = array();
    foreach($conditionSelection as $k => &$v)
      $where[]='`'.$k.'` = "'.$v.'"';
    $queryResult = self::$storage->query('SELECT * FROM `'.$name.'` WHERE '.implode(' AND ', $where));
    if($queryResult === false || $queryResult->num_rows == 0)
      return array();
    $result = array();
    while($result[] = $queryResult->fetch_assoc());
    array_pop($result);
    return $result;
    //echo 'Запрос:  '.'SELECT * FROM `'.$name.'` WHERE '.implode(' AND ', $where);
  }
  function remove($name, array $conditionSelection = null){
    if(!empty($conditionSelection)){
      $where = array();
      foreach($conditionSelection as $k => $v)
        $where[]='`'.$k.'` = "'.$v.'"';
      $where = 'WHERE '.implode(' AND ', $where);
    }
    else
      $where = '';
    //echo 'Запрос:  '.'DELETE FROM `'.$name.'` '.$where;
    return self::$storage->query('DELETE FROM `'.$name.'` '.$where);
  }
  function update($name, array $data, array $conditionSelection = null){
    $changes = array();
    foreach($data as $k => $v)
      $changes[] ='`'.$k.'` = "'.$v.'"';
    if(!empty($conditionSelection)){
      $where = array();
      foreach($conditionSelection as $k => $v)
        $where[]='`'.$k.'` = "'.$v.'"';
      $where = 'WHERE '.implode(' AND ', $where);
    }
    else
      $where = '';
    //echo 'Запрос:  '.'UPDATE `'.$name.'` SET '.implode(' , ', $changes).' '.$where;
    return self::$storage->query('UPDATE `'.$name.'` SET '.implode(' , ', $changes).' '.$where);
  }
}
?>
Описывать как реализована адаптация класса работы с MySQLi я не будут, попробуйте сами это сделать придерживаясь правилам адаптации и унификации.

Простой Хранитель
Приступим к реализации простого Хранителя, т.е. такого хранителя, который не умеет переносить состояния между обращениями к серверу.
Начнем с определения: Хранитель - позволяет не нарушая инкапсуляцию зафиксировать и сохранить внутреннее состояния объекта так, чтобы позднее восстановить его в этом состоянии.
Его структура элементарна:
<?php
class memento{
  protected
  $parent, // Объект, создавший хранителя и состояние которого хранителем сохраняется
  $properties = array(); // Само состояние
  public
  function __construct(originator $parent){
    $this->parent = $parent;
  }
  function setState(array $properties){
    $this->properties = $properties;
    return true;
  }
  function getState(originator $parent){
/* Возврат состояния только родителю. 
Конечно это неправильная реализация хранителя, так как она нарушает
инкапсуляцию, но к сожалению PHP не обладает необходимыми для
реализации "хорошего" Хранителя конструкциями и возможностями */
    if($this->parent = $parent)
      return $this->properties;
    else
      return false;
  }
}
?>
Хранитель работает с родителем, который должен наследоваться от следующего абстрактного класса:
<?php
abstract class originator{
  protected
  abstract function newMemento(); // Присмотритесь к этому методу

  public
  function setMemento(memento $m){
    $prop = $m->getState($this);
    foreach($prop as $k => $v)
      $this->$k = $v;
  }
  function createMemento(){
    $memento = $this->newMemento(); // А так же к этой строчке кода
    $memento->setState(array_intersect_key(get_object_vars($this), get_class_vars(get_class($this))));
    return $memento;
  }
}
?>
Зачем мы использовали два метода newMemento и createMemento для одной задачи - создание и инициализация Хранителя? Просто здесь мы использовали шаблон "Фабричный метод" чтобы не приходилось в будущем дублировать код инициализации Хранителя. Если подробнее, то родитель сохраняет свое состояние в Хранителе при его создании методом createMemento. Этот метод выполняет функции фабрики и одновременно инициализирует Хранитель. Хранители могут быть разными (Простой Хранитель, Постоянный Хранитель), но методы инициализации всегда одинаковы, потому мы и отделили алгоритмы создающий Хранителя, от алгоритма его идентифицирующего использовав шаблон "Фабричный метод".
Решение задачи
И так последний класс - Постоянный Хранитель:
<?php
class constantMemento extends memento{
  protected static $storage; // То самое унифицированное хранилище

  public
  function save(){
    self::$storage->input(get_class($this->parent), $this->properties);
// Используем унифицированное Хранилище и не задумываемся о том, как оно работает
  }

  static function restore(originator $parent, array $identifier){
    $result = self::$storage->select(get_class($parent), $identifier);
    if($result){
      $memento = new self($parent);
      $memento->setState(array_pop($result));
      return $memento;
    }
    return false;
  }

  function update(array $identifier){
    return self::$storage->update(get_class($this->parent), $this->properties, $identifier);
  }

  function recover(array $identifier){
    return self::$storage->remove(get_class($this->parent), $identifier);
  }

  static function setStorage(storage\storage\Istorage $storage){
// Шаблон "Стратегия" в этом классе позволяет менять используемое Хранилище "на лету"!
    self::$storage = $storage;
  }
}
?>
Наследование от memento позволяет нам отделить код простого Хранителя от возможностей Постоянного Хранителя. Так же наследование позволяет без изменения originator использовать Постоянного Хранителя для восстановление состояния Родителя (никакого дублирования кода). Обратите особое внимание на $storage. Переменная вынесена из экземпляра в классификатор (сделана static). Здесь мы придерживаемся принципов паттерна "Информационный эксперт" который говорит нам что методы (Хранилище это объект, который используется как унифицированный набор методов) должны быть вынесены в тот класс, который обладает необходимой информацией для метода. Класс Постоянного Хранителя обладает всеми необходимыми данными, а использование Хранилище для каждого экземпляра класса приводит к лишней работе и использованию дополнительной памяти (не обязательно конечно).
Результаты
Мы создали довольно простой класс "Постоянный Хранитель", который позволяет реализовать очень важную операцию - СОХРАНЕНИЕ ОБЪЕКТОВ В ЛЮБЫХ ПОСТОЯННЫХ ХРАНИЛИЩАХ! Если вам нужно будет сохранить состояние Пользователя в БД, Файле и в Сессии, то используя этот класс, у вас не возникнет никаких проблем и дублирования кода.
Конечно, класс не совершенен, так как не было времени на рефакторинг, но он выполняет свои задачи хорошо и достаточно гибок для расширения за счет использования делегирования, унификации, адаптации, интерфейсам, шаблонов проектирования.
Хотелось бы отметить, что эта статья не призвана учить ООП, она лишь показывает какие проблемы можно решать с помощью ОО подхода. Если вы что-то не поняли из статьи, ничего страшного, главное что вы увидели возможности и прелести ООП для решения проблем дублирования кода и упрощения реализации.
Удачи в постижении ОО подхода.
Продолжая цикл статей об объектно-ориентированному подходу к организации программной архитектуры и мышлению, хотелось бы затронуть некоторые исторические эпизоды становления объектно-ориентированной мысли.
Рассмотрим становление процедурного подхода. Описание повторяющихся алгоритмов медленно переростает в необходимость повторного использования кода. Здесь на сцену выходят функции, которые позволяют вместить в себя алгоритм и впервые реализуются принцип "черного ящика". Действительно, функция позволят определить входные и выходные данные, а метод преобразования скрыть реализацию от пользователя. Другими словами, мы лишь знаем какие данные функция получает и что отдает функция, а ее работа нас не касается, и обычно, это позволяет упрощать разработку приложений за счет использования "черных ящиков". Помимо функций процедурный подход внес такие особенности как - тип данных (предпосылки ОО подхода) и библиотека (совокупность ориентированых на определенную область функций), эти нововведения позволили программистам использовать сторонние разработки в собственных решениях, что упростило и ускорело разработку программных решений.
ОО подход никак не заменяет принципов процедурного, а наоборот расширяет его. Именно из за этого, многие новички высказывают мнение - "Нет ничего объектно-ориентированного, с чем не может справится процедурный подход" - и это верно! Объекты и классы лишь позволяют более рационально использовать те самые переменные, функции и типы, что были со времен становления таких языков как фортран, си и паскаль, но новые конструкции позволяют использовать эти элементарные компоненты более эффективно. Представим сложную программу, включающую такие функции как подключение сторонних модулей, разделение прав доступа и гибкость в сопровождении. Все это отдельные, но взаимодействующие модули, с реализацией который процедурный подход слабо справляется, так как не позволяет скрывать переменные и методы работы с ними. Приходится помнить алгоритмы работы частей модуля и подстраивать другие субмодули к ним, это конечно приводит к некоторым границам возможности программиста, за которыми сопровождать и расширять такие программы будет очень сложно.
С другой стороны ООП позволяет облегчить разработку сложных архитектур за счет сокрытия данных. Представьте что бы разрабатываете каждый модуль через разработку простых модулей, содержащих несколько переменных и функций, это гораздо проще чем держать в голове все существующие в программе функции и переменые, ведь так? Вот в этом "сахар" ОО подхода, он позволяет закрыть модуль в классе, интерфейсе и композиции. Другими словами библиотека функций заменяется библиотекой модулей.
Здравствуйте.
Перечитывал несколько раз статьи этого блога и решил пока не заниматься переосмыслемиен парадигмы, а затронуть вопрос неверного толкования при первом знакомстве с ООП.
Другими словами чем не является объектная ориентация и как не стоит ее применять.
1. Конечно же сначало нужно выбросить из головы такое понятие как класс и посмотреть вокруг. Понимание принципов ОО начинается с осмысления того, что такое окружающие нас объекты и что они могут. Начинать изучение ООП с термина "Класс" это главная ошибка, сродни изучению программирования с определения функций (функции лишь способ группировки алгоритмов, а не алгоритм). И так что же такое объект? Возьмем что то простое, шар. Шар это объект с точки зрения ОО и логики. Какими словами можно описать шар? Ну скажем его плотностью, цветом, диаметром, материалом (прежде чем читать дальше, попробуйте продолжить список и назвать еще пару-тройку подобных характеристик шар). Хорошо. Шар имеет некоторые характеристики, с этим вроде понятно. Но вспомним сколько шаров есть с совершенно различными характеристиками. Игровые шары (футбольные и баскетбольные мячи, шарики для пинг-понга и т.д.), мячи технические (на пример в подшибниках), оружейные (ядра и пули). У них довольно много общего (попробуйте назвать одинаковые характеристики для разных шаров, на пример диаметр). Вот здесь на арену выходит "Класс". Класс - это совокупность общих характеристик разных объектов. На пример класс - футбольных мячей. Объекты этого класса будут конкретными футбольными мячами, какие-то более новые, какие-то более рваные, но характеристики их не будут отличаться, что впрочем нельзя сказать если сравнить их с объектами класса - ядро. Другими словами классы определяют и содержат общее для объектов. Создавая объект на основе класса, мы будем точно знать что этот объект содержит те же характеристики, что и его класс.
2. Теперь забудьте про функции в классах. Классы и объекты это не способ удобного хранения переменных и (что более актуально у новичков) функций. Класс это то общее что есть у его объектов, а характеристики класса и методы (функции) это то, что есть у всех объектов этого класса. В чем прелесть этого подхода. Вспомним получение от пользователя данных о его электронной почте. Нужно проверить все ли там правильно заполнено, а при необходимости резать адрес почты на части чтоб получить скажем его домен или логин. А теперь представьте что есть класс "АдресЭлектроннойПочты" который включает методы - получить весь адрес, получить логин из адреса, получить домен из адреса. И мы знаем что все объекты этого класса будут иметь эти методы и при их вызове мы всегда получим строку корректными данными не беспокоясь о подмене или безопасности. Более того, мы можем даже не знать как именно класс и его объекты хранят и возвращают эти данные, нам это больше не важно, единственно что нам важно, это то, что у них эти методы есть и они дают нам ожидаемые данные. Другими словами методы класса это те его функции, которые позволяют нам с его объектами взаимодействовать, изменять их, получать данные (которые может предоставить объект), заставлять делать что то, а не любые, возможно даже не связанные с объектом функции.
3. Медленно вытекающее из 2. Классы должны выполнять что то небольшое но хорошо. Это значит что включать в класс "адресЭлектроннойПочты" такие методы как "отправить письмо" или "найти в адресе подстроку" не правильно. Вдумайтесь в название класса - адрес электронной почты - это вам не - почтовый клиент - и тем более не - центр обработки строковых данных - это куски адресов почты которые имеют определенную структуру и не более того. Следовательно методы класса должны не выходить за рамки этой структуры.

И так продолжу писать о том, чем не является объектно-ориентированный подход и как не следует его понимать.
4. ООП не способ уменьшить или ускорить код, тем более это не способ улучшить процедурный подход, это совершенно новый взгляд на программирование и разработку. Если в основе ранних языков программирования лежит алгоритм и способ его группировки - функция, то в ООП во главе стола объект, принцип "черного ящика", повторение кода и модульность. Что это означает? Принцип "черного ящика" гласит [Сталинским поучительным тоном] - мы знаем что в черный ящик что то входит и выходит результат, а что происходит в нем мы не знаем - чистейшая инкапсуляция (сокрытие) действий и данных. С повторением кода конечно отлично справлялись функции, но классы позволяют использовать такой важный принцип как наследование кода и его расширение в субклассах (целью статьи не является описание принципов ООП, а посеяние зерна правильного направления, потому узнайте о наследовании и полиморфизме уже сами). Модульность ОО подхода обеспечивается за счет того, что объекты и классы знают только об интерфейсах (наборе доступных методов) других объектов и классов, что позволяет заменить объект другим, с таким же интерфейсом, при этом программа будет работать без перебоев. Более того, возможна динамическая смена поведения программы без изменения кода (без ООП этого добиться довольно сложно, можете попробывать сами изменить поведение функции в процессе работы программы). Если интересно больше узнать об этом, почитайте про шаблоны проектирования "Стратегия" и "Состояние".
5. ООП не усложняет код, он его расширяет новыми возможностями. Часто при использовании ОО подхода код становится в разы больше, но любому опытному ОО разработчику будет легко в нем разобраться потому, что он более абстрактен и приближен к человеческой логике и структуре мышления, нежели математический подход процедурного стиля. Постоянно растущее число небольших классов, выполняющих элементарные операции это удобно, так как работать с простым проще, чем со сложным (вспомним учительское - от простого к сложному).
6. ООП это НЕ СЛОЖНО. Просто нужно временно перестать сопротивляться новому в защиту процедурного подхода (мы же не бабки на лавочке) и попытаться понять принципы работы ООП. Они довольно интересны, но в то же время необычны и часто непонятны. Попробуйте начать сначала и придерживайтесь пути ОО хотя бы в течении пары недель, и вы удивитесь как раньше жили без ОО подхода к программированию.
P.S.: пожалуйста, не используйте классы и объекты как копилку функций и переменных, для этих целей существуют именованые области видимости. Строго ограничивайте возможности класса. Класс это не бог, и делать он умеет совсем не много.

Продолжая серию затрону еще несколько важных ошибок начинающих умов.
7. ООП это модно и в этом вся сложность. Незнакомые с ООП люди в свое время создали такой гул вокруг этой парадигмы, что чуть ли не любой продукт со словом - объектная ориентация - становился уважаемой и важной "программой". Вы и сами наверно видели не раз такие записи - ЗЦ написан с ООП, форум на ООП, гостевая на классах - долгое время находясь в поисках помошника я пересматривал все эти "супер вап программы" и не нашел ни одного правильно реализованного ОО решения! Современные вап мастера очень мало знают об ОО подходе к написанию программ. Для них это скорее модное слово из которого они знают совсем не много. Не нужно подходить к ООП как к новой, модной игрушке, потому что парадигма таковой не является. ОО подход это логичная, довольно интересная и функциональная смесь, облегчающая сопровождение, расширение, модульность и разработку программ.
8. ООП это не только понимание классов, объектов, свойств и методов. Как часто я встречался с собеседниками, которые закрывали тему, как только она заходила о таких простых вещах как "паттерны" проектирования и ООП. Знали бы вы как меня радует то описание MVC, что я читаю в различных темах. Мало кто из авторов этих тем знает что модель-вид-контролиет это паттерн паттернов, потому что состоит из очень не малого числа более простых шаблонов. Большинство сегодня знают только общие определения ОО подхода, такие как класс, объект, метод и т.д. даже не пытаясь углубиться в понимание таких фундаментальных и не менее важных вещей как интерфейс, абстрактный класс, дружественная функция и композиция, а ведь именно эти механизмы позволяют делать ОО код более гибким и модульным.
9. Не верьте сплетням об ООП. Самыми лучшими источниками информации были и остаются признанные книги (стоит ли перечислять какие именно? О них узнает без проблем любой кто захочет), а изучение семантики и принципов, заложенных в таких языках как C++ помогают еще больше понять эту концепцию. Статьи о ООП, которыми пестрит интернет, это редкие помошники, которые чаще направляют на неверный путь. Более того язык PHP не самое хорошее начало для изучения ООП, конечно для понимания всех его граней необходимо изучить его реализацию в разных языках (достаточно сравнить javascript и PHP, которые поддерживают в разной степени ОО и увидеть насколько различается их реализация), но на сегодняшний день PHP довольно слабо развит в этом направлении (хотя последние изменения в нем меня очень обрадовали).
Статью писал для своего сайта, но по некоторым причинам решил все свои статьи перезалить сюда (что бы не утерять)!

Model View Controller позволяет разделить данные, представление и обработку действий пользователя на три отдельных компонента.

Модель (Model). Модель предоставляет данные (обычно для View), а также реагирует на запросы (обычно от контроллера), изменяя своё состояние.
Представление (View). Отвечает за отображение информации (пользовательский интерфейс).
Поведение (Controller). Интерпретирует данные, введённые пользователем, и информирует модель и представление о необходимости соответствующей реакции.
---------------------------------------------------------------------------------------------------
Как-то задался вопросом о работе MVC, и не имея представления о реализации, я искал в гугле решение своего вопроса!
Гугл привел меня к вот этой статье http://habrahabr.ru/blogs/php/31270/
Я поглядел на "это" все, и решил попробовать! Особого удовлетворения от работы с данной моделью я не ощущал, и решил проблему по своему...

Вот так выглядит структура моей модели:
|- /includes/
|- /includes/classes/
|- /includes/classes/MVC_BaseController.class.php
|- /includes/classes/MVC_Registry.class.php
|- /includes/classes/MVC_Router.class.php
|- /includes/classes/TestClass.class.php
|- /includes/controllers/
|- /includes/controllers/home/
|- /includes/controllers/home/index.cont.php
|- /includes/setting/
|- /includes/setting/core.inc.php
|- /.htaccess
|- /index.php

Приступим к .htaccess
# Указываем индексный файл.
DirectoryIndex index.php
Options -Indexes
# Указываем кодировку.
AddDefaultCharset UTF-8

RewriteEngine on
# modDir:  Узнаем папку к модели
# modFile: Узнаем файл модели.
# modVar:  Узнаем переменные.
RewriteRule ^([a-z]{3,10})($|/)($|[a-zA-Z0-9]{3,10})($|/)($|[a-z0-9;:_]{3,30})? index.php?modDir=$1&modFile=$3&modVar=$5 [L,QSA]
Т.е теперь у нас url будет выглядеть так: http://example.com/home/index/id:1

Переходим к index.php
<?php
// Включаем на время отладки вывод ошибок.
error_reporting(E_ALL);

// Узнаем корневую директорию.
$pathRoot = realpath(dirname(__FILE__)).'/';
// Для ОС Windows заменяем слеш, и записываем.
define('SITE_PATH_ROOT', str_replace('\\', '/', $pathRoot));

// Подключаем конфигурационный файл.
require_once('includes/settings/core.inc.php');
На этом мы и закончим пока что работу над index.php.

Теперь можно приступить к самой структуре MVC, и первое что мы будем делать, это класс для передачи значений в в контроллер.
Класс будет состоять из двух магических методов __set() и __get().

Создадим файл /includes/classes/MVC_Registry.class.php в котором создаем класс MVC_Registry
<?php

class MVC_Registry {

    // Массив с переменными.
    private $_vars = array();

    /**
    * __set
    *
    * Присваиваем переменным значения.
    *
    * @param  string $name    Имя переменной.
    * @param  mixed  $val     Значение переменной.
    * @return bool			
    **/	
    public function __set($name, $val) 
    {
	
        if (empty($this -> _vars[$name]) == false) {
            return false;
        }


        $this -> _vars[$name] = $val;
        return true;
    }


    /**
    * __get
    *
    * Возвращаем значение переменной.
    *
    * @param string $name    Имя переменной.
    * @return mixed			
    **/	
    public function __get($name) 
    {

        if (empty($this -> _vars[$name]) == true) {
            return null;
        }
		
        return $this -> _vars[$name];
    }
}

?>

Теперь этот класс надо подключить в index.php, и это мы сделаем при помощи магического метода __autoload().
Создадим файл /includes/setting/core.inc.php и пишем в нем следующее:
<?php

function __autoload($name) 
{
    // Добавляем прификс к названию класса.
    $fileName = $name.'.class.php';
    // Узнаем категорию в которой лежит класс.
    $fileDir  = SITE_PATH_ROOT.'/includes/classes/'.$fileName;

    // Проверяем существует ли такой класс, или нет.
    if (is_file($fileDir) == false) {
        return true;
    }

    // Подключаем файл с классом.
    include_once($fileDir);
}

// Теперь можно создать объект класса MVC_Registry, что бы не гадить в index.php.
$mvcRegistry = new MVC_Registry;
?>

Перейдем к самой главной части MVC, к маршрутизатору.
Маршрутизатор у нас отвечает за то, что нам должен выдать скрипт! если точнее, то он указывает в какой папке искать модель, какой файл подключать, и что выводить.
Класс будет содержать в себе 3 метода: __construct(), _getController(), getModul()
<?php

class MVC_Router {

    // Массив со значениями.
	private $_registry;

	// Папка с контроллерами.
	private $_path = '/includes/controllers/';

	// Массив со стандартным контроллером.
	private $_arrModul = array(
							  'dir' => 'home/',
							  'file' => 'index.cont.php'
							  ); 
	

	/**
	* __construct
	*
	* @param array $registry    Массив со значениями.
	*
	**/	
	public function __construct($registry) 
	{
		$this -> _registry = $registry;
	}
	

	 
	/**
	* _getController
	*
	* Узнаем путь к файлу модуля для дальнейшего его подключения.
	*
	**/	
	private function _getControllerClass() 
	{	
	
		// Проверяем указана ли папка с контроллером.
		if (!empty($_GET['modDir'])) {

			// Проверяем существует ли категория с указанным контроллером.
			if (is_dir($this -> _path . $_GET['modDir'] . '/') === true) {

				// Записываем указанную категорию.
				$this -> _arrModul['dir'] = $_GET['modDir'] . '/';
			
			}
			
			// Проверяем указан ли контроллер, если он не указан, то пытаемся указать как index.
			$_GET['modFile'] = empty($_GET['modFile']) ? 'index' : $_GET['modFile'];
				
			// Проверяем существует ли файл контроллера
			if (is_file($this -> _path . $this -> _arrModul['dir'] . '/' . $_GET['modFile'] . '.modul.php') === true) {
				
				// Записываем указанный контраллер.
				$this -> _arrModul['file'] = $_GET['modFile'] . '.modul.php';
			
			}
		}
		
		// Записываем путь к файлу контроллера.
		$this -> modulFile = SITE_PATH_ROOT . $this -> _path . $this -> _arrModul['dir'] . $this -> _arrModul['file'];

	}



	/**
	* getModul
	*
	* Подключаем класс модуля, и выводим результат.
	*
	**/
	 public function getController()
	 {

		// Узнаем путь к контроллеру.
		$this -> _getControllerClass();
		
		// Подключаем контроллер.
		include_once ($this -> modulFile);

		// Создать объект класса DisplayController
		$controller = new DisplayController($this -> _registry);
		// Выводим результат выполнения контроллера.
		$controller -> display();
	 }

}

?>

Теперь в файле index.php добавим еще пару строк? в которых мы запускаем маршрутизатор, и выводим контроллер.
<?php
// Создадим объект класса MVC_Router
$mvcRouter = new MVC_Router($mvcRegistry);
// Подключаем контроллер.
$mvcRouter -> getController();
?>

Все готово! Теперь можно создавать контроллер!
Думаю у вас возник вопрос "Неужели нам придется каждый раз создавать конструктор класса DisplayController?", конечно же нет!

Создаем файл /includes/classes/MVC_BaseController.class.php в котором мы создадим абстрактный класс MVC_BaseController который будет выполнять не только подгрузку значений реестра, но и разберать переменные полученные из $_GET.
<?php
abstract class MVC_BaseController {

	// Массив со значениями.
	protected static $registry = array();



	/**
	* __construct
	*
	* @param array $registry    Массив со значениями.
	*
	**/	
	public function __construct($registry) 
	{
		self :: $registry = $registry;

		// Проверяем указанны ли переменные.
		if (empty($_GET['modVar']) === false) {

			// Делим строку на части.
			$aExVar = explode(';', $_GET['modVar']);

			// Если в строке есть хоть одна переменная, то работаем дальше.
			if (empty($aExVar) === false) {

				$vars = array();

				foreach ($aExVar as $aKey) {

					// Делим строку на ключ и значение.
					$aExValue = explode(':', $aKey);
					
					// Проверяем есть ли в строке ключ и значение.
					if (empty($aExValue[0]) === false and empty($aExValue[1]) === false) {

						// Записываем переменную.
						$vars[$aExValue[0]] = $aExValue[1];

					}
				}
			}
			// Пишем полученные переменные в глобальный массив $_GET
			$_GET = array_map('trim', $vars);
		}
	}

	// Указываем метод displey() как абстрактный.
	abstract function display();
}
?>

Самое главное мы сделали, осталось дело за малым! Создаем файл контроллера /includes/controllers/home/index.cont.php
И в нем мы пишем следующее:
<?php
class DisplayController extends MVC_BaseController {

	public function display() 
	{ 

		echo 'Hello World';

	}

}
?>

Посмотрев на работу скрипта, результатом у нас будет: Hello World


Теперь можно глянуть применение:
/index.php
<?php
define('SITE_PATH_ROOT', str_replace('\\', '/', realpath(dirname(__FILE__))).'/');

include_once ('includes/settings/core.inc.php');

$mvcRegistry -> testClass = new TestClass();

$mvcRouter = new MVC_Router($mvcRegistry);
$mvcRouter -> getController();
?>
/includes/classes/TestClass.class.php
<?php
class TestClass {

	public function testMethod($str) 
	{
		return 'I love '.$str;
	}
}

?>
/includes/controllers/home/index.cont.php
<?php
class DisplayController extends MVC_BaseController {

	public function display() 
	{ 
		echo 'Hello World <br />' . self :: $registry -> testClass -> testMethod('Moscow');
	}

}
?>
Результат выполнения:
Hello World 
I love Moscow
------------------------------------
Подготовил статью: Nu3oN специально для http://7je.ru
Здравствуйте.
После продолжительной работой над идеей постоянных объектов, реализовал полностью функциональный класс, позволяющий управлять постоянными объектами. В отличии от других подобных технологий, мой класс поддерживает связи любой сложности с возможностью постоянного хранения ассоциаций. Класс реализован на основе общих принципов объектно-реляционной организации данных и практически не влияет на написание программы. С другой стороны данный класс позволяет полностью отказаться от написания запросов к БД за счет автоматизации этого процесса. На деле нужно просто работать с объектами, на основе этого класса, а стандартные методы выполнят все запросы сами. Класс позволяет сохранять объекты в базе данных, а так же ассоциативные связи между ними. Восстанавливать объекты и ассоциативные связи из базы данных. Устанавливать множественные и симметричные связи между объектами. Класс облегчает реализацию проектов, смоделированных на UML.
Архив: http://
upwap.ru/1475003
Пароль: a79pkr567.99em%
Авторство: Artur-Mamedbekov@yandex.ru (Артур Мамедбеков, г. Махачкала).
Платформа: PHP, MySQL
Облако тегов / Авторы