gstreamer0.10-ffmpeg
gstreamer0.10-plugins-good
packages.
Una de las cosas más chulas, y quizá más sorprendentes, de Doctrine es que quiere que finjas que la base de datos no existe Sí, en lugar de pensar en tablas y columnas, Doctrine quiere que pensemos en objetos y propiedades.
Por ejemplo, supongamos que queremos guardar los datos de un producto. La forma de hacerlo con Doctrine es crear una clase Product
con propiedades que contengan los datos. Entonces instancias un objeto Product
, pones los datos en él y pides amablemente a Doctrine que los guarde por ti. No tenemos que preocuparnos de cómo lo hace Doctrine.
Pero, por supuesto, entre bastidores Doctrine está hablando con una base de datos. Insertará los datos del objeto Product
en una tabla product
en la que cada propiedad está asignada a una columna. Esto se llama mapeador relacional de objetos, o ORM.
Más tarde, cuando queramos recuperar esos datos, no pensaremos en "consultar" esa tabla y sus columnas. No, simplemente le pedimos a Doctrine que encuentre el objeto que teníamos antes. Por supuesto, consultará la tabla... y luego volverá a crear el objeto con los datos. Pero no es un detalle en el que pensemos: pedimos el objeto Product
, y nos lo da. Doctrine se encarga de guardar y consultar todo automáticamente.
De todos modos, cuando utilizamos un ORM como Doctrine, si queremos guardar algo en la base de datos, tenemos que crear una clase que modele lo que queremos guardar, como una clase Product
. En Doctrine, estas clases reciben un nombre especial: entidades, aunque en realidad son clases normales de PHP. Y aunque puedes crear estas clases de entidad a mano, hay un comando MakerBundle que te hace la vida mucho más agradable.
Ve a tu terminal y ejecuta:
php bin/console make:entity
En este caso, no tenemos que ejecutar symfony console make:entity
porque este comando no hablará con la base de datos: sólo genera código. Pero, si alguna vez no estás seguro, usar symfony console
siempre es seguro.
Bien, queremos crear una clase para almacenar todas las mezclas de vinilos de nuestro sistema. Así que vamos a crear una nueva clase llamada VinylMix
. A continuación, responde a no
para transmitir las actualizaciones de las entidades: es una característica extra relacionada con Symfony Turbo.
Bien, aquí está la parte importante: nos pregunta qué propiedades queremos. Vamos a añadir varias. Empezamos con una llamada title
. A continuación nos pregunta de qué tipo es este campo. Pulsa ?
para ver la lista completa.
Estos son tipos de Doctrine... y cada uno de ellos se asignará a un tipo de columna diferente en tu base de datos, dependiendo de la base de datos que estés utilizando, como MySQL o Postgres. Los tipos básicos están en la parte superior como string
, text
- que puede contener más que una cadena) - boolean
, integer
y float
. Luego los campos de relación -de los que hablaremos en el próximo tutorial-, algunos campos especiales, como el almacenamiento de JSON y los campos de fecha.
Para title
, utiliza string
, que puede contener hasta 255 caracteres. Mantendré la longitud por defecto... luego nos pregunta si el campo puede ser nulo en la base de datos. Responderé no
. Esto significa que la columna no puede ser nula. En otras palabras, la columna será obligatoria en la base de datos.
Y... ¡un campo hecho! Vamos a añadir algunos más. Necesitamos un description
, y que sea del tipo text
. string
tiene un máximo de 255 caracteres, text
puede contener un montón más. Esta vez, diré yes
para que sea anulable. Así será una columna opcional en la base de datos. ¡Otra más para abajo!
Para la siguiente propiedad, llámala trackCount
. Será un integer
y será no nulo. Luego añade genre
, como un string
, de longitud 255... y también no nulo para que sea obligatorio en la base de datos.
Por último, añade un campo createdAt
para que podamos saber cuándo se creó originalmente cada mezcla de vinilo. Esta vez, como el nombre del campo termina en "At", el comando sugiere un tipo datetime_immutable
. Pulsa "intro" para utilizarlo, y también para que no sea nulo en la base de datos.
Ahora no necesitamos añadir más propiedades, así que pulsa "intro" una vez más para salir del comando.
Ya está ¿Qué ha hecho esto? Bueno, en primer lugar, puedo decirte que esto no ha hablado con nuestra base de datos ni la ha modificado en absoluto. No, simplemente ha generado dos clases. La primera es src/Entity/VinylMix.php
. La segunda es src/Repository/VinylMixRepository.php
. Ignora la Repository
por ahora... hablaremos de su propósito en unos minutos.
... lines 1 - 8 | |
#[ORM\Entity(repositoryClass: VinylMixRepository::class)] | |
class VinylMix | |
{ | |
#[ORM\Id] | |
#[ORM\GeneratedValue] | |
#[ORM\Column()] | |
private ?int $id = null; | |
#[ORM\Column(length: 255)] | |
private ?string $title = null; | |
#[ORM\Column(type: Types::TEXT, nullable: true)] | |
private ?string $description = null; | |
... lines 22 - 31 | |
public function getId(): ?int | |
{ | |
return $this->id; | |
} | |
public function getTitle(): ?string | |
{ | |
return $this->title; | |
} | |
public function setTitle(string $title): self | |
{ | |
$this->title = $title; | |
return $this; | |
} | |
... lines 48 - 95 | |
} |
Ve a abrir la entidad VinylMix.php
. Saluda a... una... ¡vaya, una clase PHP bastante normal y aburrida! Generó una propiedad private
para cada campo que añadimos, además de una propiedad extra id
. El comando también añadió un método getter y setter para cada una de ellas. Así que... esto es básicamente una clase que contiene datos... y podemos acceder y establecer esos datos a través de los métodos getter y setter
Lo único que hace especial a esta clase son los atributos. El ORM\Entity
sobre la clase le dice a Doctrine:
Quiero poder guardar objetos de esta clase en la base de datos. Este es una entidad.
Luego, encima de cada propiedad, utilizamos ORM\Column
para decirle a Doctrine que queremos guardar esta propiedad como una columna en la tabla. Esto también comunica otras opciones como la longitud de la columna y si debe ser anulable o no.nullable: false
es el valor por defecto... así que el comando sólo generó nullable: true
en la única propiedad que lo necesita.
La otra cosa que controla ORM\Column
es el tipo de campo. Eso se establece mediante esta opción type
. Como ya he dicho, esto no se refiere directamente a un tipo de MySQL o Postgres... es un tipo de Doctrine que luego se asignará a algo específico en función de nuestra base de datos.
Pero, interesante: la opción type
sólo aparece en el campo $description
. La razón de esto es realmente genial... ¡y nueva! Doctrine es inteligente. Mira el tipo de tu propiedad y adivina el tipo de campo a partir de él. Así que cuando tienes un tipo de propiedad string
, Doctrine asume que quieres que ese sea su tipo string
. Podrías escribir Types::STRING
dentro de ORM\Column
... pero eso sería totalmente redundante.
Sin embargo, lo necesitamos para el campo description
... porque queremos utilizar el tipoTEXT
, no el tipo STRING
. Pero en cualquier otra situación, funciona. Doctrine adivina el tipo correcto a partir del tipo de propiedad ?int
... y lo mismo ocurre aquí abajo para el tipo ?\DateTimeImmutable
.
Además de controlar las cosas de cada columna, también podemos controlar el nombre de la tabla añadiendo un ORM\Table
encima de la clase con el nombre establecido, por ejemplo,vinyl_mix
. Pero, ¡sorpresa! ¡No necesitamos hacer eso! ¿Por qué? Porque Doctrine es muy bueno generando grandes nombres. Genera el nombre de la tabla transformando la clase en caso de serpiente. Así que incluso sin ORM\Table
, éste será el nombre de la tabla. Lo mismo ocurre con las propiedades. $trackCount
se asignará a una columnatrack_count
. Doctrine se encarga de todo esto por nosotros: no tenemos que pensar en absoluto en los nombres de nuestras tablas o columnas.
Llegados a este punto, hemos ejecutado make:entity
y nos ha generado una clase de entidad. Pero... todavía no tenemos una tabla vinyl_mix
en nuestra base de datos. ¿Cómo creamos una? Con la magia de las migraciones de bases de datos. Eso a continuación.
Hey Michail!
GREAT question. That was a design decision in MakerBundle and the goal was to be "friendly" over "strict" (definitely a subjective decision). So, if make bundle instead generate things like private string $title
, then if the user called $obj->getTitle()
before setting it, they would get the "not initialized" error from PHP. So, to make the entities behave identically to the "pre property types world", we made them nullable and default to null. It's very possible that, as a community, we will eventually desire to be more strict... or we might even add an option in MakerBundle. But, that's the explanation :).
Cheers!
Can you point me to tutorial about setup of symfony to use yaml format instead of php for the entity classes.
Hey BYT,
We always use PHP attributes (or annotations on old tutorials) for configuring entities, so I can't point you to any tutorial but you can see examples in the Symfony docs. For example, you can see how to configure an association here (just click on the YAML option) https://symfony.com/doc/current/doctrine/associations.html#mapping-the-manytoone-relationship
And you may want to read how to configure your entities when using YAML https://symfony.com/doc/current/reference/configuration/doctrine.html#custom-mapping-entities-in-a-bundle
Cheers!
Creating tables and columns one by one will take very long. I have hundreds of tables and thousands of columns listed in Excel. I can export table/columns/relationship info to a CSV or any format. Is there a way to generate all of the Entity classes from this existing file? I'm not talking about Down from an existing database because the exiting database has a different way. I can list schema in files, a file or any format.
Hey BYT,
I don't think so... unless you will find a bundle or a library on GitHub that may help you with this. Doctrine only allows you to import mapping information from an existing database - for this you can leverage doctrine:mapping:import
command.
If it's not something you need - I think you can create your own Symfony command and read the CSV file to import all the data from that, i.e. create entity files based on the schema in that file.
Cheers!
Hi
I want to be sure that I understand this right. I want to start new project, and I already have the database, with about 200 tables in it.
If I run "symfony console doctrine:mapping:import" command, an entity for each table will be created for me? Is my assumption correct?
Thanks.
Hey @t5810 ,
Yes, that's how it suppose to be used, it creates entities with the corresponding mapping. But on practice, it depends on how well you have your legacy DB configured. But you can easily try it and see how it works for you ;)
Cheers!
Hello. I want to create a CRM system in which there will be users with their own sets of Task entities.
It is very convenient when the ID of each entity is represented by an integer and the numbers for each individual user go sequentially and inextricably.
In simple words, you need an ID with auto-increment, then separately for each user. But how to do it right? It should be fast and have good scalability.
Hey Dmitriy,
But Doctrine already does this for you. You just need to generate an entity via MakerBundle, or make sure your User entity has this mapping config:
User
{
#[ORM\Id, ORM\Column, ORM\GeneratedValue]
private ?int $id = null;
}
Every time you will create a new entity - it will generate a sequential and inextricable ID for it. But if you delete a user from the DB - it won't be sequential anymore I suppose, and nothing much you can do with it.
I hope this helps!
Cheers!
Hi, is there a way to create the entity in a specific folder? (e.g src/CustomFolder/Entity/...)
Hey Wei,
Unfortunately, no. The Maker bundle follows Symfony's best practice when all your entities are in the src/Entity/
folder. This is also done for the simplicity of this command. The easiest way would be to generate an entity in that folder and then move it to another folder if needed.
Well, technically, you can put your entity in a subfolder of the src/Entity/
with the following syntax:
bin/console make:entity Subfolder\\YourEntityName
But if you're looking for a way to create it outside of the default src/Entity/
- nope, you would need to move it manually there.
I hope this helps!
Cheers!
// composer.json
{
"require": {
"php": ">=8.1",
"ext-ctype": "*",
"ext-iconv": "*",
"babdev/pagerfanta-bundle": "^3.7", // v3.7.0
"doctrine/doctrine-bundle": "^2.7", // 2.7.0
"doctrine/doctrine-migrations-bundle": "^3.2", // 3.2.2
"doctrine/orm": "^2.12", // 2.12.3
"knplabs/knp-time-bundle": "^1.18", // v1.19.0
"pagerfanta/doctrine-orm-adapter": "^3.6", // v3.6.1
"pagerfanta/twig": "^3.6", // v3.6.1
"sensio/framework-extra-bundle": "^6.2", // v6.2.6
"stof/doctrine-extensions-bundle": "^1.7", // v1.7.0
"symfony/asset": "6.1.*", // v6.1.0
"symfony/console": "6.1.*", // v6.1.2
"symfony/dotenv": "6.1.*", // v6.1.0
"symfony/flex": "^2", // v2.2.2
"symfony/framework-bundle": "6.1.*", // v6.1.2
"symfony/http-client": "6.1.*", // v6.1.2
"symfony/monolog-bundle": "^3.0", // v3.8.0
"symfony/proxy-manager-bridge": "6.1.*", // v6.1.0
"symfony/runtime": "6.1.*", // v6.1.1
"symfony/twig-bundle": "6.1.*", // v6.1.1
"symfony/ux-turbo": "^2.0", // v2.3.0
"symfony/webpack-encore-bundle": "^1.13", // v1.15.1
"symfony/yaml": "6.1.*", // v6.1.2
"twig/extra-bundle": "^2.12|^3.0", // v3.4.0
"twig/twig": "^2.12|^3.0" // v3.4.1
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.4", // 3.4.2
"symfony/debug-bundle": "6.1.*", // v6.1.0
"symfony/maker-bundle": "^1.41", // v1.44.0
"symfony/stopwatch": "6.1.*", // v6.1.0
"symfony/web-profiler-bundle": "6.1.*", // v6.1.2
"zenstruck/foundry": "^1.21" // v1.21.0
}
}
Why are the non-nullable properties (e.g. title) type-hinted as possibly having a null value and initialised as null?