PHP Development. Symfony Console Component – Tips & Tricks
This article was created with the aim to show you the most useful and retrieving tips and tricks about Symfony Console Development.
The new version of PHP is just around the corner. What are the new implementations you should know about? Check this article to find out!
PHP 8.2 is about to be released. Perhaps it will be the version that makes an upgrade into PHP 8 seem appealing to everyone. Let’s talk about what developers can look forward to with PHP 8.2 and prepare for its latest version. But first, let’s quickly go through all the PHP versions and changes over the years.
<?php
class User {
public int $id;
public string $name;
}
?>
<?php
$factor = 10;
$nums = array_map(fn($n) => $n * $factor, [1, 2, 3, 4]);
// $nums = array(10, 20, 30, 40);
?>
<?php
class A {}
class B extends A {}
class Producer {
public function method(): A {}
}
class ChildProducer extends Producer {
public function method(): B {}
}
?>
<?php
$array['key'] ??= computeDefault();
// is roughly equivalent to
if (!isset($array['key'])) {
$array['key'] = computeDefault();
}
?>
<?php
$parts = ['apple', 'pear'];
$fruits = ['banana', 'orange', ...$parts, 'watermelon'];
// ['banana', 'orange', 'apple', 'pear', 'watermelon'];
?>
<?php
// Returns array containing all the necessary state of the object.
public function __serialize(): array;
// Restores the object state from the given data array.
public function __unserialize(array $data): void;
?>
Released in November 2020, PHP 8.0 brought us the best features yet, which includes:
htmlspecialchars($string, double_encode: false);
class PostsController
{
#[Route("/api/posts/{id}", methods: ["GET"])]
public function get($id) { /* ... */ }
}
class Point {
public function __construct(
public float $x = 0.0,
public float $y = 0.0,
public float $z = 0.0,
) {}
}
class Number {
public function __construct(
private int|float $number
) {}
}
new Number('NaN'); // TypeError
echo match (8.0) {
'8.0' => "Oh no!",
8.0 => "This is what I expected",
};
//> This is what I expected
$country = $session?->user?->getAddress()?->country;
enum Status
{
case Draft;
case Published;
case Archived;
}
function acceptStatus(Status $status) {...}
class BlogData
{
public readonly Status $status;
public function __construct(Status $status)
{
$this->status = $status;
}
}
$foo = $this->foo(...);
$fn = strlen(...);
class Service
{
private Logger $logger;
public function __construct(
Logger $logger = new NullLogger(),
) {
$this->logger = $logger;
}
}
function count_and_iterate(Iterator&Countable $value) {
foreach ($value as $val) {
echo $val;
}
count($value);
}
$response = $httpClient->request('https://example.com/');
print json_decode($response->getBody()->buffer())['code'];
PHP 8.2 introduces further changes aimed at making the developer’s life easier and optimizing his work even more. Below is the list of new features.
One of the biggest improvements in the new version of PHP is the ability to directly create a readonly
class. A class described with this feature will automatically propagate it for its variables. DTO classes will now look neat and clean!
readonly class InvoiceDTO
{
public function __construct(
public UUID $uuid,
public Issuer $issuer,
public DateTime $issuedAt,
) {}
}
The second huge change is the deprecation of dynamic variables in classes. The following implementation will throw a deprecation in PHP 8.2 and ErrorException
in future version of PHP.
class MyUser
{
public string $name;
}
(...)
$myUser->name = 'Name'; // OK
$myUser->surname = 'Surname'; // deprecated / errorexception
It is worth mentioning that classes implementing __get
and __set
methods and classes directly inheriting from stdClass
can still implement magic methods without any obstacles.
Here I also refer you to an interesting thread on GitHub, where PHP-CS developers discuss this change and the need to modify their popular tool for the new version of the language.
Last but not least, you can disable this behavior via Annotation.
#[AllowDynamicProperties]
class MyUser
{
public string $name;
}
$myUser->surname = 'Surname'; // OK
null
, true
, and false
Until now, functions that always returned a true
or false
value had to be described with a bool
type.
function alwaysTrue(): bool { return true; }
From now on, we can use true
and false
as simple types in the returned values of functions.
function alwaysTrue(): true { return true; }
(DNF) is a standard way of organizing boolean expressions. Specifically, it means structuring a boolean expression into an ORed series of ANDs. When applied to type declarations, it allows for a standard way to write combined Union and Intersection types that the parser can handle.
It’s a big change, as we now can have nullable intersection types, for example:
function getFullName((HasName&HasSurname)|null $user) { ... }
I’m not a big proponent of using Traits and such a change is purely cosmetic to me, especially since it doesn’t allow you to use Trait values without initializing the object.
trait Foo {
public const FLAG_1 = 1;
protected const FLAG_2 = 2;
private const FLAG_3 = 2;
public function doFoo(int $flags): void {
if ($flags & self::FLAG_1) {
echo 'Got flag 1';
}
if ($flags & self::FLAG_2) {
echo 'Got flag 2';
}
if ($flags & self::FLAG_3) {
echo 'Got flag 3';
}
}
}
One of the most important changes I am looking forward to. In the latest version of PHP we will be able to mark variables as SensitiveParameterValue
. Why should we?
PHP’s stack traces in exceptions are very useful for debugging, however, they allow you to preview parameter values. For example, let’s imagine PDO code used to connect to a database. The debug trace would look as follows:
PDOException: SQLSTATE[HY000] [2002] No such file or directory in /var/www/html/test.php:3
Stack trace:
#0 /var/www/html/test.php(3): PDO->__construct('mysql:host=loca...', 'root', 'password')
#1 {main}
After using Annotation #[SensitiveParameter]
our stack trace will no longer show the value of the variable.
function test(
$foo,
#[SensitiveParameter] $bar,
$baz
) {
throw new Exception('Error');
}
test('foo', 'bar', 'baz');
/*
Fatal error: Uncaught Exception: Error in test.php:8
Stack trace:
#0 test.php(11): test('foo', Object(SensitiveParameterValue), 'baz')
#1 {main}
thrown in test.php on line 8
*/
As author says
, the primary motivation for this change is to allow fetching the name and value properties in places where enum objects aren’t allowed, like array keys. We could work on arrays so they could be extended to allow enums or all objects as keys, but allowing to fetch properties of enums is simpler.
enum A: string {
case B = 'B';
const C = [self::B->value => self::B];
}
Previously static methods worked like this:
DateTime::createFromImmutable(): DateTime
DateTimeImmutable::createFromMutable(): DateTimeImmutable
In PHP 8.2 it’s going to be changed to:
DateTime::createFromImmutable(): static
DateTimeImmutable::createFromMutable(): static
This is a breaking change for library creators and/or all custom implementations of DateTime.
Those were two function that did not served it purpose, as they only converted between ISO-8859-1
and UTF-8
. PHP Manual suggest using mb_convert_encoding
instead.
Locale sensitivity is best described by author of the RFC:
Prior to PHP 8.0, PHP’s locale was set from the environment. When a user installs Linux, it asks what language you want it to be in. The user might not fully appreciate the consequences of this decision. It not only sets the user interface language for built-in commands, it also pervasively changes how string handling in the C library works. For example, a user selecting “Turkish” when installing Linux would find that applications calling toupper(‘i’) would obtain the dotted capital I (U+0130, “İ”).
In an era of standardized text-based protocols, natural language is a minority application for case conversion. But even if the user did want natural language case conversion, they would be unlikely to achieve success with strtolower(). This is because it processes the string one byte at a time, feeding each byte to the C library’s tolower(). If the input is UTF-8, by far the most popular modern choice, strtolower() will mangle the string, typically producing invalid UTF-8 as output.
PHP 8.0 stopped respecting the locale environment variables. So the locale is always “C” unless the user explicitly calls setlocale(). This means that the bulk of the backwards-incompatible change is already behind us. Any applications depending on the system locale to do case conversion of legacy 8-bit character sets would have been broken by PHP 8.0.
What it means is that all of below function will do ASCII case conversion from PHP.8.2:strtolower
, strtoupper
, stristr
, stripos
, strripos
, lcfirst
, ucfirst
, ucwords
, str_ireplace
We’ve got a lot of ways of embedding variables into strings in PHP:
– Directly embedding variables (“$foo”)
– Braces outside the variable (“{$foo}”)
– Braces after the dollar sign (“${foo}”)
– Variable variables (“${expr}”, equivalent to (string) ${expr})
To avoid confusion and misuse those will not work anymore:
"Hello ${world}";
Deprecated: Using ${} in strings is deprecated
"Hello ${(world)}";
Deprecated: Using ${} (variable variables) in strings is deprecated
These are not all the changes that PHP 8.2 will offer us. Unfortunately, we still didn’t get support for generic types, according to what Nikita said the monomorphized generics would add too much performance overhead, and reified generics require many changes across the whole codebase. What is noticeable, however, is the discipline and vision of the product. The changes introduced in successive versions of the language are becoming clearer, and those interested will notice that PHP is moving in the right direction both in the area of syntax simplification and support for novelties. I expect that as early as next year we will see callable
as a valid type.