Инкапсуляция в PHP

В этом уроке мы узнаем о первом из трёх китов ООП - инкапсуляции. Инкапсуляция (лат. in capsula; от capsula «коробочка») — размещение в оболочке, изоляция, закрытие чего-либо с целью исключения влияния на окружающее. О том, как это используется в объектно-ориентированном программировании, вы узнаете по ходу этого урока.

Свойства объектов

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

  • имя;
  • цвет;
  • вес.

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

<?php
class Cat
{
    public $name;
    public $color;
    public $weight;
}

Всё это: $name, $color, $weight - свойства будущих объектов этого класса. Перед именем свойства всегда ставится модификатор доступа. В нашем случае - это public. Это слово говорит о том, что данное свойство будет доступно всем, кто работает с объектами данного класса. Есть и другие модификаторы доступа, но о них чуть ниже.

И снова сам по себе этот код сейчас ничего не выведет. Это опять - просто шаблон.

Итак, мы сделали некоторый шаблон, который вполне себе описывает котиков. Давайте теперь создадим новый объект этого класса.

<?php
class Cat
{
    public $name;
    public $color;
    public $weight;
}

$cat1 = new Cat();

var_dump($cat1);

Так мы создали объект с типом Cat и вывели его с помощью var_dump().

object(Cat)[1]
  public 'name' => null
  public 'color' => null
  public 'weight' => null

Как видим, в переменной лежит объект (object), и у него есть три свойства, и у всех значения - null. Давайте это исправим. Дадим нашему котику имя, покрасим и наполним его чем-нибудь, чтобы он сколько-нибудь весил. Делается это так:

...

$cat1->name = 'Снежок';
$cat1->color = 'white';
$cat1->weight = 3.5;

Оператор -> (стрелочка, состоящая из двух знаков - "тире" и "больше") используется для доступа к свойствам объекта. В данном коде мы обратились к каждому свойству отдельно и присвоили им значения. Если теперь мы выведем $cat1 с помощью var_dump(), то получим следующее:

object(Cat)[1]
  public 'name' => string 'Снежок' (length=12)
  public 'color' => string 'white' (length=5)
  public 'weight' => float 3.5

Как видим, это уже не ерунда какая-то, а белый Снежок, который весит три с половиной кило.

Теперь мы можем обратиться к свойству этого кота и узнать его имя.

echo $cat1->name;

И получим в результате "Снежок".

Можем создать несколько котов и задать им разные свойства:

<?php

class Cat
{
    public $name;
    public $color;
    public $weight;
}

$cat1 = new Cat();
$cat1->name = 'Снежок';
$cat1->color = 'white';
$cat1->weight = 3.5;

$cat2 = new Cat();
$cat2->name = 'Барсик';
$cat2->color = 'black';
$cat2->weight = 6.2;

var_dump($cat1);
var_dump($cat2);

Результат получится вполне ожидаемый:

object(Cat)[1]
  public 'name' => string 'Снежок' (length=12)
  public 'color' => string 'white' (length=5)
  public 'weight' => float 3.5
object(Cat)[2]
  public 'name' => string 'Барсик' (length=12)
  public 'color' => string 'black' (length=5)
  public 'weight' => float 6.2

Два разных объекта со своими значениями свойств.

Это довольно похоже на работу с массивами, как будто записываем значение по ключу.

То, что внутри объектов есть свойства - это уже проявление инкапсуляции. У объекта есть свойства, он их внутри себя содержит - вот и "капсула".

Методы объектов

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

Методы объявляются следующим образом:

class Cat
{
    public $name;
    public $color;
    public $weight;

    public function sayHello()
    {
        echo 'Мяу!';
    }
}

public - модификатор доступа к методу, говорящий о том, что его могут вызвать все, кто пользуется объектом, sayHello - имя метода, а далее идут аргументы (в нашем случае их нет), а далее - тело метода, в котором мы просто выводим строку 'Мяу!';

Как мы видим, в целом методы объектов не сильно отличаются от обычных функций. При их описании мы только добавляем модификатор доступа.

Вызвать метод мы можем у созданного объекта. Давайте создадим нового кота и попросим его с нами поздороваться. Для вызова метода объекта используется такой же оператор как и для доступа к свойствам объекта ->

$cat1 = new Cat();
$cat1->name = 'Снежок';
$cat1->color = 'white';
$cat1->weight = 3.5;

$cat1->sayHello();

Этот код выведет строку 'Мяу!'. Вот так вот, с нами поздоровался виртуальный кот!

Переменная $this

Да только методы - это не такие уж и простые функции. Внутри методов доступна специальная переменная $this, и в ней хранится... наш текущий созданный объект. БДЫЩЬ! Мозг взорвался :)

На деле всё не так уж и сложно. Мы можем с помощью этой переменной обращаться к другим методам и свойствам данного объекта. Например, давайте научим кота здороваться по-человечески. Пусть он будет называть своё имя. Для этого нам нужно переписать метод sayHello() следующим образом:

class Cat
{
    public $name;
    public $color;
    public $weight;

    public function sayHello()
    {
        echo 'Привет! Меня зовут ' . $this->name . '.';
    }
}

И теперь, когда мы создадим новый объект кота, и попросим его с нами поздороваться, то $this->name вернёт значение свойства name у текущего объекта.

<?php

class Cat
{
    public $name;
    public $color;
    public $weight;

    public function sayHello()
    {
        echo 'Привет! Меня зовут ' . $this->name . '.';
    }
}

$cat1 = new Cat();
$cat1->name = 'Снежок';
$cat1->sayHello();

$cat2 = new Cat();
$cat2->name = 'Барсик';
$cat2->sayHello();

Данный код выведет следующее:

Привет! Меня зовут Снежок.
Привет! Меня зовут Барсик.

Вот так всё просто. Надеюсь, этот наглядный пример помог вам понять, что $this - это просто текущий объект, и что $this есть только у созданного объекта.

И методы, и переменная $this - тоже инкапсуляция! Но и на этом ещё не всё :)

Модификаторы доступа

Сейчас у нас с вами все свойства и методы объектов являются публичными - из любого места в коде, где этот объект доступен, мы можем получить доступ к этим свойствам и методам. Для того чтобы сделать свойство или метод публичным используется ключевое слово public.

Однако, есть и другие модификаторы доступа, и в этом уроке мы с вами изучим ещё один модификатор - private. Он позволяет сделать свойства и методы объекта приватными, после этого они будут доступны только внутри этого объекта.

Например, давайте изменим модификатор для свойства name:

class Cat
{
    private $name;
    public $color;
    public $weight;

    public function sayHello()
    {
        echo 'Привет! Меня зовут ' . $this->name . '.';
    }
}

Давайте теперь попытаемся изменить это свойство у объекта:

$cat1 = new Cat();
$cat1->name = 'Снежок';

Мы получим ошибку:
Ошибка при попытке доступа к приватному свойству

Однако, мы можем написать публичный метод, который позволит задать данное свойство с помощью него. Назовём его setName(). Он будет брать переданную в него строку и устанавливать это значение в свойство name.

class Cat
{
    private $name;
    public $color;
    public $weight;

    public function sayHello()
    {
        echo 'Привет! Меня зовут ' . $this->name . '.';
    }

    public function setName(string $name)
    {
        $this->name = $name;
    }
}

Теперь давайте зададим имя коту с помощью этого метода:

$cat1 = new Cat();
$cat1->setName('Снежок');
$cat1->sayHello();

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

echo $cat1->name;

то мы бы снова получили ошибку доступа.

Как вы могли заметить с помощью метода setName мы позволяем задать в свойство name только строку. Ни число, ни массив, ни что-то ещё, а именно - строку. Это хороший подход - это позволяет использовать различного рода валидации. Ведь внутри метода вы можете выполнить какие-то проверки, прежде чем положить значение в свойство. Это позволяет избежать ситуаций, когда в свойства можно засунуть любую ерунду. Такие методы как setName(), задающие значения свойствам объекта называются сеттерами.

Чтобы получить напрямую значение приватного свойства у объекта можно написать другой публичный метод, который будет просто возвращать значение этого свойства. Напишем метод getName().

class Cat
{
    private $name;
    public $color;
    public $weight;

    public function sayHello()
    {
        echo 'Привет! Меня зовут ' . $this->name . '.';
    }

    public function setName(string $name)
    {
        $this->name = $name;
    }

    public function getName(): string
    {
        return $this->name;
    }
}

Теперь мы можем просто получить имя кота извне:

$cat1 = new Cat();
$cat1->setName('Снежок');
echo $cat1->getName();

Такие методы, в свою очередь, именуются геттерами.

Модификаторы доступа - это ещё одно проявление инкапсуляции.

Конструктор

А теперь давайте возьмём и сломаем одного кота :)

Для этого мы не будем давать ему имя, и вызовем метод getName().

$cat1 = new Cat();
echo $cat1->getName();

Что произойдёт? Правильно - ошибка!

Ведь мы описали, что getName() всегда должна отдавать строку. А в нашем объекте возвращается null.

Можно ли как-то гарантировать, что в свойстве name всегда будет строка? Можно. Для этого существует конструктор - это метод, который вызывается при создании объекта этого класса. В принципе, это такой же метод, как и все другие, он может иметь различные аргументы. Но он обязательно вызывается автоматически при создании объекта класса, в котором он описан.

Метод-конструктор должен называться __construct. Именно так и никак иначе.

Давайте создадим конструктор для нашего кота, который будет иметь обязательный аргумент $name с типом строка.

class Cat
{
    private $name;

    public function __construct(string $name)
    {
        $this->name = $name;
    }

    public function sayHello()
    {
        echo 'Привет! Меня зовут ' . $this->name . '.';
    }

    public function setName(string $name)
    {
        $this->name = $name;
    }

    public function getName(): string
    {
        return $this->name;
    }
}

Конструктор принято объявлять в начале класса, после объявления свойств, но перед другими методами.

Теперь чтобы создать кота с именем Снежок мы должны передать аргумент при создании нового объекта:

$cat1 = new Cat('Снежок');

И вот что сейчас произошло: аргумент, переданный в круглые скобки, был передан в метод __construct(). Там это значение установилось в свойство объекта name.

Если мы сейчас попробуем узнать имя этого кота, то мы его получим.

echo $cat1->getName();

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

$cat1 = new Cat();

Мы получим ошибку.
Ошибка об отсутствии нужного количества аргументов конструктора

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

Таким образом, мы получили класс котов, объекты которого нельзя создать без имени. И именно так и должно быть в мире - у всех котиков должны быть имена. Согласитесь, такой мир был бы гораздо лучше чем тот, в котором мы живём. Ну так вот в программировании, мы способны построить такие миры, какие сами пожелаем. И это круто :)

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

Это был первый кит ООП - инкапсуляция. До встречи на следующем уроке, а пока за домашку.

loader
Домашнее задание
  • Дополните метод sayHello(), чтобы котики после того, как назвали своё имя, говорили о том, какого они цвета.
  • Сделайте свойство color приватным и добавьте в конструктор ещё один аргумент, через который при создании котика будет задаваться его цвет.
  • Сделайте геттер, который будет позволять получить свойство color.
  • Подумайте, стоит ли давать возможность менять котикам цвета после их создания? Если вам хватило совести сказать да - добавьте ещё и сеттер для этого свойства. Это вам в наказание - хорошие люди котов не перекрашивают.
  • Создайте теперь белого Снежка и рыжего Барсика и попросите их рассказать о себе.
Комментарии
Этот урок набрал набрал достаточно большое количество комментариев и дальнейшее его комментирование отключено. Если вы хотели убедиться в правильности выполнения ДЗ или у вас возник вопрос по уроку, посмотрите ранее добавленные комментарии, кликнув по кнопке ниже. Скорее всего вы найдете там то, что искали. Если это не помогло - задайте вопрос в чате в телеграме - https://t.me/php_zone
Логические задачи с собеседований