Nette Documentation Preview

syntax
Введение в объектно-ориентированное программирование
****************************************************

.[perex]
Термин "ООП" означает объектно-ориентированное программирование - способ организации и структурирования кода. ООП позволяет рассматривать программу не как последовательность команд и функций, а как набор объектов, взаимодействующих друг с другом.

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

Рассмотрим, как можно создать простой класс в PHP. При определении класса используется ключевое слово "class", за которым следует имя класса, а затем фигурные скобки, в которые заключаются функции класса (называемые "методами") и переменные класса (называемые "свойствами" или "атрибутами"):

```php
class Car
{
	function honk()
	{
		echo 'Beep beep!';
	}
}
```

В данном примере мы создали класс `Car` с одной функцией (или "методом") `honk`.

Каждый класс должен решать только одну основную задачу. Если класс решает слишком много задач, то целесообразно разделить его на более мелкие, специализированные классы.

Классы обычно хранятся в отдельных файлах, что позволяет упорядочить код и облегчить навигацию по нему. Имя файла должно совпадать с именем класса, поэтому для класса `Car` имя файла будет `Car.php`.

При именовании классов следует придерживаться правила "PascalCase", то есть каждое слово в имени начинается с заглавной буквы, без подчеркиваний и других разделителей. Методы и свойства следует называть в "camelCase", то есть со строчной буквы.

Некоторые методы в PHP играют особую роль и имеют префикс `__` (два знака подчеркивания). Одним из наиболее важных специальных методов является "конструктор", обозначаемый как `__construct`. Конструктор - это метод, который автоматически вызывается при создании нового экземпляра класса.

Мы часто используем конструктор для установки начального состояния объекта. Например, при создании объекта, представляющего человека, с помощью конструктора можно задать его возраст, имя или другие атрибуты.

Рассмотрим, как использовать конструктор в PHP:

```php
class Person
{
	private $age;

	function __construct($age)
	{
		$this->age = $age;
	}

	function howOldAreYou()
	{
		return $this->age;
	}
}

$person = new Person(25);
echo $person->howOldAreYou(); // Outputs: 25
```

В данном примере класс `Person` имеет свойство `$age` и конструктор, который устанавливает это свойство. Затем метод `howOldAreYou()` предоставляет доступ к возрасту человека.

Ключевое слово `new` используется для создания нового экземпляра класса. В приведенном примере мы создали нового человека в возрасте 25 лет.

Также можно задать значения по умолчанию для параметров конструктора, если они не указаны при создании объекта. Например:

```php
class Person
{
	private $age;

	function __construct($age = 20)
	{
		$this->age = $age;
	}

	function howOldAreYou()
	{
		return $this->age;
	}
}

$person = new Person;  // if no argument is passed, parentheses can be omitted
echo $person->howOldAreYou(); // Outputs: 20
```

В данном примере, если при создании объекта `Person` не указать возраст, будет использовано значение по умолчанию 20.

Наконец, определение свойства с его инициализацией через конструктор можно сократить и упростить следующим образом:

```php
class Person
{
	function __construct(
		private $age = 20,
	) {
	}
}
```


Пространства имен .[#toc-namespaces]
------------------------------------

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

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

Представьте, что в вашем проекте есть класс с именем `Car`, и вы хотите поместить его в пространство имен `Transport`. Это можно сделать следующим образом:

```php
namespace Transport;

class Car
{
	function honk()
	{
		echo 'Beep beep!';
	}
}
```

Если вы хотите использовать класс `Car` в другом файле, то вам необходимо указать, из какого пространства имен он происходит:

```php
$car = new Transport\Car;
```

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

```php
use Transport\Car;

$car = new Car;
```


Наследование .[#toc-inheritance]
--------------------------------

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

Проще говоря, если у нас есть один класс и мы хотим создать другой, производный от него, но с некоторыми изменениями, то мы можем "унаследовать" новый класс от исходного.

В PHP наследование реализуется с помощью ключевого слова `extends`.

Наш класс `Person` хранит информацию о возрасте. Мы можем иметь другой класс, `Student`, который расширяет `Person` и добавляет информацию о специальности.

Рассмотрим пример:

```php
class Person
{
	private $age;

	function __construct($age)
	{
		$this->age = $age;
	}

	function howOldAreYou()
	{
		return $this->age;
	}
}

class Student extends Person
{
	private $fieldOfStudy;

	function __construct($age, $fieldOfStudy)
	{
		parent::__construct($age);
		$this->fieldOfStudy = $fieldOfStudy;
	}

	function printInformation()
	{
		echo 'Age of student: ', $this->howOldAreYou();
		echo 'Field of study: ', $this->fieldOfStudy;
	}
}

$student = new Student(20, 'Computer Science');
$student->printInformation();
```

Как работает этот код?

- Мы использовали ключевое слово `extends` для расширения класса `Person`, то есть класс `Student` наследует все методы и свойства от `Person`.

- Ключевое слово `parent::` позволяет нам вызывать методы из родительского класса. В данном случае мы вызвали конструктор из класса `Person`, а затем добавили собственную функциональность в класс `Student`.

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

Важно понимать, что основная цель наследования **не** заключается в предотвращении дублирования кода. Напротив, неправильное использование наследования может привести к появлению сложного и трудноразрешимого кода. Если между классами нет отношения "является", то вместо наследования следует рассмотреть композицию.


Композиция .[#toc-composition]
------------------------------

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

Например, у нас есть класс `Engine` и класс `Car`. Вместо того чтобы сказать "Автомобиль - это двигатель", мы говорим "Автомобиль имеет двигатель", что является типичным композиционным отношением.

```php
class Engine
{
	function start()
	{
		echo "Engine is running.";
	}
}

class Car
{
	private $engine;

	function __construct()
	{
		$this->engine = new Engine;
	}

	function start()
	{
		$this->engine->start();
		echo "The car is ready to drive!";
	}
}

$car = new Car;
$car->start();
```

Здесь `Car` не имеет всех свойств и методов `Engine`, но имеет доступ к ним через свойство `$engine`.

Преимуществом композиции является большая гибкость проектирования и лучшая адаптируемость к будущим изменениям.


Наглядность .[#toc-visibility]
------------------------------

В PHP можно определить "видимость" для свойств, методов и констант класса. Видимость определяет, где можно получить доступ к этим элементам.

1. **Public:** Если элемент помечен как `public`, это означает, что вы можете получить к нему доступ из любого места, даже за пределами класса.

2. **Защищенный:** Элемент, помеченный как `protected`, доступен только в пределах данного класса и всех его потомков (классов, наследующих от него).

3. **Private:** Если элемент помечен как `private`, то доступ к нему возможен только из класса, в котором он был определен.

Если не указать значение видимости, PHP автоматически установит его равным `public`.

Рассмотрим пример кода:

```php
class VisibilityExample
{
	public $publicProperty = 'Public';
	protected $protectedProperty = 'Protected';
	private $privateProperty = 'Private';

	public function printProperties()
	{
		echo $this->publicProperty;     // Works
		echo $this->protectedProperty;  // Works
		echo $this->privateProperty;    // Works
	}
}

$object = new VisibilityExample;
$object->printProperties();
echo $object->publicProperty;        // Works
// echo $object->protectedProperty;   // Throws an error
// echo $object->privateProperty;     // Throws an error
```

Продолжаем наследование классов:

```php
class ChildClass extends VisibilityExample
{
	public function printProperties()
	{
		echo $this->publicProperty;     // Works
		echo $this->protectedProperty;  // Works
		// echo $this->privateProperty;   // Throws an error
	}
}
```

В данном случае метод `printProperties()` в `ChildClass` может получить доступ к публичным и защищенным свойствам, но не может получить доступ к приватным свойствам родительского класса.

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


Ключевое слово final .[#toc-final-keyword]
------------------------------------------

В PHP мы можем использовать ключевое слово `final`, если хотим запретить наследование или переопределение класса, метода или константы. Если класс помечен как `final`, он не может быть расширен. Если метод помечен как `final`, он не может быть переопределен в подклассе.

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

```php
final class FinalClass
{
}

// The following code will throw an error because we cannot inherit from a final class.
class ChildOfFinalClass extends FinalClass
{
}
```

В данном примере попытка наследоваться от конечного класса `FinalClass` приведет к ошибке.


Статические свойства и методы .[#toc-static-properties-and-methods]
-------------------------------------------------------------------

Когда мы говорим о "статических" элементах класса в PHP, мы имеем в виду методы и свойства, которые принадлежат самому классу, а не его конкретному экземпляру. Это означает, что для доступа к ним не нужно создавать экземпляр класса. Вместо этого вы вызываете или обращаетесь к ним непосредственно через имя класса.

Следует помнить, что поскольку статические элементы принадлежат классу, а не его экземплярам, то внутри статических методов нельзя использовать псевдопеременную `$this`.

Использование статических свойств приводит к [обфусцированному коду, полному подводных камней |dependency-injection:global-state], поэтому их никогда не следует использовать, и мы не будем приводить здесь пример. С другой стороны, статические методы полезны. Вот пример:

```php
class Calculator
{
	public static function add($a, $b)
	{
		return $a + $b;
	}

	public static function subtract($a, $b)
	{
		return $a - $b;
	}
}

// Using the static method without creating an instance of the class
echo Calculator::add(5, 3); // Output: 8
echo Calculator::subtract(5, 3); // Output: 2
```

В этом примере мы создали класс `Calculator` с двумя статическими методами. Мы можем вызывать эти методы напрямую, не создавая экземпляр класса, используя оператор `::`. Статические методы особенно полезны для операций, которые не зависят от состояния конкретного экземпляра класса.


Константы класса .[#toc-class-constants]
----------------------------------------

Внутри классов мы имеем возможность определять константы. Константы - это значения, которые никогда не изменяются в процессе выполнения программы. В отличие от переменных, значение константы остается неизменным.

```php
class Car
{
	public const NumberOfWheels = 4;

	public function displayNumberOfWheels(): int
	{
		echo self::NumberOfWheels;
	}
}

echo Car::NumberOfWheels;  // Output: 4
```

В данном примере мы имеем класс `Car` с константой `NumberOfWheels`. При обращении к константе внутри класса мы можем использовать ключевое слово `self` вместо имени класса.


Объектные интерфейсы .[#toc-object-interfaces]
----------------------------------------------

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

В PHP интерфейсы определяются с помощью ключевого слова `interface`. Все методы, определенные в интерфейсе, являются публичными (`public`). Когда класс реализует интерфейс, он использует ключевое слово `implements`.

```php
interface Animal
{
	function makeSound();
}

class Cat implements Animal
{
	public function makeSound()
	{
		echo 'Meow';
	}
}

$cat = new Cat;
$cat->makeSound();
```

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


Абстрактные классы .[#toc-abstract-classes]
-------------------------------------------

Абстрактные классы служат базовыми шаблонами для других классов, но их экземпляры нельзя создавать напрямую. Они содержат смесь полных методов и абстрактных методов, которые не имеют определенного содержания. Классы, наследующие от абстрактных классов, должны предоставлять определения для всех абстрактных методов родительского класса.

Для определения абстрактного класса мы используем ключевое слово `abstract`.

```php
abstract class AbstractClass
{
	public function regularMethod()
	{
		echo 'This is a regular method';
	}

	abstract public function abstractMethod();
}

class Child extends AbstractClass
{
	public function abstractMethod()
	{
		echo 'This is the implementation of the abstract method';
	}
}

$instance = new Child;
$instance->regularMethod();
$instance->abstractMethod();
```

В данном примере мы имеем абстрактный класс с одним обычным и одним абстрактным методом. Затем у нас есть класс `Child`, который наследуется от `AbstractClass` и предоставляет реализацию абстрактного метода.


Проверка типа .[#toc-type-checking]
-----------------------------------

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

Типы, с которыми мы можем столкнуться в PHP:

1. **Базовые типы**: К ним относятся `int` (целые числа), `float` (числа с плавающей точкой), `bool` (булевы значения), `string` (строки), `array` (массивы) и `null`.
2. **Классы**: Когда мы хотим, чтобы значение было экземпляром определенного класса.
3. **Интерфейсы**: Определяют набор методов, которые должен реализовать класс. Значение, удовлетворяющее интерфейсу, должно обладать этими методами.
4. **Смешанные типы**: Мы можем указать, что переменная может иметь несколько допустимых типов.
5. **Void**: Этот специальный тип указывает на то, что функция или метод не возвращает никакого значения.

Рассмотрим, как модифицировать код для включения типов:

```php
class Person
{
	private int $age;

	public function __construct(int $age)
	{
		$this->age = $age;
	}

	public function printAge(): void
	{
		echo "This person is " . $this->age . " years old.";
	}
}

/**
 * A function that accepts a Person object and prints the person's age.
 */
function printPersonAge(Person $person): void
{
	$person->printAge();
}
```

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


Сравнение и идентичность .[#toc-comparison-and-identity]
--------------------------------------------------------

В PHP можно сравнивать объекты двумя способами:

1. Сравнение по значению `==`: проверяется, относятся ли объекты к одному классу и имеют ли они одинаковые значения в своих свойствах.
2. Идентичность `===`: Проверяет, является ли объект одним и тем же экземпляром.

```php
class Car
{
	public string $brand;

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

$car1 = new Car('Skoda');
$car2 = new Car('Skoda');
$car3 = $car1;

var_dump($car1 == $car2);   // true, because they have the same value
var_dump($car1 === $car2);  // false, because they are not the same instance
var_dump($car1 === $car3);  // true, because $car3 is the same instance as $car1
```


Оператор `instanceof` .[#toc-the-instanceof-operator]
-----------------------------------------------------

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

Представьте, что у нас есть класс `Person` и другой класс `Student`, который является потомком `Person`:

```php
class Person
{
	private int $age;

	public function __construct(int $age)
	{
		$this->age = $age;
	}
}

class Student extends Person
{
	private string $major;

	public function __construct(int $age, string $major)
	{
		parent::__construct($age);
		$this->major = $major;
	}
}

$student = new Student(20, 'Computer Science');

// Check if $student is an instance of the Student class
var_dump($student instanceof Student);  // Output: bool(true)

// Check if $student is an instance of the Person class (because Student is a descendant of Person)
var_dump($student instanceof Person);   // Output: bool(true)
```

Из выводов видно, что объект `$student` считается экземпляром обоих классов `Student` и `Person`.


Текучие интерфейсы .[#toc-fluent-interfaces]
--------------------------------------------

Fluent Interface" - это техника в ООП, позволяющая объединять методы в цепочку одним вызовом. Это часто упрощает и уточняет код.

Ключевым элементом флюентного интерфейса является то, что каждый метод в цепочке возвращает ссылку на текущий объект. Это достигается за счет использования `return $this;` в конце метода. Такой стиль программирования часто ассоциируется с методами, называемыми "сеттерами", которые устанавливают значения свойств объекта.

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

```php
public function sendMessage()
{
	$email = new Email;
	$email->setFrom('sender@example.com')
		  ->setRecipient('admin@example.com')
		  ->setMessage('Hello, this is a message.')
		  ->send();
}
```

В данном примере методы `setFrom()`, `setRecipient()` и `setMessage()` используются для установки соответствующих значений (отправитель, получатель, содержание сообщения). После установки каждого из этих значений методы возвращают текущий объект (`$email`), что позволяет выстроить после него цепочку других методов. Наконец, мы вызываем метод `send()`, который, собственно, и отправляет письмо.

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


Копирование с помощью `clone` .[#toc-copying-with-clone]
--------------------------------------------------------

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

Если при копировании объекта нам необходимо изменить некоторые его свойства, мы можем определить в классе специальный метод `__clone()`. Этот метод автоматически вызывается при клонировании объекта.

```php
class Sheep
{
	public string $name;

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

	public function __clone()
	{
		$this->name = 'Clone of ' . $this->name;
	}
}

$original = new Sheep('Dolly');
echo $original->name . "\n";  // Outputs: Dolly

$clone = clone $original;
echo $clone->name . "\n";     // Outputs: Clone of Dolly
```

В данном примере мы имеем класс `Sheep` с одним свойством `$name`. Когда мы клонируем экземпляр этого класса, метод `__clone()` гарантирует, что имя клонированной овцы получит префикс "Clone of".


Свойства .[#toc-traits]
-----------------------

Трейты в PHP - это инструмент, позволяющий совместно использовать методы, свойства и константы между классами и предотвращающий дублирование кода. Их можно рассматривать как механизм "копирования и вставки" (Ctrl-C и Ctrl-V), когда содержимое трейта "вставляется" в классы. Это позволяет повторно использовать код без создания сложных иерархий классов.

Рассмотрим простой пример использования трейтов в PHP:

```php
trait Honking
{
	public function honk()
	{
		echo 'Beep beep!';
	}
}

class Car
{
	use Honking;
}

class Truck
{
	use Honking;
}

$car = new Car;
$car->honk(); // Outputs 'Beep beep!'

$truck = new Truck;
$truck->honk(); // Also outputs 'Beep beep!'
```

В этом примере у нас есть тред `Honking`, содержащий один метод `honk()`. Затем у нас есть два класса: `Car` и `Truck`, оба из которых используют признак `Honking`. В результате оба класса "имеют" метод `honk()`, и мы можем вызывать его на объектах обоих классов.

Трейты позволяют легко и эффективно обмениваться кодом между классами. Они не входят в иерархию наследования, т.е. `$car instanceof Honking` вернет `false`.


Исключения
----------

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

В PHP для таких объектов существует встроенный класс `Exception`. Он имеет несколько методов, позволяющих получить дополнительную информацию об исключении, например, сообщение об ошибке, файл, строку, в которой произошла ошибка, и т.д.

При возникновении проблемы мы можем "выбросить" исключение (используя `throw`). Если мы хотим "поймать" и обработать это исключение, мы используем блоки `try` и `catch`.

Давайте посмотрим, как это работает:

```php
try {
	throw new Exception('Message explaining the reason for the exception');

	// This code won't execute
	echo 'I am a message that nobody will read';

} catch (Exception $e) {
	echo 'Exception caught: '. $e->getMessage();
}
```

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

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

Можно также создать иерархию исключений, в которой каждый класс исключений наследуется от предыдущего. В качестве примера рассмотрим простое банковское приложение, позволяющее вносить и снимать деньги:

```php
class BankingException extends Exception {}
class InsufficientFundsException extends BankingException {}
class ExceededLimitException extends BankingException {}

class BankAccount
{
	private int $balance = 0;
	private int $dailyLimit = 1000;

	public function deposit(int $amount): int
	{
		$this->balance += $amount;
		return $this->balance;
	}

	public function withdraw(int $amount): int
	{
		if ($amount > $this->balance) {
			throw new InsufficientFundsException('Not enough funds in the account.');
		}

		if ($amount > $this->dailyLimit) {
			throw new ExceededLimitException('Daily withdrawal limit exceeded.');
		}

		$this->balance -= $amount;
		return $this->balance;
	}
}

$account = new BankAccount;
$account->deposit(500);

try {
	$account->withdraw(1500);
} catch (ExceededLimitException $e) {
	echo $e->getMessage();
} catch (InsufficientFundsException $e) {
	echo $e->getMessage();
} catch (BankingException $e) {
	echo 'An error occurred during the operation.';
}
```

В этом примере важно обратить внимание на порядок следования блоков `catch`. Поскольку все исключения наследуются от `BankingException`, то если бы этот блок был первым, то все исключения были бы пойманы в нем, а код не дошел бы до последующих блоков `catch`. Поэтому важно, чтобы более специфические исключения (т.е. те, которые наследуются от других) располагались выше в порядке блоков `catch`, чем их родительские исключения.


Лучшие практики .[#toc-best-practices]
--------------------------------------

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

1) **Разделение обязанностей**: Каждый класс должен иметь четко определенную ответственность и решать только одну основную задачу. Если класс выполняет слишком много задач, то целесообразно разделить его на более мелкие специализированные классы.
2) **Инкапсуляция**: Данные и методы должны быть максимально скрыты и доступны только через определенный интерфейс. Это позволяет изменять внутреннюю реализацию класса, не затрагивая остальной код.
3) **Инъекция зависимостей**: Вместо того чтобы создавать зависимости непосредственно внутри класса, следует "инжектировать" их извне. Для более глубокого понимания этого принципа мы рекомендуем [главы, посвященные Dependency Injection |dependency-injection:introduction].

Введение в объектно-ориентированное программирование

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

В ООП „объект“ – это единица, содержащая данные и функции, которые оперируют этими данными. Объекты создаются на основе „классов“, которые можно понимать как чертежи или шаблоны объектов. Имея класс, мы можем создать его „экземпляр“, то есть конкретный объект, созданный на основе этого класса.

Рассмотрим, как можно создать простой класс в PHP. При определении класса используется ключевое слово „class“, за которым следует имя класса, а затем фигурные скобки, в которые заключаются функции класса (называемые „методами“) и переменные класса (называемые „свойствами“ или „атрибутами“):

class Car
{
	function honk()
	{
		echo 'Beep beep!';
	}
}

В данном примере мы создали класс Car с одной функцией (или „методом“) honk.

Каждый класс должен решать только одну основную задачу. Если класс решает слишком много задач, то целесообразно разделить его на более мелкие, специализированные классы.

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

При именовании классов следует придерживаться правила „PascalCase“, то есть каждое слово в имени начинается с заглавной буквы, без подчеркиваний и других разделителей. Методы и свойства следует называть в „camelCase“, то есть со строчной буквы.

Некоторые методы в PHP играют особую роль и имеют префикс __ (два знака подчеркивания). Одним из наиболее важных специальных методов является „конструктор“, обозначаемый как __construct. Конструктор – это метод, который автоматически вызывается при создании нового экземпляра класса.

Мы часто используем конструктор для установки начального состояния объекта. Например, при создании объекта, представляющего человека, с помощью конструктора можно задать его возраст, имя или другие атрибуты.

Рассмотрим, как использовать конструктор в PHP:

class Person
{
	private $age;

	function __construct($age)
	{
		$this->age = $age;
	}

	function howOldAreYou()
	{
		return $this->age;
	}
}

$person = new Person(25);
echo $person->howOldAreYou(); // Outputs: 25

В данном примере класс Person имеет свойство $age и конструктор, который устанавливает это свойство. Затем метод howOldAreYou() предоставляет доступ к возрасту человека.

Ключевое слово new используется для создания нового экземпляра класса. В приведенном примере мы создали нового человека в возрасте 25 лет.

Также можно задать значения по умолчанию для параметров конструктора, если они не указаны при создании объекта. Например:

class Person
{
	private $age;

	function __construct($age = 20)
	{
		$this->age = $age;
	}

	function howOldAreYou()
	{
		return $this->age;
	}
}

$person = new Person;  // if no argument is passed, parentheses can be omitted
echo $person->howOldAreYou(); // Outputs: 20

В данном примере, если при создании объекта Person не указать возраст, будет использовано значение по умолчанию 20.

Наконец, определение свойства с его инициализацией через конструктор можно сократить и упростить следующим образом:

class Person
{
	function __construct(
		private $age = 20,
	) {
	}
}

Пространства имен

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

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

Представьте, что в вашем проекте есть класс с именем Car, и вы хотите поместить его в пространство имен Transport. Это можно сделать следующим образом:

namespace Transport;

class Car
{
	function honk()
	{
		echo 'Beep beep!';
	}
}

Если вы хотите использовать класс Car в другом файле, то вам необходимо указать, из какого пространства имен он происходит:

$car = new Transport\Car;

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

use Transport\Car;

$car = new Car;

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

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

Проще говоря, если у нас есть один класс и мы хотим создать другой, производный от него, но с некоторыми изменениями, то мы можем „унаследовать“ новый класс от исходного.

В PHP наследование реализуется с помощью ключевого слова extends.

Наш класс Person хранит информацию о возрасте. Мы можем иметь другой класс, Student, который расширяет Person и добавляет информацию о специальности.

Рассмотрим пример:

class Person
{
	private $age;

	function __construct($age)
	{
		$this->age = $age;
	}

	function howOldAreYou()
	{
		return $this->age;
	}
}

class Student extends Person
{
	private $fieldOfStudy;

	function __construct($age, $fieldOfStudy)
	{
		parent::__construct($age);
		$this->fieldOfStudy = $fieldOfStudy;
	}

	function printInformation()
	{
		echo 'Age of student: ', $this->howOldAreYou();
		echo 'Field of study: ', $this->fieldOfStudy;
	}
}

$student = new Student(20, 'Computer Science');
$student->printInformation();

Как работает этот код?

  • Мы использовали ключевое слово extends для расширения класса Person, то есть класс Student наследует все методы и свойства от Person.
  • Ключевое слово parent:: позволяет нам вызывать методы из родительского класса. В данном случае мы вызвали конструктор из класса Person, а затем добавили собственную функциональность в класс Student.

Наследование предназначено для ситуаций, когда между классами существует связь „есть“. Например, Student – это Person. Кошка – это животное. Это позволяет нам в случаях, когда в коде ожидается один объект (например, „Person“), использовать вместо него производный объект (например, „Student“).

Важно понимать, что основная цель наследования не заключается в предотвращении дублирования кода. Напротив, неправильное использование наследования может привести к появлению сложного и трудноразрешимого кода. Если между классами нет отношения „является“, то вместо наследования следует рассмотреть композицию.

Композиция

Композиция – это техника, при которой вместо наследования свойств и методов другого класса мы просто используем его экземпляр в своем классе. Это позволяет объединить функциональные возможности и свойства нескольких классов без создания сложных структур наследования.

Например, у нас есть класс Engine и класс Car. Вместо того чтобы сказать „Автомобиль – это двигатель“, мы говорим „Автомобиль имеет двигатель“, что является типичным композиционным отношением.

class Engine
{
	function start()
	{
		echo "Engine is running.";
	}
}

class Car
{
	private $engine;

	function __construct()
	{
		$this->engine = new Engine;
	}

	function start()
	{
		$this->engine->start();
		echo "The car is ready to drive!";
	}
}

$car = new Car;
$car->start();

Здесь Car не имеет всех свойств и методов Engine, но имеет доступ к ним через свойство $engine.

Преимуществом композиции является большая гибкость проектирования и лучшая адаптируемость к будущим изменениям.

Наглядность

В PHP можно определить „видимость“ для свойств, методов и констант класса. Видимость определяет, где можно получить доступ к этим элементам.

  1. Public: Если элемент помечен как public, это означает, что вы можете получить к нему доступ из любого места, даже за пределами класса.
  2. Защищенный: Элемент, помеченный как protected, доступен только в пределах данного класса и всех его потомков (классов, наследующих от него).
  3. Private: Если элемент помечен как private, то доступ к нему возможен только из класса, в котором он был определен.

Если не указать значение видимости, PHP автоматически установит его равным public.

Рассмотрим пример кода:

class VisibilityExample
{
	public $publicProperty = 'Public';
	protected $protectedProperty = 'Protected';
	private $privateProperty = 'Private';

	public function printProperties()
	{
		echo $this->publicProperty;     // Works
		echo $this->protectedProperty;  // Works
		echo $this->privateProperty;    // Works
	}
}

$object = new VisibilityExample;
$object->printProperties();
echo $object->publicProperty;        // Works
// echo $object->protectedProperty;   // Throws an error
// echo $object->privateProperty;     // Throws an error

Продолжаем наследование классов:

class ChildClass extends VisibilityExample
{
	public function printProperties()
	{
		echo $this->publicProperty;     // Works
		echo $this->protectedProperty;  // Works
		// echo $this->privateProperty;   // Throws an error
	}
}

В данном случае метод printProperties() в ChildClass может получить доступ к публичным и защищенным свойствам, но не может получить доступ к приватным свойствам родительского класса.

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

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

В PHP мы можем использовать ключевое слово final, если хотим запретить наследование или переопределение класса, метода или константы. Если класс помечен как final, он не может быть расширен. Если метод помечен как final, он не может быть переопределен в подклассе.

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

final class FinalClass
{
}

// The following code will throw an error because we cannot inherit from a final class.
class ChildOfFinalClass extends FinalClass
{
}

В данном примере попытка наследоваться от конечного класса FinalClass приведет к ошибке.

Статические свойства и методы

Когда мы говорим о „статических“ элементах класса в PHP, мы имеем в виду методы и свойства, которые принадлежат самому классу, а не его конкретному экземпляру. Это означает, что для доступа к ним не нужно создавать экземпляр класса. Вместо этого вы вызываете или обращаетесь к ним непосредственно через имя класса.

Следует помнить, что поскольку статические элементы принадлежат классу, а не его экземплярам, то внутри статических методов нельзя использовать псевдопеременную $this.

Использование статических свойств приводит к обфусцированному коду, полному подводных камней, поэтому их никогда не следует использовать, и мы не будем приводить здесь пример. С другой стороны, статические методы полезны. Вот пример:

class Calculator
{
	public static function add($a, $b)
	{
		return $a + $b;
	}

	public static function subtract($a, $b)
	{
		return $a - $b;
	}
}

// Using the static method without creating an instance of the class
echo Calculator::add(5, 3); // Output: 8
echo Calculator::subtract(5, 3); // Output: 2

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

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

Внутри классов мы имеем возможность определять константы. Константы – это значения, которые никогда не изменяются в процессе выполнения программы. В отличие от переменных, значение константы остается неизменным.

class Car
{
	public const NumberOfWheels = 4;

	public function displayNumberOfWheels(): int
	{
		echo self::NumberOfWheels;
	}
}

echo Car::NumberOfWheels;  // Output: 4

В данном примере мы имеем класс Car с константой NumberOfWheels. При обращении к константе внутри класса мы можем использовать ключевое слово self вместо имени класса.

Объектные интерфейсы

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

В PHP интерфейсы определяются с помощью ключевого слова interface. Все методы, определенные в интерфейсе, являются публичными (public). Когда класс реализует интерфейс, он использует ключевое слово implements.

interface Animal
{
	function makeSound();
}

class Cat implements Animal
{
	public function makeSound()
	{
		echo 'Meow';
	}
}

$cat = new Cat;
$cat->makeSound();

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

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

Абстрактные классы служат базовыми шаблонами для других классов, но их экземпляры нельзя создавать напрямую. Они содержат смесь полных методов и абстрактных методов, которые не имеют определенного содержания. Классы, наследующие от абстрактных классов, должны предоставлять определения для всех абстрактных методов родительского класса.

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

abstract class AbstractClass
{
	public function regularMethod()
	{
		echo 'This is a regular method';
	}

	abstract public function abstractMethod();
}

class Child extends AbstractClass
{
	public function abstractMethod()
	{
		echo 'This is the implementation of the abstract method';
	}
}

$instance = new Child;
$instance->regularMethod();
$instance->abstractMethod();

В данном примере мы имеем абстрактный класс с одним обычным и одним абстрактным методом. Затем у нас есть класс Child, который наследуется от AbstractClass и предоставляет реализацию абстрактного метода.

Проверка типа

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

Типы, с которыми мы можем столкнуться в PHP:

  1. Базовые типы: К ним относятся int (целые числа), float (числа с плавающей точкой), bool (булевы значения), string (строки), array (массивы) и null.
  2. Классы: Когда мы хотим, чтобы значение было экземпляром определенного класса.
  3. Интерфейсы: Определяют набор методов, которые должен реализовать класс. Значение, удовлетворяющее интерфейсу, должно обладать этими методами.
  4. Смешанные типы: Мы можем указать, что переменная может иметь несколько допустимых типов.
  5. Void: Этот специальный тип указывает на то, что функция или метод не возвращает никакого значения.

Рассмотрим, как модифицировать код для включения типов:

class Person
{
	private int $age;

	public function __construct(int $age)
	{
		$this->age = $age;
	}

	public function printAge(): void
	{
		echo "This person is " . $this->age . " years old.";
	}
}

/**
 * A function that accepts a Person object and prints the person's age.
 */
function printPersonAge(Person $person): void
{
	$person->printAge();
}

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

Сравнение и идентичность

В PHP можно сравнивать объекты двумя способами:

  1. Сравнение по значению ==: проверяется, относятся ли объекты к одному классу и имеют ли они одинаковые значения в своих свойствах.
  2. Идентичность ===: Проверяет, является ли объект одним и тем же экземпляром.
class Car
{
	public string $brand;

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

$car1 = new Car('Skoda');
$car2 = new Car('Skoda');
$car3 = $car1;

var_dump($car1 == $car2);   // true, because they have the same value
var_dump($car1 === $car2);  // false, because they are not the same instance
var_dump($car1 === $car3);  // true, because $car3 is the same instance as $car1

Оператор instanceof

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

Представьте, что у нас есть класс Person и другой класс Student, который является потомком Person:

class Person
{
	private int $age;

	public function __construct(int $age)
	{
		$this->age = $age;
	}
}

class Student extends Person
{
	private string $major;

	public function __construct(int $age, string $major)
	{
		parent::__construct($age);
		$this->major = $major;
	}
}

$student = new Student(20, 'Computer Science');

// Check if $student is an instance of the Student class
var_dump($student instanceof Student);  // Output: bool(true)

// Check if $student is an instance of the Person class (because Student is a descendant of Person)
var_dump($student instanceof Person);   // Output: bool(true)

Из выводов видно, что объект $student считается экземпляром обоих классов Student и Person.

Текучие интерфейсы

Fluent Interface" – это техника в ООП, позволяющая объединять методы в цепочку одним вызовом. Это часто упрощает и уточняет код.

Ключевым элементом флюентного интерфейса является то, что каждый метод в цепочке возвращает ссылку на текущий объект. Это достигается за счет использования return $this; в конце метода. Такой стиль программирования часто ассоциируется с методами, называемыми „сеттерами“, которые устанавливают значения свойств объекта.

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

public function sendMessage()
{
	$email = new Email;
	$email->setFrom('sender@example.com')
		  ->setRecipient('admin@example.com')
		  ->setMessage('Hello, this is a message.')
		  ->send();
}

В данном примере методы setFrom(), setRecipient() и setMessage() используются для установки соответствующих значений (отправитель, получатель, содержание сообщения). После установки каждого из этих значений методы возвращают текущий объект ($email), что позволяет выстроить после него цепочку других методов. Наконец, мы вызываем метод send(), который, собственно, и отправляет письмо.

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

Копирование с помощью clone

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

Если при копировании объекта нам необходимо изменить некоторые его свойства, мы можем определить в классе специальный метод __clone(). Этот метод автоматически вызывается при клонировании объекта.

class Sheep
{
	public string $name;

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

	public function __clone()
	{
		$this->name = 'Clone of ' . $this->name;
	}
}

$original = new Sheep('Dolly');
echo $original->name . "\n";  // Outputs: Dolly

$clone = clone $original;
echo $clone->name . "\n";     // Outputs: Clone of Dolly

В данном примере мы имеем класс Sheep с одним свойством $name. Когда мы клонируем экземпляр этого класса, метод __clone() гарантирует, что имя клонированной овцы получит префикс „Clone of“.

Свойства

Трейты в PHP – это инструмент, позволяющий совместно использовать методы, свойства и константы между классами и предотвращающий дублирование кода. Их можно рассматривать как механизм „копирования и вставки“ (Ctrl-C и Ctrl-V), когда содержимое трейта „вставляется“ в классы. Это позволяет повторно использовать код без создания сложных иерархий классов.

Рассмотрим простой пример использования трейтов в PHP:

trait Honking
{
	public function honk()
	{
		echo 'Beep beep!';
	}
}

class Car
{
	use Honking;
}

class Truck
{
	use Honking;
}

$car = new Car;
$car->honk(); // Outputs 'Beep beep!'

$truck = new Truck;
$truck->honk(); // Also outputs 'Beep beep!'

В этом примере у нас есть тред Honking, содержащий один метод honk(). Затем у нас есть два класса: Car и Truck, оба из которых используют признак Honking. В результате оба класса „имеют“ метод honk(), и мы можем вызывать его на объектах обоих классов.

Трейты позволяют легко и эффективно обмениваться кодом между классами. Они не входят в иерархию наследования, т.е. $car instanceof Honking вернет false.

Исключения

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

В PHP для таких объектов существует встроенный класс Exception. Он имеет несколько методов, позволяющих получить дополнительную информацию об исключении, например, сообщение об ошибке, файл, строку, в которой произошла ошибка, и т.д.

При возникновении проблемы мы можем „выбросить“ исключение (используя throw). Если мы хотим „поймать“ и обработать это исключение, мы используем блоки try и catch.

Давайте посмотрим, как это работает:

try {
	throw new Exception('Message explaining the reason for the exception');

	// This code won't execute
	echo 'I am a message that nobody will read';

} catch (Exception $e) {
	echo 'Exception caught: '. $e->getMessage();
}

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

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

Можно также создать иерархию исключений, в которой каждый класс исключений наследуется от предыдущего. В качестве примера рассмотрим простое банковское приложение, позволяющее вносить и снимать деньги:

class BankingException extends Exception {}
class InsufficientFundsException extends BankingException {}
class ExceededLimitException extends BankingException {}

class BankAccount
{
	private int $balance = 0;
	private int $dailyLimit = 1000;

	public function deposit(int $amount): int
	{
		$this->balance += $amount;
		return $this->balance;
	}

	public function withdraw(int $amount): int
	{
		if ($amount > $this->balance) {
			throw new InsufficientFundsException('Not enough funds in the account.');
		}

		if ($amount > $this->dailyLimit) {
			throw new ExceededLimitException('Daily withdrawal limit exceeded.');
		}

		$this->balance -= $amount;
		return $this->balance;
	}
}

$account = new BankAccount;
$account->deposit(500);

try {
	$account->withdraw(1500);
} catch (ExceededLimitException $e) {
	echo $e->getMessage();
} catch (InsufficientFundsException $e) {
	echo $e->getMessage();
} catch (BankingException $e) {
	echo 'An error occurred during the operation.';
}

В этом примере важно обратить внимание на порядок следования блоков catch. Поскольку все исключения наследуются от BankingException, то если бы этот блок был первым, то все исключения были бы пойманы в нем, а код не дошел бы до последующих блоков catch. Поэтому важно, чтобы более специфические исключения (т.е. те, которые наследуются от других) располагались выше в порядке блоков catch, чем их родительские исключения.

Лучшие практики

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

  1. Разделение обязанностей: Каждый класс должен иметь четко определенную ответственность и решать только одну основную задачу. Если класс выполняет слишком много задач, то целесообразно разделить его на более мелкие специализированные классы.
  2. Инкапсуляция: Данные и методы должны быть максимально скрыты и доступны только через определенный интерфейс. Это позволяет изменять внутреннюю реализацию класса, не затрагивая остальной код.
  3. Инъекция зависимостей: Вместо того чтобы создавать зависимости непосредственно внутри класса, следует „инжектировать“ их извне. Для более глубокого понимания этого принципа мы рекомендуем главы, посвященные Dependency Injection.