Наш чат в Telegram для обмена идеями, проектами, мыслями, людьми в сфере ИТ г.Ростова-на-Дону: @it_rostov

Объектное программирование

Объектно-ориентированное программирование (ООП) на PHP

Объект - это набор специальных переменных - свойств и специальных функций - методов.

То, что в процедурном программировании называлось переменной — в ООП называется свойство. То, что в процедурном программировании называлось функцией — в ООП называется методом класса.

Созданные на основе класса объекты называются экземплярами класса или просто объекты.

Обращение из метода к свойствам только через служебное слово $this: $this->name; (обратите внимание на отсутствие знака доллара перед name) Обращение внутри метода к другому методу тоже через $this: $this->foo(); Для доступа к свойствам и методам объекта служит оператор "->": $this->name; (обратите внимание на отсутствие знака доллара перед name)
Обращение внутри метода к другому методу тоже через $this: $this->foo();. Объект создается с помощью оператора new на основании шаблона, называемого классом. Класс определяется ключевым словом class.

Пример 1

<html>
<head>
    <title>Класс со свойством и методом</title>
</head>
<body>
<?php
class классN1
{
    public $имя = "Маша"; // - это свойство класса доступное снаружи класса
    private $Private_name;     // - это свойство доступно только методам класса
    protected $Protected_name;  // это свойство доступно методам собственного класса, а также методам наследуемых классов
    function Привет() // - это метод класса
    {
        echo "<h2>".$this->имя."! Привет!</h2>";
    }
    function Пока( $a )
    {
        $this->имя = $a;
        echo "<h2>".$this->имя."! Пока!</h2>";
    }
}
$obj = new классN1();
$obj->Привет();
$obj->имя = "Миша";
$obj->Привет();
$obj->Пока("Яша");
$obj->Привет();
?>
</body>
</html>

Модификаторы доступа в ООП:

  • public — позволяет иметь доступ к свойствам и методам из любого места (глобальная область)
  • protected — доступ к родительскому и наследуемому классу (область класса наследника)
  • private — доступ только из класса, в котором объявлен сам элемент (область самого класса)

Метод по умолчанию — public. У свойств значения модификатора по умолчанию нет.

Константы класса в ООП

const NAME = 2;

Таким образом можно создавать константы и вне класса. Это именно константы класса, они не принадлежат ни одному объекту, они общие на все объекты, поэтому использование внутри метода:

function printname(){
    echo self::NAME;
}

self — это сам класс!

Обращение вне класса (можно вызывать из глобальной области видимости без инициализации экземпляра класса):

echo OurClass::NAME;

this и self

Внутри класса использована специальная переменная this. Это указатель, с помощью которого объект может ссылаться на самого себя.

Для обращения к статическим методам используется self::

Методу Пока передан аргумент точно так же, как и обычной функции. При вызове этого метода объект меняет свое свойство имя.

РЕЗУЛЬТАТ ПРИМЕРА 1:

Маша! Привет!

Миша! Привет!

Яша! Пока!

Яша! Привет!

Конструктор — это метод, который автоматически вызывается при создании нового объекта: public function __construct(){}. При инициализации6 объекта через служебную конструкцию new, PHP ищет __construct и если он есть, то вызывается.

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

Пример 2

<html>
<head>
    <title>Класс с конструктором</title>
</head>
<body>
<?php

class классN2
{
    private $имя; // - это свойство класса НЕ доступное снаружи класса
    function __construct( $a="Кто-то там" )
    {
        $this->имя = $a;
    }
    function Привет()
    {
        echo "<h2>".$this->имя."! Привет!</h2>";
    }
}

$obj0 = new классN2();
$obj1 = new классN2("Миша");
$obj2 = new классN2("Маша");
$obj0->Привет();
$obj1->Привет();
$obj2->Привет();
?>
</body>
</html>
РЕЗУЛЬТАТ ПРИМЕРА 2:

Кто-то там! Привет!

Миша! Привет!

Маша! Привет!

Сложив все, изложенное выше, можно создать более осмысленный класс. Например, класс, который будет располагать данные в виде таблицы с поименнованными столбцами.

Пример 3

<html>
<head>
    <title>Класс Table</title>
</head>
<body>
<?php

class Table
{
    private $headers = [];
    private $data = [];
    function Table ( $headers )
    {
        $this->headers = $headers;
    }
    function addRow ( $row )
    {
        $tmp = [];
        foreach ( $this->headers as $header )
        {
            if ( ! isset( $row[$header] )) $row[$header] = "";
            $tmp[] = $row[$header];
        }
        array_push ( $this->data, $tmp );
    }
    function output ()
    {
        echo "<pre><B>";
        foreach ( $this->headers as $header ) echo "$header  ";
        echo "</B><BR>";
        foreach ( $this->data as $y )
        {
            foreach ( $y as $x ) echo "$x  ";
            echo "<br>";
        }
        echo "</pre>";
    }
}

$test = new Table (array("a","b","c"));
$test->addRow(array("a"=>1,"b"=>3,"c"=>2));
$test->addRow(array("b"=>1,"a"=>3));
$test->addRow(array("c"=>1,"b"=>3,"a"=>4));
$test->output();
?>
</body>
</html>

Свойства класса Table - массив имен столбцов таблицы и двумерный массив строк данных. Конструктор класса Table получает массив имен столбцов таблицы. Метод addRow добавляет в таблицу новую строку данных. Метод output выводит таблицу на экран.

РЕЗУЛЬТАТ ПРИМЕРА 3:
a  b  c  
1  3  2
3  1
4  3  1

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

Свойства и методы класса могут быть как открытыми (public), так и скрытыми (private). Скрытые свойства и методы недоступны извне класса, т.е. из сценария, в котором используется данный класс, или из другого класса.

Наследование

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

Чтобы создать новый класс, наследующий поведение существующего класса, надо использовать ключевое слово extends в его объявлении. Например:

class классN2 extends классN1
   {
   .......
    }

Здесь классN1 - родительский класс, классN2 - производный.

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

классN1::классN1();

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

Пример 4

<html>
<head>
    <title>Переопределение метода родительского класса</title>
</head>
<body>
<?php
class классN3
{
    public $имя = "Маша";
    function Привет()
    {
        echo "<H1>".$this->имя."! Привет!</H1>";
    }
}
class классN4 extends классN3
{
    function Привет()
    {
        echo "<h2>".$this->имя."! Какая встреча!</h2>";
    }
}
$obj = new классN4();
$obj->Привет();
?>
</body>
</html>

Метод Привет переопределен для производного класса. Свойство имя наследуется от родительского.

РЕЗУЛЬТАТ ПРИМЕРА 4:

Маша! Какая встреча!

Начиная с 4-й версии PHP, в объекте производного класса можно вызвать метод родительского класса, который был переопределен.

Пример 5

<html>
<head>
    <title>Вызов метода родительского класса</title>
</head>
<body>
<?php
class классN5
{
    public $имя = "Маша";
    function Привет()
    {
        echo "<h2>".$this->имя."! Привет!</h2>";
    }
    function Пока()
    {
        echo "<h2>".$this->имя.", пока!</h2>";
    }

}

/**
 * Class классN6
 */
class классN6 extends классN5
{
    /**
     *
     */
    function Привет()
    {
        echo "<H1>".$this->имя."! Какая встреча!</H1>";
        классN5::Привет();
    }
}
$obj = new классN6();
$obj->Привет();
$obj->Пока();
?>
</body>
</html>
РЕЗУЛЬТАТ ПРИМЕРА 5:

Маша! Какая встреча!

Маша! Привет!

Маша, пока!

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

В следующем примере создан класс HTMLTable, основанный на классе Table из примера 3. Новый класс формирует данные, сохраненные методом addRow родительского класса, и выводит их в HTML-таблицу. Свойства $cellpadding и $bgcolor дают возможность изменять соответствующие аргументы, при этом переменной $cellpadding присваивается значение по умолчанию, равное 2.

Пример 6

<html>
<head>
    <title>Классы Table и HTMLTable</title>
</head>
<body>
<?php

class Tables
{
    public $headers = [];
    public $data = [];
    function Tables( $headers )
    {
        $this->headers = $headers;
    }
    function addRow ( $row )
    {
        $tmp = [];
        foreach ( $this->headers as $header )
        {
            if ( ! isset( $row[$header] )) $row[$header] = "";
            $tmp[] = $row[$header];
        }
        array_push ( $this->data, $tmp );
    }
    function output ()
    {
        echo "<pre><b>";
        foreach ( $this->headers as $header ) echo "$header  ";
        echo "</b><br>";
        foreach ( $this->data as $y )
        {
            foreach ( $y as $x ) echo "$x  ";
            echo "<br>";
        }
        echo "</pre>";
    }
}

class HTMLTable extends Tables
{
    public $cellpadding = "2";
    public $bgcolor;
    function HTMLTable ( $headers, $bg="FFFFFF" )
    {
        Tables::Tables( $headers );
        $this->bgcolor = $bg;
    }
    function setCellpadding ( $padding )
    {
        $this->cellpadding = $padding.'px';
    }
    function output ()
    {
        echo "<table><tr>";
        foreach ( $this->headers as $header ){
            echo "<th style='background-color:".$this->bgcolor.";padding=".$this->cellpadding."px'>".$header."</th>";
        }
        echo "</tr>";
        foreach ( $this->data as $y )
        {
            echo "<tr>";
            foreach ( $y as $x ){
                echo "<td style='background-color:".$this->bgcolor.";padding=".$this->cellpadding."px'>$x</td>";
            }
            echo "</tr>";
        }
        echo "</table>";
    }
}

$test = new HTMLTable ( array("a","b","c"), "#00FFFF" );
$test->setCellpadding ( 7 );
$test->addRow(array("a"=>1,"b"=>3,"c"=>2));
$test->addRow(array("b"=>1,"a"=>3));
$test->addRow(array("c"=>1,"b"=>3,"a"=>4));
$test->output();
?>
</body>
</html>

Обратите внимание на то, что значение свойства сellpadding меняется с помощью отдельного метода setCellpadding. Конечно, значения свойств можно менять непосредственно, вне объекта:

$test->сellpadding = 7 ;

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

РЕЗУЛЬТАТ ПРИМЕРА 6:
abc
132
31
431

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

Удаление объектов

Удалить ранее созданный объект можно следующим образом:

unset($objName);

Ниже приведен пример, в котором объект класса Саг создается, а затем удаляется.

$myCar = new Car;
unset($myCar);

После вызова функции unset() объект больше не существует. В РНР имеется специальный метод __destruct(), который автоматически вызывается при удалении объекта. Ниже приведен класс, содержащий этот метод.

class Bridge
{
    function __destruct()
    {
        echo "Мост разрушен";
    }
}
$bigBridge = new Bridge;
unset($bigBridge);

При создании объекта класса Bridge, а затем его удалении отобразится следующее сообщение:

Мост разрушен

Оно отображается вследствие вызова метода __destruct() при вызове функции unset(). При удалении объекта может потребоваться акрыть некоторые файлы или записать информацию в базу данных.

Копирование (клонирование) объекта

Клонирование объекта:

$a = clone $b;

Конструктор не вызывается при клонировании, вызывается магический метод __clone(){}. Он НЕ принимает аргументов и к нему нельзя обратиться как к методу.

Преобразование объекта в строку

Для конвертации объекта в строку, и обратно, используются следующие функции:
serialize() - принимает объект и возвращает строковое представление его класса и свойств;
unserialize() - принимает строку, созданную при помощи serialize(), и возвращает объект.

serialize() и unserialize() работают со всеми типами данных, но они не работают с ресурсами.


Специальные методы для обслуживания функций serialize() и unserialize():
__sleep() - вызывается строго перед тем, как объект сериализуется с помощью функции serialize(). Функция __sleep() должна будет вернуть список полей класса, которые функция serialize() включит в возвращаемую строку. Вы можете использовать это для того, чтобы исключить ненужные поля из строкового представления объекта. Например:

public function __sleep() {   // почистить
    return array_keys( get_object_vars( $this ) );
}
__wakeup() - вызывается сразу после того, как объект десериализуется с помощью unserialize().

Абстрактный класс

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

abstract class Person {

    private $firstName = "";
    private $lastName = "";

    public function setName( $firstName, $lastName ) {
    $this->firstName = $firstName;
    $this->lastName = $lastName;
    }

    public function getName() {
    return "$this->firstName $this->lastName";
    }

    abstract public function showWelcomeMessage();
    /*  абстрактный метод showWelcomeMessage().
    Так как он абстрактный, в нем нет ни строчки кода, это просто его объявление.
    Любой дочерний класс обязан добавить и описать метод showWelcomeMessage() */
}

Интерфейс

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

  • Ни один метод не может быть описан в интерфейсе. Они все абстрактны. В абстрактном классе могут быть и не абстрактные методы.
  • Интерфейс не может содержать полей - только методы.
  • Класс имплементирует интерфейс, и класс наследует или расширяет другой класс.
  • Класс может имплементировать несколько интерфейсов одновременно. Этот же класс может наследовать другой класс. Но у дочернего класса может быть только один супер-класс (абстрактный или нет).
interface MyInterface {
    public function aMethod();
    public function anotherMethod();
}

class MyClass implements MyInterface {

    public function aMethod() {
        // (имплементация метода)
    }

    public function anotherMethod() {
        // (имплементация метода)
    }
}

Методы-перехватчики (магические методы)

  • __get($property) - вызывается при обращении к неопределенному свойству
  • __set($property,$value) - вызывается, когда неопределенному свойству присваивается значение
  • __unset($property) - вызывается, когда функция unset() вызывается для неопределенного свойства
  • __isset($property) - вызывается, когда функция isset() вызывается для неопределенного свойства
  • __call($method,$arg array) - вызывается при обращении к неопределенному методу
  • __callStatic($method,$arg array) - вызывается при обращении к неопределенному статическому методу
  • __toString() - Вызывается, если есть попытка вывести объект, как строку.
  • __debugInfo() - В PHP 5.6 был добавлен новый магический метод, который позволяет менять свойства и значения объекта, когда он печатается с помощью функции var_dump(класс).
  • __invoke() - для вызова объекта как функции. Пример

Пример использования необъявленных свойств класса

Где и зачем могут быть использованны методы-перехватчики?

Например есть у вас таблица в базе данных, называется user и есть в ней некие поля, например id, name, email, phone, password, avatar И Вы создали класс на для работы с юзерами, так его и назвали - User

Какие свойства будут у данного класса? Если вы сделаете такие же как в БД - id, name, email и так далее, то получается что при каждом изменении базы данных - вам нужно менять код в классе User, как то не очень удобно. Добавили вы например поле site - значит нужно его добавлять и в класс User, ну и так далее.
Используя же методы __get() и __set() Вы можете это всё автоматизировать. У вас в классе User вообще не будет ни одного свойства из БД, у нас есть допустим только одно $data - мы туда взяли, да и загрузили всё что есть в базе данных на данного пользователя. А потом, когда программист что то запрашивает, например $user->email мы просто в __get() методе можете посмотреть - если мы такую информацию загрузили из БД, и она лежит в $data['email'] - то вот мы её вам и возвращаем. А в __set() наоборот. Есть такое поле в БД? Значит присвоим ему новое значение.

/**
 * Class User
 * @property-read integer id текущего пользователя
 * @property-write String site возвращает ссылку на сайт пользователя
 */
class User
{
    private $data;
    private $f_write=false;
    public function __set($name, $value) {
        $this->data[$name] = $value;
        $this->f_write=true; // признак, что нужно сохранить данные
    }

    public function __get($name) {
        if(empty($data)){
            // читаем запись из БД в data
        }
        return $this->data[$name];
    }
    function __destruct()
    {
        if(!empty($data)&&$this->f_write){
            // сохраняем изменения в БД
        }
    }
}

$user=new User();
$user->site='http://kdg.htmlweb.ru/';        //присваеваем переменной
echo $user->site;        //выводим значение переменной
// записываем в БД. Можно это явно не делать, т.к. при окончании работы скрипта это поизойдет автоматически
unset($user);

Пример использование необъявленного свойства класса как элемент массива

Обратите внимание на то, что из __get возвращается ссылка:

class Foo {
    private $data = [];
    public function __set($name, $value) {
        $this->data[$name] = $value;
    }

    public function & __get($name) {
        return $this->data[$name];
    }
}

$foo = new Foo();
$foo->bar[2] = 'lol';
var_dump($foo->bar);

Использоватние перехватчиков обращения к необъявленным методам класса

class OurClass
{
    public function __call($name,array $params)
    {
        echo 'Вы хотели вызвать $Object->'.$name.', но его не существует,
                и сейчас выполняется '.__METHOD__.'()';
        return;
    }

    public static function __callStatic($name,array $params)
    {
        echo 'Вы хотели вызвать '.__CLASS__.'::'.$name.', но его не существует,
                и сейчас выполняется '.__METHOD__.'()';
        return;
    }
}

$Object=new OurClass;
$Object->DynamicMethod();
OurClass::StaticMethod();

Пример обхода закрытых метов класса:

class _byCallStatic{
  // Пример обхода "закрытых" методов класса,
  // при использовании метода "__callStatic()" для вызова статического метода.
  public static function __callStatic($_name, $_param) {
    return call_user_func_array('static::'. $_name, $_param);
  }
  private static function _newCall(){ echo 'Method: '. __METHOD__; }
}
echo _byCallStatic::_newCall(114, 'Integer', 157); # Результат: Method: _byCallStatic::_newCall

Как вызвать через статический метод любой динамический:

/**
 * Class o
 * @method static void __f(int $a1 = 1)
 */
class o
{
    public static function __callStatic($method, $args)
    {
        $class = get_called_class();
        $obj = new $class($args[0]);
        $method = substr($method, 2);
        $pass = array_slice($args,1);
        $reflection = new ReflectionMethod($obj, $method);
        return $reflection->invokeArgs($obj, $pass);
    }

    public function f($a1 = 1) {
        var_dump('oo', func_get_args());
    }
}
class a extends o
{
    public function f($a1 = 1, $a2 = 2) { var_dump('aa', $a1 ); }
}
class b extends o
{
    public function f($b1 = 1) { var_dump('bb', $b1); }
}
a::__f(1,2,3);
b::__f(4,5,6);

Полезное описание работы с ReflectionClass, когда вы можете проанализировать свойства и методы класса, проверить параметры по шаблонам и т.д.: http://habrahabr.ru/post/139649/

Как использовать объект как функцию?

class Dog
{
    private $name;
    public function __construct($dogName = 'Тузик') {
        $this->name = $dogName;
    }
    public static function __invoke() {
        $args = func_get_args();
        echo 'Собака получила: ' . implode(' и ', $args);
    }
}

$dog = new Dog('Мухтар');
$dog('кость', 'поводок');
Собака получила: кость и поводок

Как обращаться к объекту как к массиву?

Для этого необходимо создать такой объект который реализует интерфейс ArrayAccess из SPL. Следующий пример реализует объект доступ к данным которого можно получать как в стиле обращения к массиву, так и через получение свойств:

class MyArray implements ArrayAccess
{
    protected $arr = array();
    public function offsetSet($key, $value) {
        $this->arr[$key] = $value;
    }
    public function offsetUnset($key) {
        unset($this->arr[$key]);
    }
    public function offsetGet($key) {
        return $this->arr[$key];
    }
    public function offsetExists($key) {
        return isset($this->arr[$key]);
    }
    public function __get($key)
    {
        return $this->offsetGet($key);
    }
    public function __set($key, $val)
    {
        $this->offsetSet($key, $val);
    }
}
$a = new MyArray();
$a['whoam'] = 'Я значение массива, или объекта? <br/ >';
echo $a['whoam'];
echo $a->whoam;
Я значение массива, или объекта? Я значение массива, или объекта?

Автозагрузка классов

Файлы автозагружаемых классов обычно располагаются в общем месте, например в /include/class/. Имя файла формируется в формате ИМЯ_КЛАССА.php. Данный код необходимо подключить во все PHP-скрипты:
spl_autoload_register(function ($class_name) {
    //echo "Autoload ".$class_name;
    $file = $_SERVER['DOCUMENT_ROOT'] . "/include/class/" . strtolower($class_name) . '.php';
    if (file_exists($file) == false) {
        if($GLOBALS['DEBUG']) echo "Нет файла ".$file;
        return false;
    }
    include_once($file);
    return true;
});

Для автоподгрузки классов можно также использовать определение функции __autoload();

Обработка исключений в ООП

Для обработки некритических ошибок используются исключения(Exception).

try {
    $a = 1;
    $b = 0;
    if($b == 0)
        throw new Exception ("деление на ноль!");
    $c = $a/$b;
} catch (Exception $e) {
    echo $e->getMessage();
    echo $e->getLine();
}

Exception — встроенный класс. Если попали в throw, то код ниже не выполняется и осуществляется переход к блоку catch.

Блок try-catch используется как в процедурном, так и в ООП программировании. Он используется для отлова ошибок — большой блок try с множеством throw и все отлавливаются в одном месте — блоке catch.

Exception можно наследовать, желательно при этом перезагрузить конструктор:

class MyException extends Exception {
    function __construct($msg){
        parent::__construct($msg);
    }
}

Блоков catch может быть несколько — для каждого класса наследника Exception.

Ещё примеры работы с DOM на PHP

Читать дальше: Работа с формами на PHP


.