What’s new in PHP 8.4
- Release: https://www.php.net/releases/8.4/ru.php
- Wiki: PHP 8.3
- github: PHP 8.3 changelog
- https://habr.com/ru/news/776250/
public $foo { set () } — Property hooks
Doc: https://www.php.net/manual/ru/migration84.new-features.php
Wiki: https://wiki.php.net/rfc/property-hooks
Property hooks allow you to add logic when reading/writing (get/set) a property — without creating separate methods getFoo() and setFoo() (getters and setters).
Before:
class User {
private string $name;
public function setName( string $name ): void {
if ( $name === '' ) throw new ValueError('Empty');
$this->name = $name;
}
public function getName(): string {
return ucfirst( $this->name );
}
}
Now:
class User {
public string $name {
set {
if ( $value === '' ) throw new ValueError('Empty');
$this->name = $value;
}
get => ucfirst( $this->name );
}
public function __construct(string $name) {
$this->name = $name;
}
}
Now you can write $user->name = 'Tim'; and when setting the value, the set function will be called:
$user = new User(); $user->name = 'tim'; // goes through the set hook echo $user->name; // Tim
Shorthand get => expr; supported:
public string $fullName { get => $this->first . ' ' . $this->last; }
Interfaces with get and set properties:
Previously interfaces could require only methods, and now they can require properties — readable (get) and/or writable (set).
That is, you can now say:
“Any class implementing this interface must have
$namethat can be read (or written)”
Example:
interface Named {
public string $name { get; } // required readable property
}
class User implements Named {
public string $name = 'Tim'; // fits, get exists by default
}
class Person implements Named {
public string $name {
get => strtoupper( $this->realName ); // fits, get hook exists
}
}
This brings interfaces closer to “data contracts” rather than just method sets.
Makes properties “smart” without extra magic __get/__set:
public string $slug {
set {
$this->slug = strtolower( trim( $value ) );
}
}
// No magic and extra code
In summary: Property hooks provide support for computed properties that can be understood by IDEs and static analysis tools, without needing to write DocBlock comments that may not match. They also allow reliable pre- or post-processing of values without checking whether the corresponding getter or setter exists in the class.
public private(set) string $ver — Asymmetric property visibility
Doc: https://www.php.net/manual/ru/language.oop5.visibility.php#language.oop5.visibility-members-aviz
Wiki: https://wiki.php.net/rfc/asymmetric-visibility-v2
In PHP 8.4 you can set an asymmetric visibility scope for properties — separately for reading and writing.
set-visibility is specified as private(set) or protected(set) immediately after the visibility modifier.
class Book {
public function __construct(
public private(set) string $title,
public protected(set) string $author,
protected private(set) int $year,
) {}
}
class SpecialBook extends Book {
public function update( string $author, int $year ): void {
$this->author = $author; // All good
$this->year = $year; // Critical error
}
}
$b = new Book( 'PHP', 'Peter', 2024 );
echo $b->title; // OK
echo $b->author; // OK
echo $b->pubYear; // Fatal
$b->title = 'How not to PHP'; // Fatal
$b->author = 'Pedro H. Peterson'; // Fatal
$b->pubYear = 2023; // Fatal
Features:
-
Get always has the same or broader scope than set.
In other words, the restriction for set must be equal to or stricter than for get:public protected(set) // OK protected public(set) // error
-
private(set)makes the property final - it cannot be overridden or re-declared in a descendant class. -
Works only with typed properties - can be used only on properties with a type - e.g., string, int, array, etc.:
public private(set) string $title; // OK public private(set) $title; // fatal
-
Getting a reference to a property follows the set visibility, not get. This is because the reference allows modifying the property value.
Similarly, an attempt to write into an array contained in a property involves both an internal read - get, and a write - set, and therefore follows the set visibility, since the write visibility restriction is stronger.
- Spaces are not allowed:
private( set )- a space will cause an error.
Inheritance
When inheriting you can loosen read or write access, but private(set) is final and cannot be overridden:
class Book {
protected string $title;
public protected(set) string $author;
protected private(set) int $year; // final
}
class SpecialBook extends Book {
// OK - read restriction is looser, and write restriction is the same
public protected(set) string $title;
// OK - read restriction is the same, and write is looser
public string $author;
// Fatal Error - properties with private(set) are final!
public protected(set) int $year;
}
new MyClass()->method() — without parentheses
Doc: https://www.php.net/manual/ru/migration84.new-features.php#migration84.new-features.core.new-chaining
Wiki: https://wiki.php.net/rfc/new_without_parentheses
Properties and methods of a newly initialized object can now be accessed without wrapping the new expression in parentheses.
// Was: $ver = ( new PhpVersion() )->getVersion(); // Now: $ver = new PhpVersion()->getVersion();
#[\Deprecated] — Attribute
Doc: https://www.php.net/manual/ru/class.deprecated.php
Wiki: https://wiki.php.net/rfc/deprecated_attribute
The attribute #[\Deprecated] allows declaring functions, methods, constants, and enum cases as deprecated.
When such (deprecated) elements are called, a E_USER_DEPRECATED warning will be issued.
You can specify a message $message and a version $since, which will appear in the warning text. For example:
#[\Deprecated('Use newMethod() instead', since: '2.4')]
function oldMethod() {}
oldMethod(); // Deprecated: Function oldMethod() is deprecated since 2.4, Use newMethod() instead
More usage examples:
#[\Deprecated]
function test() {
}
#[\Deprecated("use test() instead")]
function test2() {
}
#[\Deprecated("use test() instead", since: "2.4")]
function test3() {
}
#[\Deprecated(since: "2024-05-07")]
function test4() {
}
class Clazz {
#[\Deprecated]
public const OLD_WAY = 'foo';
#[\Deprecated]
function test() {
}
#[\Deprecated("use test() instead")]
function test2() {
}
}
enum MyEnum {
#[\Deprecated]
case OldCase;
}
Reflection
Method isDeprecated() of ReflectionFunctionAbstract and ReflectionClassConstant will return true:
#[\Deprecated]
function test() {}
$r = new ReflectionFunction('test');
var_dump( $r->isDeprecated() ); // bool(true)
class Clazz {
#[\Deprecated]
public const OLD_WAY = 'foo';
}
$r = new ReflectionClassConstant( Clazz::class, 'OLD_WAY' );
var_dump( $r->isDeprecated() ); // bool(true)
The attribute #[\Deprecated] in stub files will work the same as the documentation comment /** @deprecated */.
- Old functions with such a comment will not automatically receive the attribute.
- It is recommended for extension authors to add
#[\Deprecated]manually for consistency.
In the global namespace you cannot declare a class named Deprecated.
—