Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Actualización a PHP 8

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $10.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

No perdamos de vista nuestro objetivo. Ahora que hemos actualizado a Symfony 5.4, en cuanto eliminemos todas estas depreciaciones, podremos actualizar con seguridad a Symfony 6. Pero Symfony 6 requiere PHP 8, y yo he estado construyendo este proyecto en PHP 7. Así que el siguiente paso es actualizar nuestro código para que sea compatible con PHP 8. En la práctica, eso significa actualizar partes de nuestro código para utilizar algunas nuevas y geniales características de PHP 8. ¡Vaya! Y este es otro punto en el que Rector puede ayudarnos.

¡Rector actualiza a PHP 8!

Empieza por abrir rector.php y eliminar las tres líneas de actualización de Symfony. Sustitúyelas por LevelSetList::UP_TO_PHP_80. Al igual que con Symfony, puedes actualizar específicamente a PHP 7.3 o 7.4, pero tienen estas bonitas declaraciones UP_TO_[...]que actualizarán nuestro código a través de todas las versiones de PHP hasta PHP 8.0.

31 lines rector.php
... lines 1 - 12
return static function (ContainerConfigurator $containerConfigurator): void {
... lines 14 - 22
$containerConfigurator->import(LevelSetList::UP_TO_PHP_80);
... lines 24 - 29
};

Y... ¡eso es todo lo que necesitamos!

En tu terminal, he confirmado todos mis cambios, excepto el que acabamos de hacer. Así que ahora podemos ejecutar:

vendor/bin/rector process src

¡Genial! Vamos a revisar algunos de estos cambios. Si quieres profundizar más, busca la entrada del blog getrector.org, que te muestra cómo hacer lo que acabamos de hacer... pero también te da más información sobre lo que hizo Rector y por qué.

Por ejemplo, uno de los cambios que hace es sustituir las sentencias switch() por una nueva función de PHP 8 match(). Esto explica eso... y muchos otros cambios. Ah, y la gran mayoría de estos cambios no son necesarios: no tienes que hacerlos para actualizar a PHP 8. Simplemente están bien.

Promoción de las propiedades de PHP 8

El cambio más importante, que casualmente es el más común, es algo llamado "Propiedades Promocionadas". Esta es una de mis características favoritas en PHP 8, y puedes verla aquí. En PHP 8, puedes añadir una palabra clave private, public, oprotected justo antes de un argumento en el constructor... y eso creará esa propiedad y la establecerá a este valor. Así que ya no tienes que añadir una propiedad manualmente ni establecerla a continuación. Sólo tienes que añadir private y... ¡listo!

... lines 1 - 8
class MarkdownExtension extends AbstractExtension
{
public function __construct(private MarkdownHelper $markdownHelper)
{
}
... lines 14 - 28
}

La gran mayoría de los cambios en este archivo son exactamente eso... aquí hay otro ejemplo en MarkdownHelper. La mayoría de los demás cambios son menores. Se han modificado algunas funciones de devolución de llamada para utilizar la nueva sintaxis corta =>, que en realidad es de PHP 7.4

... lines 1 - 8
class MarkdownHelper
{
... lines 11 - 14
public function parse(string $source): string
{
... lines 17 - 24
return $this->cache->get('markdown_'.md5($source), fn() => $this->markdownParser->transformMarkdown($source));
}
}

También puedes ver, aquí abajo, un ejemplo de refactorización de las sentencias switch() para utilizar la nueva función match()

... lines 1 - 11
class QuestionVoter extends Voter
{
... lines 14 - 24
protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool
{
... lines 27 - 40
return match ($attribute) {
'EDIT' => $user === $subject->getOwner(),
default => false,
};
}
}

Todo esto es opcional, pero es bueno que nuestro código se haya actualizado para utilizar algunas de las nuevas funciones. Si me desplazo un poco más hacia abajo, verás más de esto.

¿Tipos de propiedades de las entidades?

Ah, y dentro de nuestras entidades, fíjate en que, en algunos casos, ¡se han añadido tipos de propiedades! En el caso de $roles, esta propiedad se inicializa en una matriz. Rector se dio cuenta de ello... así que añadió el tipo array

... lines 1 - 15
class User implements UserInterface
{
... lines 18 - 29
/**
* @ORM\Column(type="json")
*/
private array $roles = [];
... lines 34 - 226
}

En otros casos, como $password, vio que tenemos PHPDoc por encima, así que también añadió el tipo allí

... lines 1 - 15
class User implements UserInterface
{
... lines 18 - 34
/**
* @var string The hashed password
* @ORM\Column(type="string")
*/
private string $password;
... lines 40 - 226
}

Sin embargo, esto es un poco cuestionable. El $password también podría ser nulo.

Abre src/Entity/User.php y baja hasta $password. El rector le dio un tipo string... ¡pero eso está mal! Si te fijas en el constructor de aquí abajo, no inicializamos $password a ningún valor... lo que significa que empezará null. Así que el tipo correcto para esto es un ?string anulable . La razón por la que Rector hizo esto mal es... bueno... ¡porque tenía un error en mi documentación!. Esto debería serstring|null

... lines 1 - 15
class User implements UserInterface
{
... lines 18 - 34
/**
* @var string|null The hashed password
* @ORM\Column(type="string")
*/
private ?string $password = null;
... lines 40 - 226
}

Uno de los mayores cambios que he realizado en mi código durante el último año, más o menos desde que se publicó PHP 7.3, ha sido añadir tipos de propiedades como ésta, tanto en mis clases de entidad como en mis clases de servicio. Si esto ha sido un poco confuso, no te preocupes. Vamos a hablar más sobre los tipos de propiedades dentro de las entidades en unos minutos. Puedes ver que Rector ha añadido algunas, pero a muchas de nuestras propiedades todavía les faltan.

Configurando PHP 8 en composer.json

Bien, nuestro código debería estar ahora preparado para PHP 8. ¡Yay! Así que vamos a actualizar nuestras dependencias para PHP 8. En composer.json, bajo la clave require, actualmente dice que mi proyecto funciona con PHP 7.4 o 8. Voy a cambiar eso para que sólo diga"php": "^8.0.2", que es la versión mínima para Symfony 6.0

111 lines composer.json
{
... lines 2 - 5
"require": {
"php": "^8.0.2",
... lines 8 - 45
},
... lines 47 - 109
}

Por cierto, Symfony 6.1 requiere PHP 8.1. Así que si vas a actualizarte a eso muy pronto, puedes saltar directamente a la 8.1.

Hay otra cosa que tengo aquí abajo, cerca de la parte inferior. Veamos... aquí vamos. Enconfig, platform, tengo PHP configurado en 7.4. Eso asegura que si alguien está usando PHP 8, Composer todavía se asegurará de descargar dependencias compatibles con PHP 7.4. Cambia esto a 8.0.2.

111 lines composer.json
{
... lines 2 - 56
"config": {
... lines 58 - 61
"platform": {
"php": "8.0.2"
},
... lines 65 - 68
},
... lines 70 - 109
}

¡Qué bien! Y ahora, como estamos usando PHP 8 en nuestro proyecto, es muy probable que algunas dependencias puedan ser actualizadas. Ejecuta:

composer up

Y... ¡sí! Hay varias. Parece que psr/cache, psr/log, ysymfony/event-dispatcher-contracts se han actualizado. Lo más probable es que todas estas nuevas versiones requieran PHP 8. Antes no podíamos actualizar, pero ahora sí. Si vamos a nuestra página y recargamos... ¡todo sigue funcionando!

Actualizar Symfony Flex

Otra cosa en composer.json es el propio Symfony Flex. Flex utiliza su propio esquema de versiones, y la última versión es la 2.1. En este momento, la versión 2 de Flex y la versión 1 de Flex son idénticas... excepto que Flex 2 requiere PHP 8. Ya que estamos usando eso, ¡actualicemos! Cambia la versión a ^2.1... luego vuelve a tu terminal y ejecuta

composer up

una vez más. ¡Muy bien!

¡Muy bien, equipo! Nuestro proyecto ya utiliza PHP 8. Para celebrarlo, vamos a refactorizar y pasar de usar anotaciones a atributos nativos de PHP 8. OOOoo. Me encanta este cambio... en parte porque Rector lo hace súper fácil.

Leave a comment!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^8.0.2",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "babdev/pagerfanta-bundle": "^3.6", // v3.6.1
        "composer/package-versions-deprecated": "^1.11", // 1.11.99.5
        "doctrine/annotations": "^1.13", // 1.13.2
        "doctrine/dbal": "^3.3", // 3.3.5
        "doctrine/doctrine-bundle": "^2.0", // 2.6.2
        "doctrine/doctrine-migrations-bundle": "^3.2", // 3.2.2
        "doctrine/orm": "^2.0", // 2.11.2
        "knplabs/knp-markdown-bundle": "^1.8", // 1.10.0
        "knplabs/knp-time-bundle": "^1.18", // v1.18.0
        "pagerfanta/doctrine-orm-adapter": "^3.6", // v3.6.1
        "pagerfanta/twig": "^3.6", // v3.6.1
        "sensio/framework-extra-bundle": "^6.0", // v6.2.6
        "sentry/sentry-symfony": "^4.0", // 4.2.8
        "stof/doctrine-extensions-bundle": "^1.5", // v1.7.0
        "symfony/asset": "6.0.*", // v6.0.7
        "symfony/console": "6.0.*", // v6.0.7
        "symfony/dotenv": "6.0.*", // v6.0.5
        "symfony/flex": "^2.1", // v2.1.7
        "symfony/form": "6.0.*", // v6.0.7
        "symfony/framework-bundle": "6.0.*", // v6.0.7
        "symfony/mailer": "6.0.*", // v6.0.5
        "symfony/monolog-bundle": "^3.0", // v3.7.1
        "symfony/property-access": "6.0.*", // v6.0.7
        "symfony/property-info": "6.0.*", // v6.0.7
        "symfony/proxy-manager-bridge": "6.0.*", // v6.0.6
        "symfony/routing": "6.0.*", // v6.0.5
        "symfony/runtime": "6.0.*", // v6.0.7
        "symfony/security-bundle": "6.0.*", // v6.0.5
        "symfony/serializer": "6.0.*", // v6.0.7
        "symfony/stopwatch": "6.0.*", // v6.0.5
        "symfony/twig-bundle": "6.0.*", // v6.0.3
        "symfony/ux-chartjs": "^2.0", // v2.1.0
        "symfony/validator": "6.0.*", // v6.0.7
        "symfony/webpack-encore-bundle": "^1.7", // v1.14.0
        "symfony/yaml": "6.0.*", // v6.0.3
        "symfonycasts/verify-email-bundle": "^1.7", // v1.10.0
        "twig/extra-bundle": "^2.12|^3.0", // v3.3.8
        "twig/string-extra": "^3.3", // v3.3.5
        "twig/twig": "^2.12|^3.0" // v3.3.10
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.4", // 3.4.1
        "phpunit/phpunit": "^9.5", // 9.5.20
        "rector/rector": "^0.12.17", // 0.12.20
        "symfony/debug-bundle": "6.0.*", // v6.0.3
        "symfony/maker-bundle": "^1.15", // v1.38.0
        "symfony/var-dumper": "6.0.*", // v6.0.6
        "symfony/web-profiler-bundle": "6.0.*", // v6.0.6
        "zenstruck/foundry": "^1.16" // v1.18.0
    }
}