Почему возникает ошибка "Доступ к типизированному свойству должен выполняться после инициализации"?

Как избежать ошибки "Fatal error: Uncaught Error: Typed property Product::$parentId must not be accessed before initialization" и улучшить структуру кода для инициализации свойств в PHP?
  • 16 августа 2024 г. 8:43
Ответы на вопрос 4
Ошибка "Доступ к типизированному свойству должен выполняться после инициализации" возникает тогда, когда вы пытаетесь обратиться к типизированному свойству объекта до его явной инициализации. 

Чтобы избежать этой ошибки и улучшить структуру кода для инициализации свойств в PHP, рекомендуется присваивать значения свойствам объекта сразу после его создания, как показано в примере ниже:

```php
class Product {
    public int $id;
    public string $name;
    public int $parentId = 0; // явная инициализация
}

$product = new Product();
$product->id = 1;
$product->name = "Product 1";

echo $product->parentId; // выведет 0
```

В этом примере свойство `$parentId` инициализируется явно значением по умолчанию после объявления свойства. Таким образом, при создании объекта `Product`, все его свойства будут иметь значения по умолчанию, и доступ к ним будет возможен без ошибок.
Используйте Constructor property promotion чтобы писать меньше букв) <br/> <pre><code class="php">&lt;?php

class Product
{
    public function __construct(
        public int $id,
        public ?int $parentId = null
    ) {
    }

    public function getParentId(): ?int
    {
        return $this-&gt;parentId;
    }

}

$obj = new Product(1);
if (!empty($obj-&gt;getParentId())) {
    echo 'ok';
}</code></pre> <br/> <br/> Либо уберите типизацию у свойства и при <code>public $parentId;</code> ошибки не будет. Всё как и написано в тексте ошибки, нельзя обратиться к <b>типизированному</b> свойству до его инициализации, а значит нужно либо задать значение по умолчанию при описании свойства, либо через конструктор, либо вызвав setter. <br/> <br/> uninitialized и typed properties <br/> <a href="https://php.watch/versions/7.4/typed-properties#uninitialized" rel="nofollow">https://php.watch/versions/7.4/typed-properties#un...</a>
<blockquote>1. Разве по умолчанию свойства не null?<br/>
2. У меня 20-30 свойств. Для каждого что-ли делать так? Код какой-то не логичный и громоздкий.</blockquote> <br/> 1. Нет, в данном случае у свойства нет значения, оно не инициализировано. Какое значение вы хотите получить из свойства, если у свойства нет значения? То, что php в некоторых случаях при отсутствии типов выполняет неявное приведение к null не распространяется на ситуации, когда вы явно указали типы, а значит хотите явного поведения. <br/> А вот Null - это уже значение, которое используется для передачи отсутствия значения как значение. <br/> <br/> 2. Если заботитесь о качестве кода, то да. Все абсолютно логично, php подсказывает вам, что вы вероятно забыли инициализировать переменную, т.е. допустили ошибку, обратившись к переменной раньше, чем положили в нее значение. <br/> Здесь нет ничего "громоздкого", просто вы явно описываете поведение. Можете этого, конечно, не делать, например не использовать указание типов и расчитывать на то, что php сам что-нибудь подставит - но, очевидно, это ненадежный путь, ведущий к ошибкам. <br/> В идеале, вы должны задать свойству корректное значение, а затем его использовать. Т.е. использовать 1 единственный тип. Не всегда логика это позволяет и тогда нужно инициализировать переменную значением null. <br/> <br/> Хороший код тот, который очевиден и понятен с первого взгляда, а не в котором мало букв. Указывая типы вы всегда знаете что за тип лежит в конкретной переменной и вам не нужно продумывать обработку других типов. Даже когда вы пишете ?int, значит кроме целого числа там еще и null может быть, и это придется учитывать при всех манипуляцих с этой переменной. И вот это, и будет делать код более сложным. А если вы не сделаете обработку всех доступных типов, тогда поведение кода может стать непредсказуемым.
1. Нет, по умолчанию типизированное свойство не инициализировано <br/> <pre><code class="php">class Product
{
    private int $parentId;

    private $nonTyped;

    public function __construct(
        private readonly int $id
    ) {
    }
}

$obj = new Product(1);
var_dump($obj);

// class \Product#2 (3) {
//   private int $parentId =&gt; *uninitialized*
//   private $nonTyped =&gt; NULL
//   private readonly int $id =&gt; int(1)
// }</code></pre> <br/> <br/> 2. Если это свойство необязательное, то вариантов три: <br/> - не обращаться к свойству до его инициализации; <br/> - инициализировать свойство значением по умолчанию при определении; <br/> - делать коалесценцию в геттере, возвращая значение по умолчанию ( <code class="php">return $this-&gt;parentId ?? null;</code> ).
Похожие вопросы