Chapters
Scroll down to the script below, click on any sentence (including terminal blocks) to jump to that spot in the video!
Tengo que decir que echo de menos los años 90. Bueno, no los beanie babies y... definitivamente no la forma de vestir de entonces, pero... las cintas de mezclas. Si no eras un niño en los 80 o los 90, quizá no sepas lo difícil que era compartir tus canciones favoritas con tus amigos. Oh sí, estoy hablando de un mashup de Michael Jackson, Phil Collins y Paula Abdul. La perfección.
Para aprovechar esa nostalgia, pero con un toque hipster, vamos a crear una nueva aplicación llamada Mixed Vinyl: una tienda en la que los usuarios pueden crear cintas de mezclas, con Boyz || Men, Mariah Carey y Smashing Pumpkins... sólo que prensadas en un disco de vinilo. Hmm, puede que tenga que poner un tocadiscos en mi coche.
La página que estamos viendo, que es súper bonita y cambia de color cuando refrescamos... no es una página real. Es sólo una forma de que Symfony nos diga "hola" y nos enlace a la documentación. Y por cierto, la documentación de Symfony es genial, así que no dudes en consultarla mientras aprendes.
Rutas y controladores
Vale: todo framework web en cualquier lenguaje tiene el mismo trabajo: ayudarnos a crear páginas, ya sean páginas HTML, respuestas JSON de la API o arte ASCII. Y casi todos los marcos lo hacen de la misma manera: mediante un sistema de rutas y controladores. La ruta define la URL de la página y apunta a un controlador. El controlador es una función PHP que construye esa página.
Así que ruta + controlador = página. Son matemáticas, gente.
Crear el controlador
Vamos a construir estas dos cosas... un poco al revés. Así que primero, vamos a crear la función del controlador. En Symfony, la función del controlador es siempre un método dentro de una clase PHP. Te lo mostraré: en el directorio src/Controller/
, crea una nueva clase PHP. Vamos a llamarla VinylController
, pero el nombre puede ser cualquier cosa.
<?php | |
namespace App\Controller; | |
class VinylController | |
{ | |
} |
Y, ¡felicidades! ¡Es nuestra primera clase PHP! ¿Y adivina dónde vive? En el directorio src/
, donde vivirán todas las clases PHP. Y en general, no importa cómo organices las cosas dentro de src/
: normalmente puedes poner las cosas en el directorio que quieras y nombrar las clases como quieras. Así que da rienda suelta a tu creatividad.
Tip
En realidad, los controladores deben vivir en src/Controller/
, a menos que cambies alguna configuración. La mayoría de las clases de PHP pueden vivir en cualquier lugar de src/
.
Pero hay dos reglas importantes. En primer lugar, fíjate en el espacio de nombres que PhpStorm ha añadido sobre la clase: App\Controller
. Independientemente de cómo decidas organizar tu directorio src/
, el espacio de nombres de una clase debe coincidir con la estructura del directorio... empezando por App
. Puedes imaginar que el espacio de nombres App\
apunta al directoriosrc/
. Entonces, si pones un archivo en un subdirectorio Controller/
, necesita una parte Controller
en su espacio de nombres.
Si alguna vez metes la pata, por ejemplo, si escribes algo mal o te olvidas de esto, lo vas a pasar mal. PHP no podrá encontrar la clase: obtendrás un error de "clase no encontrada". Ah, y la otra regla es que el nombre de un archivo debe coincidir con el nombre de la clase dentro de él, más .php
. Por lo tanto, VinylController.php
. Seguiremos esas dos reglas para todos los archivos que creemos en src/
.
Crear el controlador
Volvemos a nuestra tarea de crear una función de controlador. Dentro, añade un nuevo método público llamado homepage()
. Y no, el nombre de este método tampoco importa: prueba a ponerle el nombre de tu gato: ¡funcionará!
Por ahora, sólo voy a poner una declaración die()
con un mensaje.
<?php | |
namespace App\Controller; | |
class VinylController | |
{ | |
public function homepage() | |
{ | |
die('Vinyl: Definitely NOT a fancy-looking frisbee!'); | |
} | |
} |
Crear la ruta
¡Buen comienzo! Ahora que tenemos una función de controlador, vamos a crear una ruta, que define la URL de nuestra nueva página y apunta a este controlador. Hay varias formas de crear rutas en Symfony, pero casi todo el mundo utiliza atributos.
Así es como funciona. Justo encima de este método, decimos #[]
. Esta es la sintaxis de atributos de PHP 8, que es una forma de añadir configuración a tu código. Empieza a escribir Route
. Pero antes de que termines, fíjate en que PhpStorm lo está autocompletando. Pulsa el tabulador para dejar que termine.
Eso, muy bien, completó la palabra Route
para mí. Pero lo más importante es que ha añadido una declaración use
en la parte superior. Siempre que utilices un atributo, debes tener una declaración use
correspondiente en la parte superior del archivo.
Dentro de Route
, pasa /
, que será la URL de nuestra página.
<?php | |
namespace App\Controller; | |
use Symfony\Component\Routing\Annotation\Route; | |
class VinylController | |
{ | |
#[Route('/')] | |
public function homepage() | |
{ | |
die('Vinyl: Definitely NOT a fancy-looking frisbee!'); | |
} | |
} |
Y... ¡listo! Esta ruta define la URL y apunta a este controlador... simplemente porque está justo encima de este controlador.
¡Vamos a probarlo! Refresca y... ¡felicidades! ¡Symfony miró la URL, vio que coincidía con la ruta - /
o sin barra es lo mismo para la página de inicio - ejecutó nuestro controlador y golpeó la declaración die
!
Ah, y por cierto, sigo diciendo función del controlador. Comúnmente se llama simplemente "controlador" o "acción"... sólo para confundir.
Devolver una respuesta
Bien, dentro del controlador -o acción- podemos escribir el código que queramos para construir la página, como hacer consultas a la base de datos, llamadas a la API, renderizar una plantilla, lo que sea. Al final vamos a hacer todo eso.
Lo único que le importa a Symfony es que tu controlador devuelva un objetoResponse
. Compruébalo: escribe return
y luego empieza a escribir Response
. Woh: hay bastantes clases Response
ya en nuestro código... ¡y dos son de Symfony! Queremos la de HTTP foundation. HTTP foundation es una de esas librerías de Symfony... y nos da bonitas clases para cosas como la Petición, la Respuesta y la Sesión. Pulsa el tabulador para autocompletar y termina eso.
Oh, debería haber dicho devolver una nueva respuesta. Así está mejor. Ahora dale al tabulador. Cuando dejé que Response
autocompletara la primera vez, muy importante, PhpStorm añadió esta declaración de uso en la parte superior. Cada vez que hagamos referencia a una clase o interfaz, tendremos que añadir una sentencia use
al principio del archivo en el que estemos trabajando.
Al dejar que PhpStorm autocompletara eso por mí, añadió la declaración use
automáticamente. Lo haré cada vez que haga referencia a una clase. Ah, y si todavía eres un poco nuevo en lo que respecta a los espacios de nombres de PHP y las declaraciones use
, echa un vistazo a nuestro breve y gratuito tutorial sobre espacios de nombres de PHP.
De todos modos, dentro de Response
, podemos poner lo que queramos devolver al usuario: HTML, JSON o, por ahora, un simple mensaje, como el título del vinilo Mixto en el que estamos trabajando: PB y jams.
<?php | |
namespace App\Controller; | |
use Symfony\Component\HttpFoundation\Response; | |
use Symfony\Component\Routing\Annotation\Route; | |
class VinylController | |
{ | |
#[Route('/')] | |
public function homepage() | |
{ | |
return new Response('Title: "PB and Jams"'); | |
} | |
} |
Bien, equipo, ¡vamos a ver qué pasa! Actualiza y... ¡PB y mermeladas! Puede que no parezca gran cosa, ¡pero acabamos de construir nuestra primera página Symfony totalmente funcional! ¡Ruta + controlador = beneficio!
Y acabas de aprender la parte más fundamental de Symfony... y sólo estamos empezando. Ah, y como nuestros controladores siempre devuelven un objeto Response
, es opcional, pero puedes añadir un tipo de retorno a esta función si lo deseas. Pero eso no cambia nada: sólo es una forma agradable de codificar.
Show Lines
|
// ... lines 1 - 9 |
#[Route('/')] | |
public function homepage(): Response | |
Show Lines
|
// ... lines 12 - 16 |
A continuación me siento bastante seguro. Así que vamos a crear otra página, pero con una ruta mucho más elegante que coincide con un patrón comodín.
37 Comments
Hi AlejandroVillafane!
Hmm, that is super weird! So, no, you should not need to do anything like what's described in the StackOverflow post: it should work out-of-the-box. But let me explain, so we can maybe find the problem :).
When you start a new project (e.g. with symfony new my_new_project
), one of the files that you should immediately have is config/routes.yaml
, which looks like this:
controllers:
resource:
path: ../src/Controller/
namespace: App\Controller
type: attribute
THAT is what will load the "attributes" routes from your src/Controller
directory. It looks slightly different than what's in the SO post, but it's effectively the same. So that poster IS right that you DO need a file like this... but it should be there without you doing anything :).
Do you have this config/routes.yaml
file in your app? If so, what does it look like?
Cheers!
Thanks weaverryan
Yes, the file config/routes.yml
exists.
But the content is different:
#index:
# path: /
# controller: App\Controller\DefaultController::index
That's the file content given with the project, without being modified.
Hi Alejandro!
Hmm, super weird. Are you starting a new Symfony 5.4 project? Basically, since Symfony 6.0, if you're starting a new app, your file should look like mine. But for 5.4 and earlier, the file would look like your version.
Cheers!
Hello , i did all the coding that i was instructed on this tutorial.
The problem is that when i refresh the page , the Welcome to
Symfony 5.4.24 page is still shown.
Hey @Michael-Kost
I'm guessing your routes are not being loaded for some reason, perhaps because you forgot to update the config/routes.yaml
file. If you execute php symfony console debug:router
what do you see? It should print all of your routes
Cheers!
I ran the following command
symfony console debug:router
and i got the following message:
Name Method Scheme Host Path
_preview_error ANY ANY ANY /_error/{code}.{_format}
yea, it means your routes are not being loaded. Double-check your config/routes.yaml
file. If you're using PHP attributes you need to set the type
key to attribute
When i open the config/routes.yaml file, i get the following
#index:
path: /
#controller: App\Controller\DefaultController::index
Ok, since you're in Symfony 5.4 you'll need to install doctrine/annotations
and, of course use annotations on your routes (unless you're on PHP 8). You can find more info here: https://symfony.com/doc/5.4/routing.html
Cheers!
ok apparently this is broken using annotations so stoping the use of symfony until you figure out how to use it yourselfs. I think that the problem with frameworks is just amplified here. You keep updating the code base and outdating everyones beautiful creations. I do realize that you have to keep up with newer versions of PHP that keep comming out but at least update your beginers tutorials so people new to this would be able to get past the basics and create.
Hey Andromeda,
We're sorry to hear you have trouble with following this course! Could you share more information about your problem? Do you have problems with annotations? Do you see any errors? What errors exactly? Please, give us a bit more context and our support team will help you to fix it and move forward. Also important to know, did you download the course code and run the project from the start/ dir? Did you follow all the instructions in the README.md file? Did you ever run composer update
command in your project?
About this specific course, we do have automated CI on it that tests course code on PHP 8.0 and 8.2 and it does not show any problems in this course. So your problem might be an edge case problem that isn't covered by our CI or it might depend on your specific setup. We definitely would like to know about it so we could make the course code even better.
Unfortunately, project maintenance is a required non-stop process. Along with framework development devs should upgrade their projects as well at least to support new PHP versions as you said.
Cheers!
No errors it just dosn't work there is some issue with symfony that is making the annotation routing not work I tried installing it with composer even and nothing it just doesn't work I suppose I could have fought it harder and more but I choose to give up it is way easier to program php than someone elses interpreation of what php should be. I then know what is going on and don't have to weave through miles of spaggettie and extreme bloatwear to find out what the issue is.
Hey @Andromeda,
The issue is that Symfony doesn't include annotation reader BTW it wasn't a part of symfony at all, it was part of Doctrine library so the easiest way to get annotations back is install doctrine/annotations
package but with version constraint
use:
composer require doctrine/annotations:^1.14
also there might be required one more package: sensio/framework-extra-bundle
I hope we got the correct understanding of the issue you have, also I'd like to know why do you need the support of annotations? From my point of view it's not a perfect solution for metadata it also requires some additional code to work, I mean it's not the part of PHP itself so it may have performance downsides so that's why I'm asking!
Cheers!
Hi there,
This is something I have already mentioned but I still did not find the way to the have an autocompletion with the use. It does not work on my laptop.
https://www.dropbox.com/s/qwjm5qfldtq0jc5/AutoImport.png?dl=0
Hey Benoit,
Did you have Symfony plugin installed? Please, make sure you have it installed and configured, but first - try to upgrade your PhpStorm to the latest version. Then, after all installs and upgrades - make sure to restart the PhpStorm IDE. Then it should work I think :)
Cheers!
Hey there,
My apologies for any inconvenience you may have experienced, but just to corroborate, is this the tutorial you got problems with? I'm asking because this tutorial was built with Symfony 6 and PHP 8
You may need to run composer require annotations
to get the routes to load.
Hey Matthew,
Yes, but only if you're defining your routes with PHP annotations, but in a modern Symfony app, you'd be using PHP attributes
Cheers!
I have created a TopicController with#[Route('/{topic}', name: 'topic.index', methods: ['GET'])
]
and then I created Photographer Controller with#[Route('/photographers', name: 'photographer.index', method: ['GET'])]
Now if I try /anytopic , this goes to topic controller, and /photographers also goes to topic controller.
How can I force /photographers controller to go to Photographer controller?
Hey Ariful,
Good question! But the legacy workaround is simple ;) You just need to change the order of your controllers, i.e. make that "photographer.index" controller going first, it will do the trick. Or, switch to yaml config instead of PHP Attributes for that exact controllers and place them in the correct order there.
Also, since Symfony 5.1 you can set the "priority" for routes, see the blog post with example: https://symfony.com/blog/ne... - so if you're on a newer version of Symfony - this will be the best option for you.
Cheers!
I'm not quite sure where to put this.
I am creating a project in SF6, I created a couple of entities, migrated, yada yada, all good and dandy.
now, some tables have relations, and once I submit a form be it for add or edit, I get this error.
Expected argument of type
"?App\Entity\WalletProvider",
"Doctrine\Common\Collections\ArrayCollection" given at property path
"walletProvider".
it happens when I try to add anything with relationships, I get that the form doesnt send the walletProvider object as is, but I havent modified any of that code that was generated through the make:crud function in the console, any pointers?
Hey jlchafardet
Could you double check your entities relationship? It seems to me that you have a OneToMany instead of a having a ManyToOne or OneToOne. If that's not the case I'd have to see your entitie's metadata and possible the form as well.
Cheers!
it is ManyToOne :S
<blockquote>
Entity<br />#[ORM\ManyToOne(targetEntity: WalletProvider::class, inversedBy: 'coins')]<br />#[ORM\JoinColumn(nullable: false)]<br />private $walletProvider;<br />
FormType
`
$builder
->add('name')
->add('abreviation')
->add('coingecko_url')
->add('walletProvider', null, [
'required' => false,
'multiple' => true,
]);
<br />Controller<br />
#[Route('/new', name: 'app_coin_new', methods: ['GET', 'POST'])]
public function new(Request $request, CoinRepository $coinRepository): Response
{
$coin = new Coin();
$form = $this->createForm(CoinType::class, $coin);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$coinRepository->add($coin);
return $this->redirectToRoute('app_coin_index', [], Response::HTTP_SEE_OTHER);
}
return $this->renderForm('coin/new.html.twig', [
'coin' => $coin,
'form' => $form,
]);
}
`
</blockquote>
Ok, try this in your form type
$builder
->add('walletProvider', EntityType::class, [
'class' => WallerProvider::class,
'required' => false,
'multiple' => false, # attention to this
]);
with multiple => false works, but that goes against everything I'm interested in having... I need it to allow me to select multiple providers.
I see.. then, you need to rethink the relationship of your entities. What you need is to have multiple WalletProvider objects inside your other entity (I believe its Coin). Or, you can create an intermediate entity that will hold the collection of WalletProvider per Coin record
The key here is that your Coin entity is holding a single reference of a WalletProvider object, but what you actually want is to hold a collection of them (OneToMany relationship)
so in this case, what I should do is "start" the relationship of "OneToMany" from walletProvider, or straight up go "ManyToMany" as many wallet providers can hold many coins??
Let me see if I can explain it better.
There are many coins, just as there are more than 1 WalletProviders(at least 2 at this time but could be more in the future)
there are also "Wallets", so each Wallet can hold many coins, but 1 wallet can only belong to 1 WalletProvider.
does that makes more sense?
and to complicate it more, the app manages "users"
so each user can have 1 or more wallet, and each coin can have an "amount" that is tied to both the wallet and the user.
user 1, wallet-from-walletProvider-1,
accepts: coin1 and coin2
coin 1 balance: 22
coin 2 balance: 5
Hey jlchafardet
Ok so, you'll need a ManyToMany relationship on your Coin - WalletProvider entities
Then, the Wallet entity will have a OneToMany to the Coin entity, and a OneToOne (or ManyToOne) to the WalletProvider entity
I hope it helps. Cheers!
Great! For your User relationship it's not that easy to tell because I don't know the use-cases of your application but just keep in mind that you can add an intermediate entity that work like thin wrapper between your relationships, it's quite useful for storing extra fields and for adding behavior to the entities.
The relationships are in theory fairly simple.
Wallet Provider (Binance, KuCoin for example, they offer exchance services for cryptocurrencies[it is not really that important as the app does nothing nor has anything to do with trades, trading or the likes, we want them just because we want to know who provides the eWallet for the users.) its just there for us to know who provides the eWallet service. Each Wallet Provider can have many wallets, and support many coins.
Wallet: the eWallet itself, where the User will receive their coins, each wallet can hold many coins. but belong to a single user, and each user can only register 1 wallet.
Coin: the cryptocurrency our app will manage (we have 2, but might add more in the future), Users earn Coins for doing stuff, sometimes is Coin 1, sometimes is Coin 2, and in the future we might add Coin X.
User: the user itself, email, name, last name, favorite food, name of pet, foot size...... you get the gist of it. Each user can have ONLY 1 wallet, but can receive more than 1 coin to their Wallet.
this is the "MVP" aspect of the app.
I also have a role in user, that is not in use yet, but in a future iteration I want to be able to assign "regular users" to "staff users" to manage. example, we have 100 regular users and 3 staff users, Staff user 1, manages user 1 to 33, Staff user 2 manages user 34 to 67, and staff user 3 manages users 68 to 100.
in that iteration I will want to display in those "staff user" dashboard, a list of their "managed accounts". so they can only see what they should see. but we can get to that later :D.
did this get you a little better picture of what I want to do with the relationships? (another thing important to note is, the app itself does nothing more than parse a bunch of logs that cli tools output, and displays them on screen)
Hi,
I'm following the 'Symfony 6' course on SymfonyCast.
I run the commands:
`symfony new mixed_vinyl
cd mixed_vinyl
symfony serve -d.`
I display the symfony initial page : https://127.0.0.1:8000/
I create the 'VinylController.php' file and the route #[Route('/')] via php attribute.
I refresh the page on my browser
and I get the following error.:
`Cannot rename
"C:\Symfony\mixed_vinyl\var\cache\dev\Container35B09qN\get93B0.tmp" to
"C:\Symfony\mixed_vinyl/var/cache/dev/Container35B09qN/get_Console_Command_ConfigDebug_LazyService.php":
rename(C:\Symfony\mixed_vinyl\var\cache\dev\Container35B09qN\get93B0.tmp,C:\Symfony\mixed_vinyl/var/cache/dev/Container35B09qN/get_Console_Command_ConfigDebug_LazyService.php):
Accès refusé (code: 5)`
cache:clear gives the same error.<br />I have to remove the
cache/dev` directory to continue
If i add the second method with a second route and i refresh the page
I get the same type of error again
Hey Michel,
Oh, this sounds like permissions error on Windows OS. You might need to double-check the permissions for that folder, but if you say that "rm -rf var/cache/" helps only until you edit a file and then the problem returns - most probably it won't help. I'd suggest you to put your project somewhere in a public dir, I see Symfony dir it's located in the root of disk C. Try to create a project somewhere in the user directory and try again, otherwise try to google this problem, most probably some Windows users may give you some hints over the internet.
Also, it might depends on how you run the PHP. If you're using a separate server like XAMPP, I'd recommend you to try to run it without admin permissions, or the reverse if you run it without admin permissions - it might help. Or probably better to install PHP natively in your system and use it directly with Symfony CLI built-in web server.
If no luck, I'd recommend you to take a look at Docker or other virtualization tools. Or probably better take a look at Windows WSL - Windows guys say that it's the best way lately if you're on Windows OS.
I hope this helps!
Cheers!
It seems that the Acronis anti-ransomware app is the cause of this issue
Thanks.
Hey Michel,
Ah, yes, some antiviruses might cause such errors too, good catch!
Cheers!
"Houston: no signs of life"
Start the conversation!
What PHP libraries does this tutorial use?
// composer.json
{
"require": {
"php": ">=8.1",
"ext-ctype": "*",
"ext-iconv": "*",
"symfony/asset": "6.0.*", // v6.0.3
"symfony/console": "6.0.*", // v6.0.3
"symfony/dotenv": "6.0.*", // v6.0.3
"symfony/flex": "^2", // v2.4.5
"symfony/framework-bundle": "6.0.*", // v6.0.4
"symfony/monolog-bundle": "^3.0", // v3.7.1
"symfony/runtime": "6.4.3", // v6.4.3
"symfony/twig-bundle": "6.0.*", // v6.0.3
"symfony/ux-turbo": "^2.0", // v2.0.1
"symfony/webpack-encore-bundle": "^1.13", // v1.13.2
"symfony/yaml": "6.0.*", // v6.0.3
"twig/extra-bundle": "^2.12|^3.0", // v3.3.8
"twig/twig": "^2.12|^3.0" // v3.3.8
},
"require-dev": {
"symfony/debug-bundle": "6.0.*", // v6.0.3
"symfony/stopwatch": "6.0.*", // v6.0.3
"symfony/web-profiler-bundle": "6.0.*" // v6.0.3
}
}
I followed the steps and cannot access to the route, just a 404 error was thrown.
The solution was as this post https://stackoverflow.com/a/73933837
I cannot found any of that requirement in tutorial or documentation. I am wrong?