Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Anotaciones a atributos

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

Ahora que estamos en PHP 8, vamos a convertir nuestras anotaciones de PHP en los atributos de PHP 8, que están más de moda. Refactorizar las anotaciones a atributos es básicamente... un trabajo muy ocupado. Puedes hacerlo a mano: los atributos y las anotaciones funcionan exactamente igual y utilizan las mismas clases. Incluso la sintaxis es sólo un poco diferente: utilizas dos puntos para separar los argumentos... porque en realidad estás aprovechando los argumentos con nombre de PHP. Genial.

Configurar Rector para actualizar las anotaciones

Así que la conversión es sencilla... pero uf, no me entusiasma hacer todo eso manualmente. Afortunadamente, ¡Rector vuelve al rescate! Busca "rector annotations to attributes" para encontrar una entrada del blog que te dice la configuración exacta de importación que necesitamos en rector.php. Copia estas tres cosas. Ah, y a partir de Rector 0.12, hay un nuevo objeto RectorConfig más sencillo que verás en esta página. Si tienes esa versión, no dudes en utilizar ese código.

Ah, y antes de pegar esto, busca tu terminal, añade todo... y luego confirma. Perfecto

De vuelta en rector.php, sustituye la única línea por estas cuatro líneas... excepto que no necesitamos la NetteSetList... y tenemos que añadir unas cuantas declaraciones use. Volveré a escribir la "t" en DoctrineSetList, pulsaré "tab", y haré lo mismo paraSensiolabsSetList.

35 lines rector.php
... lines 1 - 6
use Rector\Doctrine\Set\DoctrineSetList;
... lines 8 - 9
use Rector\Symfony\Set\SensiolabsSetList;
... lines 11 - 14
return static function (ContainerConfigurator $containerConfigurator): void {
... lines 16 - 24
$containerConfigurator->import(DoctrineSetList::ANNOTATIONS_TO_ATTRIBUTES);
$containerConfigurator->import(SymfonySetList::ANNOTATIONS_TO_ATTRIBUTES);
$containerConfigurator->import(SensiolabsSetList::FRAMEWORK_EXTRA_61);
... lines 28 - 33
};

Ahora, ya sabes lo que hay que hacer. Ejecuta

vendor/bin/rector process src

y mira lo que pasa. Vaya... ¡esto es impresionante! Mira! Ha refactorizado maravillosamente esta anotación a un atributo y... ¡lo ha hecho por todas partes! Tenemos rutas aquí arriba. Y todas nuestras anotaciones de entidades, como la entidad Answer también se han convertido. Eso fue una tonelada de trabajo... ¡todo automático!

... lines 1 - 4
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
use Symfony\Component\Routing\Annotation\Route;
class UserController extends BaseController
{
#[Route(path: '/api/me', name: 'app_user_api_me')]
#[IsGranted('IS_AUTHENTICATED_REMEMBERED')]
public function apiMe(): \Symfony\Component\HttpFoundation\Response
{
... lines 14 - 16
}
}

... lines 1 - 11
#[ORM\Entity(repositoryClass: UserRepository::class)]
#[ORM\Table(name: '`user`')]
class User implements UserInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private $id;
... lines 20 - 203
}

Arreglando PHP CS

Aunque, como hace a veces Rector, estropeó algunas de nuestras normas de codificación. Por ejemplo, hasta el final, refactorizó esta anotación Route a un atributo... pero luego añadió un pequeño espacio extra antes del tipo de retorno Response. Eso no es un problema. Después de ejecutar Rector, siempre es una buena idea ejecutar PHP CS Fixer. Hazlo:

tools/php-cs-fixer/vendor/bin/php-cs-fixer fix

Me encanta. Un montón de correcciones para que nuestro código vuelva a estar en línea. Ejecuta

git diff

para ver cómo quedan las cosas ahora. La anotación Route se ha convertido en un atributo... y PHP CS Fixer ha vuelto a poner el tipo de retorno Response como estaba antes. Rector incluso refactorizó IsGranted de SensioFrameworkExtraBundle a un atributo.

Pero si sigues desplazándote hacia abajo hasta que encuentres una entidad... aquí vamos... ¡oh! ¡Se ha cargado los saltos de línea entre nuestras propiedades! No es súper obvio en el diff, pero si abres cualquier entidad... ¡vaya! Esto parece... estrecho. Me gustan los saltos de línea entre las propiedades de mis entidades.

... lines 1 - 9
class Answer
{
use TimestampableEntity;
public const STATUS_NEEDS_APPROVAL = 'needs_approval';
public const STATUS_SPAM = 'spam';
public const STATUS_APPROVED = 'approved';
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private $id;
#[ORM\Column(type: 'text')]
private $content;
... lines 22 - 48
public function getUsername(): ?string
... lines 50 - 113
}

Podríamos arreglar esto a mano... pero me pregunto si podemos enseñar a PHP CS Fixer a hacer esto por nosotros.

Abre php-cs-fixer.php. La regla que controla estos saltos de línea se llamaclass_attributes_separation con una "s" - lo arreglaré en un minuto. Ponlo en una matriz que describa todas las diferentes partes de nuestra clase y cómo debe comportarse cada una. Por ejemplo, podemos decir ['method' => 'one'] para decir que queremos una línea vacía entre cada método. También podemos decir ['property' => 'one'] para tener un salto de línea entre nuestras propiedades. También hay otro llamado trait_import. Ponlo también en one. Eso nos da una línea vacía entre nuestras importaciones de rasgos, que es algo que tenemos encima de Answer.

... lines 1 - 7
return $config->setRules([
... lines 9 - 10
'class_attributes_separation' => [
'elements' => ['method' => 'one', 'property' => 'one', 'trait_import' => 'one']
]
])
... line 15
;

Ahora prueba de nuevo con php-cs-fixer:

tools/php-cs-fixer/vendor/bin/php-cs-fixer fix

¡Ups!

Las reglas contienen fijadores desconocidos: "class_attribute_separation"

Quise decir class_attributes_separation con una "s". Sin embargo, qué gran error. Probemos de nuevo y... ¡genial! Ha cambiado cinco archivos, y si los compruebas... ¡han vuelto!

... lines 1 - 9
class Answer
{
use TimestampableEntity;
public const STATUS_NEEDS_APPROVAL = 'needs_approval';
public const STATUS_SPAM = 'spam';
public const STATUS_APPROVED = 'approved';
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private $id;
#[ORM\Column(type: 'text')]
private $content;
... lines 25 - 120
}

Con sólo unos pocos comandos hemos convertido todo nuestro sitio de anotaciones a atributos. ¡Guau!

A continuación, vamos a añadir tipos de propiedades a nuestras entidades. Eso nos va a permitir tener menos configuración de entidades gracias a una nueva función de Doctrine.

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
    }
}