gstreamer0.10-ffmpeg
gstreamer0.10-plugins-good
packages.
Oh hey there friends! Welcome back to part 2 of our Doctrine in Symfony series... you wonderful database nerds you.
Last time we mastered the basics, but good stuff! Creating an entity, migrations, fixtures, saving, querying and making the perfect omelette... I think. This time, we're going to do some mega study on Doctrine relations.
So let's get our project rocking. To avoid foreign key constraints in your brain while watching the tutorial, I recommend downloading the course code from this page and coding along with me. After unzipping the file, you'll find a start/
directory with all the fancy files that you see here. Check out the README.md
file for all the fun details on how to get this project running.
The last step will be to find a terminal, move into the project and run:
symfony serve -d
I'm using the Symfony binary to start a local web server. Let's go see our site. Spin over to your browser and head to https://127.0.0.1:8000.
Oh, hey there Cauldron Overflow! This is a site where the budding industry of witches and wizards can come to ask questions... after - sometimes - prematurely shipping their spells to production... and turning their clients into small adorable frogs. It could be worse.
The questions on the homepage are coming from the database... we rock! We built a Question
entity in the first tutorial. But if you click into a question... yea. These answers? These are totally hard-coded. Time to change that.
I want you to, for now, forget about any potential relationship between questions and answers. It's really simple: our site has answers! And so, if we want to store those answers in the database, we need an Answer
entity.
At your terminal, let's generate one. Run:
symfony console make:entity
Now, as a reminder, symfony console
is just a fancy way of saying php bin/console
. I'm using the Docker & Symfony web server integration. That's where the Symfony web server reads your docker-compose.yaml
file and exposes environment variables to the services inside of it. We talked about that in the first Symfony 5 tutorial. By using symfony console
- instead of running bin/console
directly - my commands will be able to talk to my Docker services... which for me is just a database. That's not needed for this command, but it will be for others.
Anyways, run this and create a new entity called Answer
. Let's give this a few basic properties like content
which will store the answer itself. Set this to a text
type: the string
type maxes out at 255 characters. Say "no" to nullable: that will make this column required in the database.
Let's also add a username
property, which will be a string. Eventually, in the security tutorial, we'll change this to be a relationship to a User
entity. Use the 255 length and make it not nullable.
Oh, and one more: a votes
property that's an integer
so that people can up vote and down vote this answer. Make this not nullable and... done! Hit enter one more time to finish.
... lines 1 - 2 | |
namespace App\Entity; | |
use App\Repository\AnswerRepository; | |
use Doctrine\ORM\Mapping as ORM; | |
/** | |
* @ORM\Entity(repositoryClass=AnswerRepository::class) | |
*/ | |
class Answer | |
{ | |
/** | |
* @ORM\Id | |
* @ORM\GeneratedValue | |
* @ORM\Column(type="integer") | |
*/ | |
private $id; | |
/** | |
* @ORM\Column(type="text") | |
*/ | |
private $content; | |
/** | |
* @ORM\Column(type="string", length=255) | |
*/ | |
private $username; | |
/** | |
* @ORM\Column(type="integer") | |
*/ | |
private $votes; | |
public function getId(): ?int | |
{ | |
return $this->id; | |
} | |
public function getContent(): ?string | |
{ | |
return $this->content; | |
} | |
public function setContent(string $content): self | |
{ | |
$this->content = $content; | |
return $this; | |
} | |
public function getUsername(): ?string | |
{ | |
return $this->username; | |
} | |
public function setUsername(string $username): self | |
{ | |
$this->username = $username; | |
return $this; | |
} | |
public function getVotes(): ?int | |
{ | |
return $this->votes; | |
} | |
public function setVotes(int $votes): self | |
{ | |
$this->votes = $votes; | |
return $this; | |
} | |
} |
Before we generate the migration, go open up that class: src/Entity/Answer.php
. So far... there's nothing special here! It looks pretty much like our other entity. Oh, but if you're using PHP 8, then the command may have generated PHP 8 attributes instead of annotations. That's great! They work exactly the same and you should use attributes if you can.
At the top of the class, add use TimestampableEntity
. We talked about that in the last tutorial: it adds nice createdAt
and updatedAt
properties that will be set automatically.
... lines 1 - 6 | |
use Gedmo\Timestampable\Traits\TimestampableEntity; | |
... lines 8 - 11 | |
class Answer | |
{ | |
use TimestampableEntity; | |
... lines 15 - 77 | |
} |
Oh, and one other thing: default the votes to zero. I made this column not nullable in the database. Thanks to this = 0
, if we do not set the votes on a new answer, instead of getting a database error about null
not being allowed, the Answer
will save with votes = 0
.
... lines 1 - 11 | |
class Answer | |
{ | |
... lines 14 - 32 | |
/** | |
* @ORM\Column(type="integer") | |
*/ | |
private $votes = 0; | |
... lines 37 - 77 | |
} |
Now let's generate the migration. Find your terminal and run:
symfony console make:migration
As a reminder, this command is smart: it looks at all of your entities and your actual database structure, and generates the SQL needed to make them match. Go check out that new file... it's in the migrations/
directory. And... perfect! CREATE TABLE answer
... and then it adds all of the columns.
... lines 1 - 2 | |
declare(strict_types=1); | |
namespace DoctrineMigrations; | |
use Doctrine\DBAL\Schema\Schema; | |
use Doctrine\Migrations\AbstractMigration; | |
/** | |
* Auto-generated Migration: Please modify to your needs! | |
*/ | |
final class Version20210902130926 extends AbstractMigration | |
{ | |
public function getDescription(): string | |
{ | |
return ''; | |
} | |
public function up(Schema $schema): void | |
{ | |
// this up() migration is auto-generated, please modify it to your needs | |
$this->addSql('CREATE TABLE answer (id INT AUTO_INCREMENT NOT NULL, content LONGTEXT NOT NULL, username VARCHAR(255) NOT NULL, votes INT NOT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); | |
} | |
public function down(Schema $schema): void | |
{ | |
// this down() migration is auto-generated, please modify it to your needs | |
$this->addSql('DROP TABLE answer'); | |
} | |
} |
Run the migration with:
symfony console doctrine:migrations:migrate
All good! Our database now has a question
table and an answer
table. Next, let's relate them.
Hi,
But why do you want to upgrade it? Upgrades are easy, you can learn about them for example here https://symfonycasts.com/screencast/symfony6-upgrade
But if you upgrade this code to 6.2 probably you will get lot of issues in next chapters 'cause not everything can be supported
Cheers!
Hi
I think you forgot to put
$answer->setCreatedAt(new \DateTime('now'));
$answer->setUpdatedAt(new \DateTime('now'));
Hello, thank you for great work. But I have problems with the installation. I am using postgres and I get blocked by sql errors while doing the migrations. Any quick solution?
Hey Steb!
Oh, yeah... our migrations are created for MySQL server, you can see it when open any migration. If you want to use a separate DB server - you would need to re-create those migration for your DB server. But the quick solution would be to do not use our migrations at all and instead execute:
bin/console doctrine:database:create
bin/console doctrine:schema:update --force
This will create the DB schema from scratch, but keep in mind that you may lose some data if you already have a DB with good data, but if no good data in your DB or you don't care if some information will be lost - then great.
Another solution would be to recreate all the migrations from scratch, you need to drop the DB, then create a new empty one, and run "bin/console make:migration" that will create a migration for your DB server. Then just migrate like we do in the README.
Cheers!
Hey Steb,
Perfect! Yeah, creating schema via "doctrine:schema:create" command or using "doctrine:schema:update" is a good trick locally :) Migrations in turn are mostly needed for production.
Cheers!
Thank you for all the amazing work. One question I had is how do we use php8 attributes to replace Gedmo annotations? I switched doctrine's config to use attributes instead of annotations and replaced all the ORM annotations for the Question entity, however I am not sure how that works for the Gedmo extension. Also, is there a way to regenerate entity classes with attributes instead of annotations :D
Hey Sajeev N.
Unfortunately Gedmo is not ready for PHP8 attributes =( There is a pull request to add support, but it's still a draft https://github.com/doctrine...
So you have 2 choices:
1. do not use attributes until gedmo updates
2. do not use gedmo and add everything you need manually (sometimes it's not to hard)
Cheers!
Hello SymfonyCast Team - once again my big gratitude for your work! Amazing tutorial!
With you're huge help I have managed to finished my studies and my ecommerce project - and finally I got employed as a junior php dev.
THANK YOU SYMFONYCAST ❤️
Hope you will continue your awesome work!!
I have an of topic question - maybe you could suggest any tuts/resources besides documentation for Sylius, as for begginers.
Would really appreciate it. :)
Hey Andrejus.
Congratulations! You made our day ;) And good luck with your job as PHP dev!
We're going to release a course about Sylius some day, but not plans to release it in the near future, sorry! Also, I don't have any specific recommendations unfortunately, but I bet you may find something on the internet. Also, the best friend is always docs, so make sure to check it as well: https://sylius.com/ . And looks like they even have their own online course about it: https://sylius.com/online-c... - but I can't tell you anything about it.
Cheers!
Thank you for amazing tutorial! You are perfect there !
Q: Where is link to Doctrine part 1? Or it is mean Symfony 4 course?
P.s. Something happend with your voice, too tired or after Covid....
Hey Mepcuk!
Ah, I'm so happy you're liking it! ❤️
> Q: Where is link to Doctrine part 1? Or it is mean Symfony 4 course?
Yup, it's the Symfony tutorial with Doctrine - so, this one: https://symfonycasts.com/sc...
> P.s. Something happend with your voice, too tired or after Covid
Oh man, I didn't realize! Hopefully it's temporary - I don't have a cold or illness at the moment... so I'm not sure what might have happened. Well, my son HAS been missing a TON of school... so maybe it's just tired ;).
Cheers!
Hello!
Just to say thanks a lot for the new videos. Keep up the incredibly work, we appreciate <3
Hey Production P.
Thank you so much for the feedback <3 We appreciate it so much! Keep learning and enjoy videos :)
Cheers!
I glad to see new video for Doctrine. Thnak you! ;)
BTW, if it possible , Could you add pause/unpause functionality video for toch screens? I mean , like for desctop PC, when I can click in the middle of video frame. For Desktop PC I see big "Play" button, for my iPad no. May be I'm wrong and it's related with Safary only.
Hey Ruslan,
Thank you one more time for this suggestion! It was just implemented by our friend @sadikoff and should be available on production from now on :) Please, give it a try and feel free to share your feedback with us if you want ;)
Cheers!
Hey Ruslan!
Ah, I see that! That's annoying! We'll see if we can make this better for you - and I'm happy that you're happy about the new videos!
Cheers!
// composer.json
{
"require": {
"php": ">=8.1",
"ext-ctype": "*",
"ext-iconv": "*",
"babdev/pagerfanta-bundle": "^3.3", // v3.3.0
"composer/package-versions-deprecated": "^1.11", // 1.11.99.3
"doctrine/doctrine-bundle": "^2.1", // 2.4.2
"doctrine/doctrine-migrations-bundle": "^3.0", // 3.1.1
"doctrine/orm": "^2.7", // 2.9.5
"knplabs/knp-markdown-bundle": "^1.8", // 1.9.0
"knplabs/knp-time-bundle": "^1.11", // v1.16.1
"pagerfanta/doctrine-orm-adapter": "^3.3", // v3.3.0
"pagerfanta/twig": "^3.3", // v3.3.0
"sensio/framework-extra-bundle": "^6.0", // v6.2.1
"stof/doctrine-extensions-bundle": "^1.4", // v1.6.0
"symfony/asset": "5.3.*", // v5.3.4
"symfony/console": "5.3.*", // v5.3.7
"symfony/dotenv": "5.3.*", // v5.3.7
"symfony/flex": "^1.3.1", // v1.17.5
"symfony/framework-bundle": "5.3.*", // v5.3.7
"symfony/monolog-bundle": "^3.0", // v3.7.0
"symfony/runtime": "5.3.*", // v5.3.4
"symfony/stopwatch": "5.3.*", // v5.3.4
"symfony/twig-bundle": "5.3.*", // v5.3.4
"symfony/validator": "5.3.*", // v5.3.14
"symfony/webpack-encore-bundle": "^1.7", // v1.12.0
"symfony/yaml": "5.3.*", // v5.3.6
"twig/extra-bundle": "^2.12|^3.0", // v3.3.1
"twig/string-extra": "^3.3", // v3.3.1
"twig/twig": "^2.12|^3.0" // v3.3.2
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.3", // 3.4.0
"symfony/debug-bundle": "5.3.*", // v5.3.4
"symfony/maker-bundle": "^1.15", // v1.33.0
"symfony/var-dumper": "5.3.*", // v5.3.7
"symfony/web-profiler-bundle": "5.3.*", // v5.3.5
"zenstruck/foundry": "^1.1" // v1.13.1
}
}
I'm trying to update my code that I've already created from previous S6 tutorial videos to align with the changes made ahead of this series to include stimulus and webpack encore. I believe I've added all of the dependecies I need and am running npm run watch to watch and bulid my webpack for my site. However, I'm getting the below error. I'm not familiar enough with nodejs to understand. Is it saying I'm missing webpack-encore? I've previously run composer require symfony/webpack-encore-bundle so that shouldn't be it. Also ran npm install encore just to be sure and am still getting the same issue.
`
root@2236ca7d93cd:/var/www/symfony_docker# npm run watch
node:internal/modules/cjs/loader:959
throw err;
^
Error: Cannot find module '../lib/config/parse-runtime'
Require stack:
at Module._resolveFilename (node:internal/modules/cjs/loader:956:15)
at Module._load (node:internal/modules/cjs/loader:804:27)
at Module.require (node:internal/modules/cjs/loader:1022:19)
at require (node:internal/modules/cjs/helpers:102:18)
at Object.<anonymous> (/var/www/symfony_docker/node_modules/.bin/encore:13:22)
at Module._compile (node:internal/modules/cjs/loader:1120:14)
at Module._extensions..js (node:internal/modules/cjs/loader:1174:10)
at Module.load (node:internal/modules/cjs/loader:998:32)
at Module._load (node:internal/modules/cjs/loader:839:12)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12) {
code: 'MODULE_NOT_FOUND',
requireStack: [ '/var/www/symfony_docker/node_modules/.bin/encore' ]
}
Node.js v18.6.0
`
<b>UPDATE</b> I finally gave up trying to figure this out and just downloaded the sample file package, deleted my previous work, and started from the start directory. After running composr install, npm install and npm update, npm run watch is now working. I'm still curious on what this was if anybody has any thoughts.