Интерфейсы в PHP: контракты для классов

Интерфейсы в PHP

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

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

Прямоугольник.

Код доступен только после покупки курса ООП в PHP7.

Квадрат.

Код доступен только после покупки курса ООП в PHP7.

Круг.

Код доступен только после покупки курса ООП в PHP7.

Константы класса

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

Код доступен только после покупки курса ООП в PHP7.

Константы принято задавать в самом начале класса и называть их CAPS-ом с подчеркушками. Вот примеры того, как могут называться константы: DB_NAME, COUNT_OF_OBJECTS.

Для того, чтобы обратиться к константе, нужно использовать конструкцию self::ИМЯ_КОНСТАНТЫ, или ИмяКласса::ИМЯ_КОНСТАНТЫ. Ключевое слово self – это обращение к текущему классу (как $this – обращение к текущему объекту, не путайте эти понятия). Константы принадлежат классу, а не его объектам.

Давайте вынесем число Пи в константу.

Код доступен только после покупки курса ООП в PHP7.

Теперь мы можем использовать её и в других методах. Или даже в других классах, обратившись к ней через Circle::PI.

Интерфейсы

Окей, разобрались с константами и имеем в итоге 3 объекта, описывающих геометрические фигуры и реализацию для вычисления их площадей. Если присмотреться, то мы видим, что во всех классах определён метод calculateSquare(), возвращающий float. Можно сказать, что у них есть что-то общее.

Допустим, мы хотели бы, чтобы у нас были фигуры, которые умеют считать свою площадь. То есть, говоря чуть более абстрактно, какие-то наши классы обязаны реализовать какой-то внешний интерфейс, а именно – иметь метод calculateSquare(), который всегда возвращает float.
Для этой задачи в PHP есть интерфейсы. Это такие «контракты», которые класс должен соблюдать, если он на это «подписался». А говоря языком программистов, классы могут реализовывать интерфейсы.

Интерфейс – это описание public методов, которые представляют собой только название метода, описание их аргументов и возвращаемый тип. Тело метода в интерфейсе не описывается.
Давайте создадим интерфейс для нашего случая.

Код доступен только после покупки курса ООП в PHP7.

Чтобы обязать класс реализовать этот интерфейс нужно использовать слово implements после имени класса.

Код доступен только после покупки курса ООП в PHP7.

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

Код доступен только после покупки курса ООП в PHP7.

IDE PhpStorm автоматически понимает, что наш класс реализует интерфейс и рисует слева от методов специальные иконки. Если по ним кликнуть, то нас перекинет на интерфейс.
Подсветка интерфейсов в PhpStorm

Ну и в интерфейсе если кликнуть на такую иконку, то нам откроется список мест, где этот интерфейс реализован.
Реализации интерфейса

Если же мы напишем, что класс реализует какой-то интерфейс, но не реализуем его, то получим ошибку. Об этом нам даже подскажет IDE. Давайте удалим метод calculateSquare() из класса Circle. IDE любезно подчеркнёт красным строку, в которой мы говорим, что класс реализует интерфейс.
Напоминание о необходимости реализации интерфейса

Если же мы попробуем запустить этот код, то и вовсе словим фатальную ошибку.
Ошибка при нереализованном интерфейсе

Так что давайте этот метод вернём обратно =)

Что ещё стоит сказать об интерфейсах – один интерфейс может содержать требования по реализации нескольких методов. Они просто перечисляются один за другим, вот так:
interface CalculateSquare

Код доступен только после покупки курса ООП в PHP7.

Но мы пока ограничимся одним методом calculateSquare().

Окей, так для чего это всё?

В программировании зачастую требуется проверить, что перед нами сейчас какой-то конкретный тип объектов, то есть что перед нами экземпляр какого-то класса, либо что этот объект реализует какой-то интерфейс. Для этого используется конструкция instanceof. С её помощью можно понять, является ли объект экземпляром какого-то класса, или реализует интерфейс. Эта конструкция возвращает true или false.

Код доступен только после покупки курса ООП в PHP7.

Результат:

Код доступен только после покупки курса ООП в PHP7.

Всё верно, объект $circle1 является экземпляром класса Circle. Давайте теперь проверим, является ли он экземпляром класса Rectangle.

Код доступен только после покупки курса ООП в PHP7.

Результат:

Код доступен только после покупки курса ООП в PHP7.

И снова всё верно, он не является экземпляром класса Rectangle.

А теперь давайте проверим, является ли он объектом, класс которого реализует интерфейс CalculateSquare.

Код доступен только после покупки курса ООП в PHP7.

И мы получим:

Код доступен только после покупки курса ООП в PHP7.

Вуаля! Теперь мы перед тем как попросить какой-либо объект посчитать свою площадь, можем проверить, есть ли у него такой метод, то есть, реализует ли он соответствующий интерфейс.
Давайте добавим во все наши классы информацию о том, что они реализуют интерфейс CalculateSquare.

Код доступен только после покупки курса ООП в PHP7.

Давайте теперь насоздаём объектов этих классов и положим их в массив:

Код доступен только после покупки курса ООП в PHP7.

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

Код доступен только после покупки курса ООП в PHP7.

Результат:

Код доступен только после покупки курса ООП в PHP7.

Давайте теперь уберём из класса Rectangle упоминание о том, что он реализует этот интерфейс.
class Rectangle

Код доступен только после покупки курса ООП в PHP7.

И снова попробуем запустить код.

Код доступен только после покупки курса ООП в PHP7.

Как видим, проверка успешно отработала и объект класса Rectangle был пропущен.

Полный код, полученный в ходе урока:

Код доступен только после покупки курса ООП в PHP7.

На этом с интерфейсами пока всё. В домашке будет ещё одна интересная функция, которая позволит вам узнать об объектах ещё кое-что.

Читайте также
Комментарии


SlipKnot
SlipKnot

Фух, ну и пот. Вот вариант домашки:

<?php

interface CalculateSquare
{
    public function calculateSquare (): float;
}

class Circle implements CalculateSquare
{
    const PI = 3.1416;
    private $r;
    public function __construct(float $r)
    {
        $this->r = $r;
    }
    public function calculateSquare(): float
    {
        return self::PI * ($this->r ** 2);
    }
}
class Restangle
{
    private $x;
    private $y;
    public function __construct (float $x, float $y)
    {
        $this->x = $x;
        $this->y = $y;
    }
    public function calculateSquare(): float
    {
        return $this->x * $this->y;
    }
}
class Square implements CalculateSquare
{
    private $x;
    public function __construct(float $x)
    {
        $this->x = $x;
    }
    public function calculateSquare():float
    {
        return $this->x ** 2;
    }
}

$objects = [
    new Square(5),
    new Restangle (2,4),
    new Circle(5)
];

foreach ($objects as $object) {
    if ($object instanceof CalculateSquare) {
        echo 'Объект класса ' . get_class($object) . ' реализует интерфейс CalculateSquare. Полщадь - ' . $object->calculateSquare();
        echo '<br>';
    } else {
        echo 'Объект класса ' . get_class($object) . ' не реализует интерфейс CalculateSquare';
        echo '<br>';
    }
}
ivashkevich
ivashkevich

Нормально, только

public function getClass()

не нужно нигде - оно ведь нигде не используется. К тому же вызов get_class() без аргумента приведет к ошибке. Поправьте, пожалуйста :)

SlipKnot
SlipKnot

Пересмотрел код, участки вида:

public function getClass()
    {
        return get_class ();
    }

оказались бессмыслены, их нужно просто удалить. Отредактировал, проверьте пожалуйста. Спасибо за подсказку)

ivashkevich
ivashkevich

Теперь отлично!
Не торопитесь и всё будет супер-пупер ;)

Galay
Galay
foreach ($object as $object) 
{
    if($object instanceof CalculateSquare)
    {
        $x = get_Class($object);
        echo "Объект принадлежи классу $x и реализует интерфейс CalculateSquare. Площадь: " . $object->calculateSquare();
        echo '<br>';
    } 
    else
    {
    $x = get_Class($object);
    echo "Объект класса $x не реализует интерфейс CalculateSquare";
    }

Как-то так.

ivashkevich
ivashkevich

Разве работает?

Galay
Galay
interface CalculateSquare
    {
        public function calculateSquare(): float;
    }
class Circle implements CalculateSquare
    {
        const PI = 3.14;
        private $r;
        public function __construct(float $r)
        {
            $this->r = $r;
        }
        public function calculateSquare(): float{

            return self::PI * ($this->r ** 2);
        }

    }
class Rectangle implements CalculateSquare
    {
        private $x;
        private $y;
        public function __construct(float $x, float $y)
        {
            $this->x = $x;
            $this->y = $y;
        }
        public function calculateSquare(): float
        {
            return $this->x * $this->y;
        }

    }
class Scuare implements CalculateSquare
    {
        private $x;
        public function __construct(float $x)
        {
            $this->x = $x;
                    }
        public function calculateSquare(): float
        {
            return $this->x ** 2;
        }

    }
class Cat 
    {
        private $name;
        private $color;
        public function __construct(string $name, string $color){
            $this->name = $name;
            $this->color = $color;
        }

        public function sayHello() {
            echo "Привет, меня зовут {$this->name}, мой цвет - $this->color.";
        }
        public function getColor(): string {
            return $this->color;
        }
        public function setColor(string $color) {
             $this->color = $color;
        }
    }
$object = 
    [
    new Scuare(5),
    new Rectangle(5, 3),
    new Circle(3),
    new Cat('Барсик','Белый')
    ];

foreach ($object as $object) 
{
    if($object instanceof CalculateSquare)
    {
        $x = get_Class($object);
        echo "Объект принадлежи классу $x и реализует интерфейс CalculateSquare. Площадь: " . $object->calculateSquare();
        echo '<br>';
    } 
    else
    {
    $x = get_Class($object);
    echo "Объект класса $x не реализует интерфейс CalculateSquare";
    }
}

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

ivashkevich
ivashkevich

Да нет, это я понял. Странно что работает функция get_Class - это видимо какой-то баг в PHP. Нужно использовать get_class - все в нижнем регистре.

computerix
computerix
<?php

interface CalculateSquare
{
    public function calculateSquare(): float;
}

class Circle implements CalculateSquare
{
    const PI = 3.1416;

    private $r;

    public function __construct(float $r)
    {
        $this->r = $r;
    }

    public function calculateSquare(): float
    {
        return self::PI * ($this->r ** 2);
    }
}

class Rectangle implements CalculateSquare
{
    private $x;
    private $y;

    public function __construct(float $x, float $y)
    {
        $this->x = $x;
        $this->y = $y;
    }

    public function calculateSquare(): float
    {
        return $this->x * $this->y;
    }
}

class Square implements CalculateSquare
{
    private $x;

    public function __construct(float $x)
    {
        $this->x = $x;
    }

    public function calculateSquare(): float
    {
        return $this->x ** 2;
    }
}

$objects = [
    new Square(5),
    new Rectangle(2, 4),
    new Circle(5)
];

foreach ($objects as $object) {
    if ($object instanceof CalculateSquare) {
        echo 'Объект реализует интерфейс CalculateSquare. Относится к классу' .get_class($object) . 'Площадь: ' . $object->calculateSquare();
        echo '<br>';
    }
    else {'Объект класса' .get_class($object) . 'не реализует интерфейс CalculateSquare.'};
}
?>
DmitryGavrilov
DmitryGavrilov
<?php

interface calculateSquare
{

    public function calculateSquare(): float;
}

class Rectangle
{

    private $x;
    private $y;

    public function __construct(float $x, float $y)
    {
        $this->x = $x;
        $this->y = $y;
    }

    public function calculateSquare(): float
    {
        return $this->x * $this->y;
    }
}

class Square implements calculateSquare
{

    private $x;

    public function __construct(float $x)
    {
        $this->x = $x;
    }

    public function calculateSquare(): float
    {
        return $this->x ** 2;
    }

}

class Circle implements calculateSquare
{

    const PI = 3.1416;

    private $r;

    public function __construct(float $r)
    {
        $this->r = $r;
    }

    public function calculateSquare(): float
    {

        return self::PI * $this->r ^ 2;

    }

}

$objects = [
    new Square(5),
    new Rectangle(2, 4),
    new Circle(5)
];

foreach ($objects as $object) {
    if ($object instanceof calculateSquare) {
        echo 'Объект реализует интерфейс calculateSquare. Площадь: ' . $object->calculateSquare();
        echo '<br>';
        echo 'Это объект класса: ' . get_class($object);
        echo '<br>';
    } else {
        echo 'Объект класса ' . get_class($object) . ' ' . 'не реализует интерфейс calculateSquare';
    }

}