Особенности реализации классов в PHP

Видимость

Область видимости позволяет контролировать, откуда можно получить доступ к членам PHP class. Например, для того, чтобы предотвратить изменение определенной переменной за пределами класса. По умолчанию видимость является открытой – это означает, что члены вашего класса могут быть доступны из любого места. Объявление видимости не обязательно, если спецификатор доступа не указан, то он определяется как public (общедоступный). Для обеспечения обратной совместимости есть устаревший способ объявления переменной класса, в котором перед именем переменной указывается ключевое слово "var" (это из PHP 4 и пользоваться этим не стоит) и по умолчанию будет public.

В PHP есть три спецификатора доступа: private (закрытый), protected (защищенный) и public (общедоступный).

Private - члены класса могут быть доступны только внутри самого класса.

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

Public - члены PHP class могут быть доступны из любого места: снаружи класса, внутри самого класса и из его дочерних классов.

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

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

Допустим, что вам нужно представить различные виды животных. Вы можете создать класс «Кошка», класс «Собака» и так далее. Но скоро вы поймете, что эти классы будут совсем немного отличаться по функциональности. С другой стороны, есть признаки, которые должны быть у каждого животного. Для реализации этого наследование великолепно подходит. Идея заключается в том, чтобы создать базовый класс Animal, а затем создать дочерний класс для каждого конкретного животного. Еще одно преимущество этого подхода заключается в том, что каждое животное будет иметь одинаковую базовую функциональность.

Давайте посмотрим на пример:

class Animal
{
    public $name;
    
    public function Greet()
    {
        return "Hello, I'm some sort of animal and my name is " . $this->name;
    }
}

Очень простой класс. Тем не менее, "some sort of animal" (какое-то животное) не очень конкретно, поэтому создадим дочерний класс для собаки:

class Dog extends Animal
{
    
}

Dog объявляется как обычный класс, но после этого мы используем ключевое слово extends, говорящее PHP, что класс Dog должен наследоваться от класса Animal. Наш PHP class extends имеет точно такую же функциональность, что и класс Animal. Проверьте это, выполнив следующий код:

$dog = new Dog();
echo $dog->Greet();

Вы увидите, что и name, и функция Greet() по-прежнему есть, но они также по-прежнему слишком анонимны. Изменим это, написав конкретную версию функции Greet() для нашего класса Dog:

class Dog extends Animal
{
    public function Greet()
    {
        return "Hello, I'm a dog and my name is " . $this->name;
    }
}

Обратите внимание, что мы объявляем функцию Greet() еще раз, потому что надо сделать кое-что еще, но переменная $name не объявлена – мы ее определили в родительском классе Animal. Как видите, даже если переменная $name не объявлена в классе Dog, мы можем использовать ее в своей функции Greet(). Теперь, когда оба класса объявлены, пришло время проверить их:

$animal = new Animal();
echo $animal->Greet();
$animal = new Dog();
$animal->name = "Bob";
echo $animal->Greet();

Мы начинаем с создания экземпляра класса Animal, а затем вызываем функцию Greet(). В результате должно быть сгенерировано приветствие, которое мы написали вначале. После этого мы присваиваем новый экземпляр класса Dog переменной $animal, даем реальное имя нашей собаке, а затем снова вызываем функцию Greet(). На этот раз функция Greet() используется экземпляром PHP class extends, и мы получаем более «конкретное» приветствие от нашего животного.

Можно создать класс, который наследуется от класса Dog, который, в свою очередь, наследуется от класса Animal, для экземпляра класса Puppy. Класс Puppy будет иметь переменные и методы как от Dog, так и от класса Animal.

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

Абстрактные классы являются особенными, потому что на их основе нельзя создавать экземпляры класса. Вместо этого можно наследовать определенный в них набор базовой функциональности в новом классе. По этой причине они обычно используются в качестве базовых классов в большей иерархии классов. В главе, посвященной наследованию, мы создали класс Animal, а затем класс Dog - потомка от класса Animal. Класс Animal будет базовым для нашей маленькой коллекции животных.

Метод также может быть помечен как abstract. Как только вы помечаете функцию класса как абстрактную, вы должны определить PHP abstract class. Только абстрактные классы могут содержать абстрактные функции. Другим следствием является то, что вы не должны (и не можете) писать код для функции - это только объявление. Это можно делать для того, чтобы «заставить» какого-либо наследника вашего абстрактного класса реализовать эту функцию и написать правильный код для него.

Если вы этого не сделаете, PHP выдаст сообщение об ошибке. Тем не менее, абстрактные классы могут также содержать неабстрактные методы, что позволяет реализовать основные функциональные возможности в абстрактном классе. Давайте посмотрим на примере. Вот это абстрактный класс:

abstract class Animal
{
    public $name;
    public $age;
    
    public function Describe()
    {
        return $this->name . ", " . $this->age . " years old";    
    }
    
    abstract public function Greet();
}

Это выглядит как обычное исключение, но с парой отличий. Первым из них является ключевое слово abstract, которое используется для обозначения самого класса и последней функции как абстрактных. Как уже упоминалось, абстрактная функция не может содержать код, поэтому заканчивается точкой с запятой. Теперь создадим PHP abstract class, который может наследовать наш класс Animal:

class Dog extends Animal
{
    public function Greet()
    {
        return "Woof!";    
    }
    
    public function Describe()
    {
        return parent::Describe() . ", and I'm a dog!";    
    }
}

Мы реализуем обе функции из класса Animal. Функцию Greet() мы вынуждены реализовать, так как она помечена как абстрактная - она возвращает слово/звук, характерное для вида животного, которое мы создаем. Мы не обязаны реализовать функцию Describe() - это уже реализовано в классе Animal, но можно немного расширить ее функциональность. Мы можем повторно использовать код, реализованный в классе Animal, а затем добавить к нему новый. В этом случае мы используем ключевое слово parent, чтобы сослаться на класс Animal, а затем вызываем в нем функцию Describe().

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

$animal = new Dog();
$animal->name = "Bob";
$animal->age = 7;
echo $animal->Describe();
echo $animal->Greet();

Мы устанавливаем экземпляру класса Dog два свойства, а затем вызываем два метода, определенные в нем. Если вы запустите этот код, то увидите, что, как и ожидалось, метод Describe() сейчас является сочетанием Animal и версии Dog.

Статические классы

PHP class может быть реализован более одного раза, поэтому значения, которые он содержит, уникальны для экземпляра, а не для самого класса. Это означает, что вы не можете использовать методы или переменные в классе без создания экземпляра, но есть исключение из этого правила. Переменные и методы в одном классе могут быть объявлены как static (также называемые shared – «общие»). Это означает, что они могут быть использованы без создания экземпляра класса.

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

В предыдущей части мы написали класс пользователя. Давайте расширим его некоторой статической функциональностью:

<?php
class User
{
    public $name;
    public $age;
    public static $minimumPasswordLength = 6;
    
    public function Describe()
    {
        return $this->name . " is " . $this->age . " years old";
    }
    
    public static function ValidatePassword($password)
    {
        if(strlen($password) >= self::$minimumPasswordLength)
            return true;
        else
            return false;
    }
}

$password = "test";
if(User::ValidatePassword($password))
    echo "Password is valid!";
else
    echo "Password is NOT valid!";
?>

Мы добавили одну статическую переменную $MinimumPasswordLength, которой присвоили значение 6, а затем добавили статическую функцию для проверки, допустим ли данный пароль. Я признаю, что проверка здесь выполняется ограничено, но она может быть расширена. Теперь не могли бы мы сделать это в качестве обычной переменной и функции в классе? Конечно, могли, но имеет смысл сделать это статически, так как мы не используем информацию, относящуюся к одному пользователю. Функциональность общая, так что нет никакой необходимости в создании и использовании экземпляра класса.

Чтобы получить доступ к нашей статической переменной из статического метода, мы вначале используем ключевое слово self, которое похоже на this, но используется для доступа к статическим членам и константам. Очевидно, что оно работает только внутри класса, поэтому для вызова функции ValidatePassword () извне класса, мы используем PHP class name. Вы также заметите, что при обращении к статическим членам требуется оператор двойное двоеточие вместо оператора ->, но кроме этого в основном все то же самое.

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

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

PHP class const просты как обычные константы, за исключением того, что они объявляются в классе и доступны через этот конкретный класс. Так же, как со статическими членами, нужно использовать оператор двойное двоеточие, чтобы получить доступ к константе класса. Вот простой пример:

<?php
class User
{
    const DefaultUsername = "John Doe";
    const MinimumPasswordLength = 6;
}

echo "The default username is " . User::DefaultUsername;
echo "The minimum password length is " . User::MinimumPasswordLength;
?>

Как видите, это то же самое, что и объявление переменных, за исключением того, что нет спецификатора доступа - константа всегда в открытом доступе. По мере необходимости мы присваиваем значения константам, которые затем остаются такими же на всем протяжении выполнения скрипта. Чтобы использовать PHP class const, мы пишем имя класса, оператор с двойным двоеточием, а затем имя константы. Это в принципе все.

Ключевое слово “final”

В предыдущих частях мы видели, как можно позволить классу наследовать от другого класса и как переопределить функцию в унаследованном классе, чтобы изменить первоначальное поведение. Но в некоторых случаях можно препятствовать тому, чтобы класс был унаследован или функция переопределена. Это может быть сделано с помощью ключевого слова final, которое вынуждает PHP выдать ошибку, если кто-то пытается расширить свой окончательный класс или переопределить конечную функцию.

Окончательный (final) класс может выглядеть следующим образом:

final class Animal
{
    public $name;
}
PHP class с окончательной (final) функцией может выглядеть следующим образом:
class Animal
{
    final public function Greet()
    {
        return "The final word!";    
    }
}

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

ВЛВиктория Лебедеваавтор-переводчик статьи «PHP Classes»