Чат PHP-разработчиков
Логические задачи с собеседований
Интерфейсы в PHP: контракты для классов

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

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

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

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

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

Квадрат.

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

Круг.

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

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

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

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

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

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

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

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

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

Интерфейсы

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Результат:

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

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

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

Результат:

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

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

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

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

И мы получим:

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

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

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

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

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

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

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

Результат:

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

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

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

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

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

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

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

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

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

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


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';
    }

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

Отлично =)

SBTesla
SBTesla
<?php

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

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;
     }
}

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 'Обьект класса '. get_class($object).' реализует интерфейс CalculateSquare. Площадь:' . $object->calculateSquare();
          echo '<br>';
     }  else {
          echo 'Обьект класса' . get_class($object) . ' не реализует интерфейс CalculateSquare';
          echo '<br>';
     }
}
ArtemijeKA
ArtemijeKA
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>';
    }
}
EWA
EWA

Вроде так.

<?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
{
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. Площадь: ' . $object->calculateSquare() . ' и является объектом класса ' . get_class($object);
echo '<br>';
}else{
echo 'Объект класса ' . get_class($object) . ' не реализует интерфейс CalculateSquar.';
echo '<br>';
}
}
Protos
Protos

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

foreach ($objects as $object){
    if ($object instanceof CalcSquare){
        echo 'Объект реализует интерфейс CalcSquare. И принадлежит к классу ' . get_class($object) . PHP_EOL ;

    }
    else {
        echo 'Объект НЕ РЕАЛИЗУЕТ интерфейс CalcSquare. И принадлежит к классу ' . get_class($object) . PHP_EOL ;
    }
}
ivashkevich
ivashkevich

Хорошо, только не стоило убирать расчет площади.

AntonM99
AntonM99
$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 {
        echo 'Объект класса ' . get_class($object) . ' не реализует интерфейс CalculateSquare';
        echo '<br>';
    }
}
zebra
zebra

Домашка :))

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;
    }
}

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); //self - обращение к текущему классу
    }
}

//$circle1 = new Circle(2.5);
// var_dump($circle1 instanceof CalculateSquare); //instanceof - проверяет является ли экзэпляром какого-либо класса, либо реализует какого-либо интерфейс
//get_class  Возвращает имя класса к которому принадлежит объект

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

//var_dump($objects);
//echo (get_class($objects[0]));
//echo '<br>';

foreach ($objects as $object) {
    if ($object instanceof CalculateSquare) {
        echo 'Объект реализует интерфейс CalculateSquare. Площадь: ' . $object->calculateSquare() . ' и это объект класса ' . get_class($object);
        echo '<br>';
    }
    else {
        echo 'Объект класса ' . get_class($object) . ' не реализует интерфейс CalculateSquare. ';
        echo '<br>';
    }
}
Todd
Todd
$objects = [
    new Rectangle(2, 4),
    new Square(5),
    new Circle(5),
];

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

Познакомьтесь самостоятельно с функцией get_class().
Дополните информацию об объекте, для которого считается площадь – пишите что это объект такого-то класса.
Для объектов, которые не реализуют интерфейс CalculateSquare пишите:
Объект класса ТУТ_НАЗВАНИЕ_КЛАССА не реализует интерфейс CalculateSquare.

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

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 
{
    private $x;

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

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

class Circle implements CalculateSquare
{
    private $r;

    const PI = 3.1416;

    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 'Объект ' . get_class($object) . ' реализует интерфейс CalculateSquare. Площадь: ' . $object->calculateSquare();
        echo '<br>';
    }
    else{
        echo 'Объект класса ' . get_class($object) . ' не реализует интерфейс CalculateSquare. Площадь: ' . $object->calculateSquare();
        echo '<br>';
    }
}
bildep@gmail.com
bildep@gmail.com
<?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
{
    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 'Объект класса ' . get_class($object) . ' реализует интерфейс CalculateSquare. Площадь: ' . $object->calculateSquare();
        echo '<br>';
    } else {
        echo 'Объект класса ' . get_class($object) .' не реализует интерфейс CalculateSquare';
        echo '<br>';
    }
}