sBdWfyoDeSVyCc0PFXhTGXgrp1o9bLn5FvEb7c4u.webp

От класса User к new User() — понимаем объекты в PHP без привычного ООП-жаргона

Если вы уже какое-то время пишете на PHP, вы, вероятно, писали эту строку чаще, чем можете вспомнить:

$user = new User();

Она выглядит невероятно просто.

У нас есть класс с именем User, мы используем ключевое слово new, и теперь у нас есть объект $user.

Готово.

Но вот что интересно.

 Что на самом деле произошло, когда PHP выполнил эту строку?

Откуда взялся этот объект?

Где хранится его имя?

Создает ли PHP полную копию класса каждый раз, когда мы используем new?

Если мы создадим 100 объектов User, получим ли мы также 100 копий метода sayHello()?

И, пожалуй, самый запутанный вопрос для новичков:

 Что именно представляет собой  $this?

Мы используем его повсюду:

$this->name

Но на что на самом деле указывает $this?

Эти вопросы касаются не только синтаксиса PHP.

Они о понимании того, чем на самом деле является объект.

Поэтому в этой статье давайте не будем спешить и построим ментальную модель с самого нуля.

Никакого Laravel.

Никакой магии фреймворков.

Никаких сложных внутренних механизмов движка.

Только  чистый PHP, классы, объекты, свойства, методы и то, что происходит при создании объекта.

Давайте начнем с класса

Рассмотрим очень простой класс:

class User
{
  public string $name;

  public function sayHello(): string
      {
          return "Hello, {$this->name}";
      }
}

На первый взгляд мы могли бы сказать:

«Мы создали пользователя (User)».

Но технически произошло не это.

Мы создали  определение класса.

Это очень важное различие.

Класс говорит PHP:

«Если кто-то создаст объект User, у этого объекта может быть name, и он сможет выполнять операцию sayHello()».

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

Но самого пользователя пока не существует.

Мы еще не создали:

Siam
Rahim
Karim

или кого-то еще.

Мы лишь описали, как должен выглядеть User и что он умеет делать.

Вы можете думать об этом так:

User Class
        ┌────────────────────┐
        │ name               │
        │ sayHello()         │
        └────────────────────┘

Это определение. Это план. Это описание.

 Это еще не объект.

Затем мы пишем new

Теперь мы пишем:

$user = new User();

Это момент, когда все становится интересным.

Ключевое слово new говорит PHP:

«Мне нужен реальный объект на основе класса User».

Концептуально PHP теперь создает экземпляр этого класса.

Мы можем представить это так:

User class
    │
    │ new User()
    ▼
User object

И теперь у нас есть реальный объект, который может хранить свое собственное состояние.

Например:

$user->name = "Siam";

Теперь у нашего объекта есть состояние:

User Object
┌─────────────────┐
│ name = "Siam"   │
└─────────────────┘

Это одно из самых больших различий между  классом и  объектом.

Класс описывает.

Объект существует.

Так что же такое объект?

Давайте сделаем это еще проще.

Предположим, наш класс выглядит так:

class User
{
    public string $name;
}

И мы создаем:

$user1 = new User();
$user2 = new User();

Теперь у нас есть два разных объекта.

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

Мы можем сделать:

$user1->name = "Siam";
$user2->name = "Rahim";

Теперь:

$user1
┌─────────────────┐
│ name = "Siam"   │
└─────────────────┘

$user2
┌─────────────────┐
│ name = "Rahim"  │
└─────────────────┘

Оба они являются объектами User. Но у них нет общего значения name.

Изменение одного не меняет автоматически другой.

Это потому, что  свойства экземпляра принадлежат конкретному объекту.

Класс определяет свойство, объект хранит значение

Это различие стоит понять как следует.

Когда мы пишем:

class User
{
    public string $name;
}

мы определяем, что объект User может иметь свойство с именем name.

Мы не говорим:

«У каждого User имя Siam».

Мы говорим:

«Каждый объект User имеет свое собственное свойство name».

Поэтому, когда мы создаем:

$user1 = new User();
$user2 = new User();

каждый объект имеет свое собственное состояние экземпляра.

Затем:

$user1->name = "Siam";
$user2->name = "Rahim";

дает каждому объекту свое значение.

Вот почему объекты полезны.

Один и тот же класс может описывать тысячи объектов, в то время как каждый объект может сохранять свое собственное состояние.

А как насчет методов?

Теперь давайте поговорим о другой части нашего класса:

public function sayHello(): string
{
    return "Hello, {$this->name}";
}

Метод описывает  поведение.

Свойство представляет то, что объект знает.

Метод представляет то, что объект может делать.

Вы можете начать думать об объекте так:

Object
│
├── State
│   └── name
│
└── Behavior
    └── sayHello()

Это очень полезная ментальная модель.

Объект объединяет:

 данные + поведение

или, в более привычных терминах PHP:

 свойства + методы

Но получает ли каждый объект копию метода?

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

Предположим, мы создаем:

$user1 = new User();
$user2 = new User();
$user3 = new User();

Новичок может представить себе что-то вроде:

$user1
├── name
└── sayHello() code

$user2
├── name
└── sayHello() code

$user3
├── name
└── sayHello() code

Другими словами:

«PHP создает три объекта, поэтому PHP должен скопировать метод три раза».

Это не лучшая ментальная модель.

Определение метода принадлежит классу.

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

Более правильная ментальная картина:

User Class
        ┌────────────────────────┐
        │ name                   │
        │                        │
        │ sayHello()             │
        │                        │
        │ method definition      │
        └───────────┬────────────┘
                    │
          ┌─────────┼─────────┐
          │         │         │
          ▼         ▼         ▼

        Object    Object    Object
         #1        #2        #3
         name      name      name
         = Siam    = Rahim   = Karim

Важная идея заключается в следующем:

 Объекты имеют свое собственное состояние; методы определяются классом.

Когда вы вызываете:

$user1->sayHello();

PHP знает, что $user1 — это объект User, поэтому он знает, какое поведение класса использовать.

Тогда что такое $this?

Теперь мы подошли к одной из самых важных концепций в ООП на PHP.

Посмотрите на этот метод еще раз:

public function sayHello(): string
{
    return "Hello, {$this->name}";
}

Что такое $this?

Проще говоря:

 $this ссылается на текущий объект, метод которого выполняется в данный момент.

Давайте посмотрим, почему это важно.

Предположим:

$user1 = new User();
$user1->name = "Siam";

Тогда:

$user1->sayHello();

Внутри sayHello() $this ссылается на $user1.

Значит:

$this->name

означает:

$user1->name

Концептуально.

Теперь предположим:

$user2 = new User();
$user2->name = "Rahim";

И мы вызываем:

$user2->sayHello();

Теперь $this ссылается на $user2.

Таким образом, один и тот же метод:

return "Hello, {$this->name}";

может давать разные результаты.

Для $user1:

$this → $user1
$this->name → "Siam"

Для $user2:

$this → $user2
$this->name → "Rahim"

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

 Метод один и тот же, но объект, предоставляющий состояние, отличается.

Один и тот же метод может вести себя по-разному

Давайте соберем все вместе:

class User
{
  public string $name;
  
  public function sayHello(): string
  {
    return "Hello, {$this->name}";
  }
}

Теперь:

$user1 = new User();
$user1->name = "Siam";

$user2 = new User();
$user2->name = "Rahim";

echo $user1->sayHello();
echo $user2->sayHello();

Определение метода одинаковое.

Но результат разный.

Почему?

Потому что $this отличается.

Для первого вызова:

$user1->sayHello();

$this означает:

User object #1

Для второго вызова:

$user2->sayHello();

$this означает:

User object #2

Таким образом, вы можете мысленно перевести:

$this->name

в:

«Дай мне name, принадлежащее объекту, который в данный момент выполняет этот метод».

Это гораздо более полезный способ понять $this, чем просто заучивание:

«$this означает текущий объект».

Что происходит, когда существует конструктор?

Теперь давайте сделаем создание объекта чуть более реалистичным.

class User
{
    public string $name;
    
    public function __construct(string $name)
    {
        $this->name = $name;
    }
    public function sayHello(): string
    {
        return "Hello, {$this->name}";
    }
}

Теперь мы можем написать:

$user = new User("Siam");

Обратите внимание, что мы передаем "Siam".

Что происходит?

Концептуально:

new User("Siam")
       │
       ▼
Create User object
       │
       ▼
Run __construct("Siam")
       │
       ▼
$this->name = "Siam"
       │
       ▼
Object is initialized

После этого у нас есть:

User Object
┌─────────────────┐
│ name = "Siam"   │
└─────────────────┘

Таким образом, конструктор — это не то, что волшебным образом создает сам класс.

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

Это различие важно.

Создание и инициализация — это не одно и то же

Это еще одна концепция, которая становится яснее, если не торопиться.

Рассмотрим:

$user = new User("Siam");

Концептуально происходят две вещи:

1. Создание

PHP создает экземпляр User.

2. Инициализация

Конструктор получает "Siam" и использует его для инициализации состояния объекта.

Значит:

new User("Siam")

можно представить как:

Create object
     ↓
Initialize object
     ↓
Give object back

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

Что именно представляет собой $user?

Теперь давайте посмотрим на это:

$user = new User("Siam");

Частый вопрос новичков:

«Является ли $user объектом?»

Для повседневного программирования на PHP мы обычно говорим:

«$user — это переменная, содержащая ссылку на объект».

Это более полезная ментальная модель.

Думайте об этом так:

$user
  │
  │ refers to
  ▼
┌─────────────────┐
│ User Object     │
│                 │
│ name = "Siam"   │
└─────────────────┘

Таким образом, $user — это не класс.

И это не просто копия содержимого объекта.

Она дает вашему PHP-коду способ получить доступ к этому объекту.

Вот почему это работает:

$user->name

и:

$user->sayHello();

Что происходит, когда мы присваиваем одну переменную объекта другой?

Вот здесь все может стать немного неожиданным.

Предположим:

$user1 = new User("Siam");
$user2 = $user1;

Новичок может ожидать, что PHP создаст совершенно новый объект.

Но происходит не это.

Обе переменные теперь ссылаются на один и тот же объект.

Концептуально:

$user1 ───────┐
              │
              ▼
        ┌───────────────┐
        │ User Object   │
        │ name = Siam   │
        └───────────────┘
              ▲
              │
$user2 ───────┘

Значит:

$user2->name = "Rahim";

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

Например:

$user1 = new User("Siam");
$user2 = $user1;

$user2->name = "Rahim";
echo $user1->name;

Вывод будет следующим:

Rahim

Это чрезвычайно важная концепция при работе с объектами в PHP.

Что если мы действительно хотим получить другой объект?

Тогда мы можем использовать clone.

Например:

$user1 = new User("Siam");
$user2 = clone $user1;

Теперь PHP создает другой объект на основе текущего состояния первого объекта.

Концептуально:

$user1
   │
   ▼
┌─────────────────┐
│ User Object #1  │
│ name = "Siam"   │
└─────────────────┘
$user2
   │
   ▼
┌─────────────────┐
│ User Object #2  │
│ name = "Siam"   │
└─────────────────┘

Теперь это отдельные объекты.

Значит:

$user2->name = "Rahim";

не меняет:

$user1->name

Разница в следующем:

$user2 = $user1;

означает:

«Обе переменные ссылаются на один и тот же объект».

В то время как:

$user2 = clone $user1;

означает:

«Создать другой объект на основе состояния этого объекта».

Объекты могут содержать другие объекты

Объект не обязательно должен содержать только строки, целые числа или булевы значения.

Объект может иметь другой объект в качестве свойства.

Например:

class Address
{
    public string $city;
    
    public function __construct(string $city)
    {
        $this->city = $city;
    }
}

И:

class User
{
    public string $name;
    public Address $address;
    
    public function __construct(
        string $name,
        Address $address
    ) {
        $this->name = $name;
        $this->address = $address;
    }
}

Теперь:

$address = new Address("Dhaka");
$user = new User("Siam", $address);

Связь наших объектов теперь выглядит примерно так:

User Object
┌─────────────────────────┐
│ name = "Siam"           │
│                         │
│ address ────────────────┼──────┐
└─────────────────────────┘      │
                                 ▼
                         Address Object
                         ┌────────────────┐
                         │ city = Dhaka   │
                         └────────────────┘

Это очень важная часть объектно-ориентированного проектирования.

Объекты могут работать вместе.

Одному объекту не нужно знать все.

Он может хранить или работать с другим объектом, у которого есть своя зона ответственности.

Но даже здесь сохраняется та же фундаментальная идея:

 Каждый объект — это реальный экземпляр со своим собственным состоянием.

Свойства и методы: состояние против поведения

На данном этапе мы можем сделать очень полезное различие.

Рассмотрим:

class User
{
    public string $name;

    public function sayHello(): string
    {
        return "Hello, {$this->name}";
    }
}

Свойство:

$name

представляет  состояние.

Метод:

sayHello()

представляет  поведение.

Значит:

User
│
├── State
│   └── name = "Siam"
│
└── Behavior
    └── sayHello()

Это один из самых простых способов понять объект.

Объект — это не просто «переменная с какими-то данными».

Он представляет собой нечто, у чего есть:

  • некоторое состояние
  • некоторое поведение
  • и правила о том, как это состояние может быть изменено или использовано

В этом суть объектно-ориентированного программирования.

А как насчет статических свойств?

Теперь нам нужно сделать одно важное различие.

Не каждое свойство принадлежит конкретному объекту.

Рассмотрим:

class User
{
    public string $name;
    public static int $count = 0;
}

Здесь:

$name

— это свойство экземпляра.

Оно принадлежит каждому объекту.

Но:

$count

является статическим.

Оно принадлежит состоянию на уровне класса, а не каждому отдельному объекту.

Значит:

$user1->name

относится к конкретному объекту.

В то время как:

User::$count

относится к статическому свойству на уровне класса.

Простая ментальная модель выглядит так:

Instance property
        ↓
belongs to an object

Static property
        ↓
belongs to the class

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

И статические методы тоже отличаются

Рассмотрим:

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

Мы можем вызвать:

$result = MathHelper::add(10, 20);

Заметили кое-что?

Мы не создавали объект.

Здесь нет:

$helper = new MathHelper();

И внутри этого статического метода нет $this.

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

Так что это:

$user->sayHello();

— поведение экземпляра.

В то время как это:

MathHelper::add(10, 20);

— поведение на уровне класса.

Это дает нам еще одно полезное ментальное разделение:

Object-oriented instance world
        ↓
$this
properties
object state
instance methods

Class-level static world
        ↓
ClassName::
static properties
static methods

Что происходит при наследовании?

Теперь давайте сделаем еще один шаг вперед.

Предположим, у нас есть:

class User
{
    public string $name;

    public function sayHello(): string
    {
        return "Hello, {$this->name}";
    }
}

И:

class Admin extends User
{
    public function deleteUser(): string
    {
        return "User deleted";
    }
}

Теперь:

$admin = new Admin();

Что такое $admin?

Это объект класса Admin.

Но поскольку Admin наследует User, он также имеет доступ к унаследованному поведению и свойствам, определенным в User, в соответствии с правилами видимости и наследования PHP.

Концептуально:

User
│
├── name
└── sayHello()
        │
        │ inherited by
        ▼
Admin
│
└── deleteUser()

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

Но опять же, когда мы пишем:

$admin = new Admin();

мы создаем  реальный объект Admin.

Иерархия классов указывает PHP, что может иметь этот объект и какое поведение ему доступно.

А как насчет интерфейсов?

Интерфейсы — еще одно место, где новички иногда путаются.

Рассмотрим:

interface PaymentGateway
{
    public function pay(float $amount): bool;
}

Это не создает объект.

Это даже не предоставляет реальную реализацию pay().

Вместо этого это определяет контракт.

Класс может реализовать его:

class BkashGateway implements PaymentGateway
{
    public function pay(float $amount): bool
    {
        return true;
    }
}

Затем:

$gateway = new BkashGateway();

Теперь объект создан.

Интерфейс помог определить, какое поведение должен предоставить класс.

Таким образом, мы можем различать:

Class
    → defines implementationObject
    → actual instanceInterface
    → defines a contract

Разделение этих трех идей делает ООП в PHP гораздо более простым для понимания.

Что происходит с объектом позже?

Объект не обязательно живет вечно.

В какой-то момент PHP он больше не нужен.

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

Вы также можете определить деструктор:

class User
{
    public function __destruct()
    {
        echo "Object is being destroyed";
    }
}

Деструктор вызывается, когда PHP уничтожает объект.

Однако важно не думать о __destruct() как о точной ручной команде управления памятью.

PHP сам управляет временем жизни объектов и памятью за вас.

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

Таким образом, жизненный цикл выглядит примерно так:

Class definition
      ↓
new User()
      ↓
Object created
      ↓
Constructor runs
      ↓
Object is used
      ↓
Object becomes unreachable
      ↓
PHP can destroy it
      ↓
Destructor may run

Так что же на самом деле означает «создание объекта»?

Теперь давайте вернемся к нашей исходной строке:

$user = new User("Siam");

Наконец-то мы можем описать ее более точно.

PHP видит:

new User("Siam")

и концептуально выполняет следующую последовательность:

1. Identify the User class
          ↓
2. Create a new User instance
          ↓
3. Prepare the object's instance state
          ↓
4. Run the constructor
          ↓
5. Initialize the object
          ↓
6. Return access to the object
          ↓
7. Store that reference in $user

Так что после:

$user = new User("Siam");

мы можем представить результат как:

User Class
        ┌─────────────────────────┐
        │ name                    │
        │ sayHello()              │
        └────────────┬────────────┘
                     │
                     │ creates
                     ▼
              User Object
        ┌─────────────────────────┐
        │ name = "Siam"           │
        └────────────┬────────────┘
                     ▲
                     │
                     │ refers to
                     │
                   $user

Это та ментальная модель, которая нам нужна.

Еще одна важная вещь: класс не «выполняется»

Иногда мы небрежно говорим:

«PHP запускает класс».

На самом деле это не лучший способ думать об этом.

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

Когда вы пишите:

class User
{
    ...
}

вы определяете класс.

Когда вы пишите:

$user = new User();

вы создаете объект.

И когда вы пишите:

$user->sayHello();

вы просите этот объект выполнить один из его методов.

Таким образом, это три разных момента:

class User { ... }
        ↓
Define the classnew User()
        ↓
Create an object$user->sayHello()
        ↓
Execute behavior on that object

Разделение этих трех вещей устраняет большую часть путаницы в ООП.

Самая полезная ментальная модель

Если вы забудете все остальное из этой статьи, запомните следующее:

CLASS
        ┌─────────────────────┐
        │ Defines             │
        │                     │
        │ Properties          │
        │ Methods             │
        │ Rules               │
        └──────────┬──────────┘
                   │
              new User()
                   │
          ┌────────┼────────┐
          ▼        ▼        ▼
        Object   Object   Object
         #1       #2       #3
        state    state    state
         ↓        ↓        ↓
         Siam     Rahim    Karim

Класс — это определение.

new создает экземпляр.

Каждый объект имеет свое собственное состояние экземпляра.

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

И $this говорит методу:

 «С каким объектом я сейчас работаю?»

Настоящая прелесть $this

Давайте вернемся к нашему крошечному примеру в последний раз:

class User
{
    public string $name;

    public function sayHello(): string
    {
        return "Hello, {$this->name}";
    }
}

Теперь создадим два объекта:

$user1 = new User();
$user1->name = "Siam";

$user2 = new User();
$user2->name = "Rahim";

Затем:

$user1->sayHello();

Внутри метода:

$this

означает:

$user1

И когда:

$user2->sayHello();

выполняется:

$this

означает:

$user2

Тот же класс.

Тот же метод.

Другой объект.

Другое состояние.

Другой результат.

В этом и смысл.

Методу не нужно знать, является ли пользователь Siam, Rahim или Karim.

Он просто работает с:

$this

— объектом, который в данный момент вызывает метод.

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

Итоговая ментальная картина

Когда вы видите:

$user = new User("Siam");

не читайте это просто как:

«Создать пользователя».

Попробуйте визуализировать весь процесс:

User
                 Class Definition
             ┌─────────────────────┐
             │ name                │
             │ sayHello()          │
             └──────────┬──────────┘
                        │
                        │ new User("Siam")
                        ▼
                ┌──────────────────┐
                │   User Object     │
                │                  │
                │ name = "Siam"    │
                └────────┬─────────┘
                         ▲
                         │
                         │ reference
                         │
                       $user

Затем, когда вы пишете:

$user->sayHello();

думайте:

$user
  ↓
User Object
  ↓
sayHello()
  ↓
$this = current User Object
  ↓
$this->name
  ↓
"Siam"

Как только эта картина прояснится, синтаксис ООП в PHP перестанет казаться загадочным.

Вы больше не будете видеть:

$this->name

как какой-то странный элемент синтаксиса PHP.

Вы будете видеть это как:

 «Имя ( name), принадлежащее объекту, который в данный момент выполняет этот метод».

И вы больше не будете видеть:

new User()

просто как строку кода, которую вы зазубрили.

Вы будете видеть это как:

 «Создать реальный экземпляр класса  User, инициализировать его и дать мне доступ к этому объекту».

Это ментальная модель, которая делает понимание остальной части ООП в PHP гораздо более простым.

Последний вывод

Объектно-ориентированное программирование становится намного проще, когда вы перестаете думать только в рамках синтаксиса.

Вместо заучивания:

class
new
$this
extends
implements
clone

постарайтесь увидеть связи между ними:

CLASS
  │
  │ describes
  ▼
OBJECT
  │
  ├── has STATE
  │      └── properties
  │
  └── has BEHAVIOR
         └── methods
                │
                └── $this
                     ↓
               current object

Класс говорит PHP,  чем объект может быть.

Объект — это  реальный экземпляр.

Свойства представляют  состояние объекта.

Методы представляют его  поведение.

new создает объект.

Конструктор инициализирует его.

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

Как только вы искренне поймете эту взаимосвязь, ООП в PHP перестанет казаться набором ключевых слов.

Оно начнет выглядеть как система.

И вот тогда все становится по-настоящему интересным.