LOGO
    • d
    • n

PHP OOP | PHP OOP Tutorial

zapaDEV Cheat Sheep

get_class($obj)         // Возвращает имя класса, к которому принадлежит объект
get_object_vars($obj)   // Возвращает свойства указанного объекта
get_class_methods($obj) // Возвращает массив имён методов класса
get_class_vars()        // Возвращает объявленные по умолчанию свойства класса
is_a()                  // Проверяет, принадлежит ли объект к данному классу или является ли этот класс одним из его родителей

Термины

  • Класс
  • Абстрактный класс
  • Интерфейс
  • Суперкласс
  • Трейт
  • Пространство имен
  • Объект
  • Свойства класса
  • Методы классы
  • Модификаторы доступа / Уровни доступа
  • Абстрактный метод
  • __construct (Конструктор)
  • __destruct
  • Константы класса. Статические свойства и методы
  • Инкапсуляция
  • Композиция
  • Наследование
  • Полиморфизм - семейство различных механизмов, позволяющих использовать один и тот же участок программы с различными типами в различных контекстах. 2. это возможность использования одного и того же кода с разными типами аргументов и переменных. Практическая польза в полиморфизме для разработчика — частичное сокращение бойлерплейт кода.
  • Позднее статическое связывание
  • Магические методы
  • Dependency injection (внедрение зависимости)
  • Шаблоны проектирования (патерны)
  • SOLID
  • Пространство имен (NameSpace)

Туториал

Класс и объект

Интерфейс

Абстрактный класс vs Интерфейс

Абстрактные классы в php реализуются добавлением ключевого слово abstract. Абстрактные классы могут иметь свойства и методы. Но в отличии от обычных классов, методы в абстрактных классах не имеют тела. Ключевое значение в таких методах — необходимость их реализации в дочерних классах.

Интерфейс — это тот же абстрактный класс, но у него нет свойств. Интерфейс объявляется при помощи ключевого слова interface.

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

Различие в применении абстрактного класса и интерфейса — очень тонкий вопрос. Абстрактный класс скорее служит для объединения семейства классов.

Например, есть абстрактный класс Автомобиль, и от него наследуются классы Ford, Toyota, у которых есть общие методы (объявленные в абстрактном классе). Но если появляется класс Велосипед, то в нем нет смысла реализовывать, например, метод ПоменятьМасло. Для таких классов лучше описывать интерфейсы.

/**
 *  Абстрактный класс обязует наследуемые от него классы
**  реализовывать все абстрактные методы
 */
abstract class Transport
{

    public $driverName = "Водила"; // свойства не могут быть абстрактными

    abstract function driverSide();
    abstract protected function changeOil(); // метод не может быть private

    // в абстрактном классе могут быть и обычные методы
    public function sayHello() {
        echo "Привет вам из метода";
    }


}

class Car extends Transport {
    public $wheels = 4;
    public function countWheels() {
        echo "У машины " . $this->wheels . " колеса";
    }
    public function driverSide() {
        echo $this->driverName . " сидит за рулем слева";
    }
    public function changeOil() {
        echo "В машине надо обязательно менять масло";
    }
}

class Bike extends Transport {
    public $wheels = 2;
    public function countWheels() {
        echo "У велосипеда " . $this->wheels . " колеса";
    }
    public function driverSide() {
        echo $this->driverName . " просто сидит за рулем";
    }
    public function changeOil() {
        echo "В велосипеде надо обязательно менять масло...Да ладно?";
    }
}

$car = new Car();
$bike = new Bike();

/*Посмотрим на объекты*/
echo "<pre>";
    print_r($car);
echo "</pre>";

echo "<pre>";
    print_r($bike);
echo "</pre>";

$car->countWheels();
echo "<br>";
$bike->countWheels();
echo "<br>";
$car->driverSide();
echo "<br>";
$bike->driverSide();
echo "<br>";
$car->changeOil();
echo "<br>";
$bike->changeOil();
echo "<br>";
$car->sayHello();
echo "<br>";
$bike->sayHello();
interface Oilable {
    public function changeOil();
}

interface Doorable {
    public function countDoors();
}

class Car implements Oilable, Doorable {

    public $wheels = 4;

    public function countWheels() {
        echo "У машины " . $this->wheels . " колеса";
    }

    public function changeOil() {
        echo "Пора менять масло";
    }

    public function countDoors() {
        echo "В машине 2 или 4 двери";
    }

}

class Bike {

    public $wheels = 2;

    public function countWheels() {
        echo "У велосипеда " . $this->wheels . " колеса";
    }


}

$car = new Car();
$bike = new Bike();

/*Посмотрим на объекты*/
echo "<pre>";
    print_r($car);
echo "</pre>";

echo "<pre>";
    print_r($bike);
echo "</pre>";

$car->countWheels();
echo "<br>";
$bike->countWheels();
echo "<br>";
$car->changeOil();
echo "<br>";
$car->countDoors();

Трейты

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

Трейты похожи на абстрактные классы. Они реализуют какую-то общую функциональность и с ними нельзя работать напрямую. Единственный способ использовать их – включение в другие классы.

// Magic.php

trait Magic
{
    // Доступно только внутри трейта
    private $properties;

    public function __get($key)
    {
        return $this->properties[$key] ?? null;
    }

    public function __set($key, $value)
    {
        $this->properties[$key] = $value;
    }
}

Трейт по большей части выглядит как (абстрактный) класс и устроен как класс. Он подчиняется тем же правилам именования и расположения в иерархии пространств имён (а следовательно и файловой структуре) что и классы. Отличия начинаются в момент использования:

// Config.php

class Config
{
    // Включение трейта в класс
    use Magic;
}

$config = new Config();
$config->key = 'value';
echo $config->key;

Трейт включается в другой класс с помощью инструкции use. С этого момента в классе становится доступна вся функциональность, определённая в трейте. По поведению трейты похожи на наследование, например, приватные части трейта доступны только внутри методов самого трейта. Но при этом трейт не встраивается в цепочку наследования, это легко проверить:

$config instanceof Magic; // false

Из этого есть пара важных следствий:

  • Внутри класса к методам трейта нельзя обратиться через parent, только через $this. При условии, что эти методы не приватные.
  • Трейт не может реализовывать интерфейс. Это могут делать только классы.

Зачем?

Трейты в отличие от наследования, не фиксируют структуру классов. Любой класс может включать в себя любое количество трейтов:

<?php

class MySuperClass
{
    use FirstTrait;
    // При включении возможны конфликты имён. Подробнее про их разрешение:
    // https://www.php.net/manual/ru/language.oop5.traits.php#language.oop5.traits.conflict
    use SecondTrait;
}

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

Трейты позволяют реализовать многие интерфейсы PHP универсальным образом, например, ArrayAccess или Iterator

Пример: Итератор

Рассмотрим готовый пример трейта-итератора. Он реализует общую логику обхода коллекций внутри объектов. С его помощью можно сделать объекты, которые можно использовать в forEach:

// Реализует итератор
$course = new Course();
foreach ($course as $lesson) {
    echo $lesson . "\n";
}

Трейт:

<?php 
trait IteratorTrait
{
    protected $offset = 0;

    public function current()
    {
        return $this->getCollection()[$this->offset] ?? null;
    }

    public function next()
    {
        $this->offset++;
    }

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

    public function valid()
    {
        return \array_key_exists($this->offset, $this->getCollection());
    }

    public function rewind()
    {
        $this->offset = 0;
    }

    abstract public function getCollection();
}

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

Это очень важная концепция. Трейту нужны данные от класса, в который его включают. И трейт строит связь с этими классами через интерфейсный метод, а не через обращение к свойству с конкретным именем. А вот класс от трейта ничего не требует. Благодаря тому, что связь строится в одну сторону (трейт зависит от метода класса, но класс не зависит от методов и свойств трейта), код остаётся модульным. Если бы и трейт требовал что-то от класса и класс от трейта, то почти наверняка в коде проблемы с архитектурой.

Полиморфизм

NameSpace