Chapters
23 Chapters
|
2:22:24
|
Login to bookmark this video
-
Course Code
Subscribe to download the code!Compatible PHP versions: ^7.4.1
Subscribe to download the code!Compatible PHP versions: ^7.4.1
-
This Video
Subscribe to download the video!
Subscribe to download the video!
-
Subtitles
Subscribe to download the subtitles!
Subscribe to download the subtitles!
-
Course Script
Subscribe to download the script!
Subscribe to download the script!
23.
Timestampable & Failed Migrations
Scroll down to the script below, click on any sentence (including terminal blocks) to jump to that spot in the video!
Subscribe to jump to this part in the video!
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.
This tutorial also works great for Symfony 6!
What PHP libraries does this tutorial use?
// composer.json
{
"require": {
"php": "^7.4.1",
"ext-ctype": "*",
"ext-iconv": "*",
"composer/package-versions-deprecated": "^1.11", // 1.11.99
"doctrine/doctrine-bundle": "^2.1", // 2.1.1
"doctrine/doctrine-migrations-bundle": "^3.0", // 3.0.2
"doctrine/orm": "^2.7", // 2.8.2
"knplabs/knp-markdown-bundle": "^1.8", // 1.9.0
"knplabs/knp-time-bundle": "^1.11", // v1.16.0
"sensio/framework-extra-bundle": "^6.0", // v6.2.1
"sentry/sentry-symfony": "^4.0", // 4.0.3
"stof/doctrine-extensions-bundle": "^1.4", // v1.5.0
"symfony/asset": "5.1.*", // v5.1.2
"symfony/console": "5.1.*", // v5.1.2
"symfony/dotenv": "5.1.*", // v5.1.2
"symfony/flex": "^1.3.1", // v1.21.6
"symfony/framework-bundle": "5.1.*", // v5.1.2
"symfony/monolog-bundle": "^3.0", // v3.5.0
"symfony/stopwatch": "5.1.*", // v5.1.2
"symfony/twig-bundle": "5.1.*", // v5.1.2
"symfony/webpack-encore-bundle": "^1.7", // v1.8.0
"symfony/yaml": "5.1.*", // v5.1.2
"twig/extra-bundle": "^2.12|^3.0", // v3.0.4
"twig/twig": "^2.12|^3.0" // v3.0.4
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.3", // 3.4.0
"symfony/debug-bundle": "5.1.*", // v5.1.2
"symfony/maker-bundle": "^1.15", // v1.23.0
"symfony/var-dumper": "5.1.*", // v5.1.2
"symfony/web-profiler-bundle": "5.1.*", // v5.1.2
"zenstruck/foundry": "^1.1" // v1.5.0
}
}
43 Comments
Hi
How do we deal with daylight savings time? All my "created at" and "updated at" entries are using UTC.
Thank you
Steve
Smack me with a wet fish... php.ini timezone setting.
Hey Steve, yep, PHP gets the date from the server, and you can configure the timezone as a PHP setting or at the moment of creating your date objects.
Cheers!
could not add created at and updated at due to i didn't install stoff composer, tryed all the solutions in comments but with no luck,
if i didn't add ctreatedat and updated fields to the table will that be a problem in the next videos ?
Hey SouFiane,
What exact error do you see? We could try to help you install that bundle. It should be possible in case you download the course code and started from the start/ directory. But in general, you can miss those fields and it would be be OK... but you would need to keep it in mind and do not use those fields in migrations, DB queries, etc. You can use different fields you have on that entity instead, it's fine for learning purposes ;)
Let us know the exact error you see in case you want to make it installed into your project.
Cheers!
A bit confused about this particular part and the steps afterwards. What would you have to do if it was already on production with many rows of data? The next step says to be safe and reset the db with `doctrine:database:drop --force` but what if this was production?
Hey Kevin!
Yea, this is tricky. What I do locally to test this migration is not what we would on production. Let me elaborate:
LOCALLY:
We are in a weird state because we have a half-executed migration. We fix it... but then we're not totally sure that it's working until we properly test it. So... we do all the "dropping the database" and re-adding the fixtures in order to get our database back into the "state" that it was BEFORE we ran that migration: a state that has the old database structure AND rows of data in it. Then we can re-try the migration.
PRODUCTION
But on production, we don't have this problem. Imagine this workflow:
A) We write some code, we deploy, we run some migrations. Everything is happy, and our "question" table DOES have real data on it.
B) Locally, we add the new created_at column to Question. We generate a migration, run it and it explodes! We are now in the situation described in this chapter. But, we go through the process, fix it, and test it.
C) THEN, we finally deploy our "already fixed" migration. This migration now runs just fine on production because the broken migration was never executed there.
So, tl;dr is that we "jump through hoops" locally to ensure that our migration IS fixed. But ultimately, when we deploy, we do nothing different than normal: we just deploy. When we do, this migration (which is now fixed) will execute just one time, successfully.
Let me know if that clarifies :).
Cheers!
If this were a production scenario, and you decided to start keeping this metadata in your questions table -- created_at, updated_at -- after some rows had already been created without it, you couldn't just set those fields to NOW(). Well, you could, but it wouldn't be accurate. Am I right? Theoretically, if you had complete logs somewhere -- web server logs, perhaps -- you could undertake the chore of parsing those and trying to reconstruct... it sounds painful.
Maybe I am overthinking this. It's just an example for the sake of the tutorial, right?
Hey davidmintz!
> you couldn't just set those fields to NOW(). Well, you could, but it wouldn't be accurate. Am I right? Theoretically, if you had complete logs somewhere -- web server logs, perhaps -- you could undertake the chore of parsing those and trying to reconstruct... it sounds painful.
You're absolutely right :). In my experience, this has been so painful (and not SO important) that we've never bothered :). But of course, there could definitely be mission-critical situations where this WOULD be important, and then you'd have some work to do! However, my instinct is that if you have some records where the created_at and updated_at *must* be 100% accurate, then you probably (not 100%, but probably) would have remembered to include them when you first created your entity.
> Maybe I am overthinking this. It's just an example for the sake of the tutorial, right?
So overall, this isn't just an example for the tutorial: this is how I would do things in most cases. If I DID need to dig through some logs to set the real date, i'd probably allow the field to be nullable and deploy. Then I would build a custom console command I could run that would look for null dates, dig for their real date (however you're doing that), then populate those fields. Once that's done, i'd deploy the "not null" change.
Cheers!
yeah that makes sense.
Oh I see!! I think I get it now!
So because we created extra migrations on local to fix the db issues we would only have to deploy/run it on production and the columns will be added without any problems and will also have the default values that were set in the second query.
Unlike Laravel migrations, symfony migrations are generated for specific database like pgsql or mysql. isn't it? So, once you generate the migration for postgres and changing the database later at some point, the migrations won't work. Am I wrong?
Hey Syed,
I'm not sure how it works in Laravel, but yes, you're right, Doctrine generates database-specific migrations. And yes, if you generate it for PgSQL, you won't be able to run it for MySQL... and the vice versa. Why? Well, first of all, because different DB should have different queries, i.e. MySQL query won't work 100% on PgSQL and the reverse, right? So, it's made this way for you, to avoid unexpected problems and prevent losing data.
In case you want to migrate to a different DB at some point, you should do it manually, and then you don't need all those old migrations actually, you can just drop them and start creating new migrations if needed, but already for the new DB. In other words, having some migrations until today that will apply to MySQL and some of them since today that will apply to PgSQL sounds very weird :)
I hope this helps!
Cheers!
Your words are crystal clear to me.
And regarding how Laravel does the migration. It uses the environment variable to pick the default database/connection you want to run the migrations. Builds queries on the fly. Runs them against the default db connection. And also, you can specify the db connection in the migration files to use any specific db if you don't want to apply the migrations to the default database.
Hey Syed,
Yeah, then the Laravel way sounds similar to what Doctrine migrations bundle does :)
Cheers!
Hi victor
I guess the doctrine migrations are different.
In Laravel, they write classes for migrations, and when you run migrations, then those classes are compiled to SQL Queries, and then runs against the currently defined database connection. It can be pgsql or mysql or anything when they are running.
An example: https://github.com/ssi-anik...
On the other hand, the symfony migrations are SQL statements already prepared for specific database when you're generating. So, if you change the connection later from MySQL to PostgreSQL, will they run correctly for that database?
Here is an example in my case, I might be wrong.
`$this->addSql('CREATE UNIQUE INDEX UNIQ_B6F7494E989D9B62 ON question (slug)');`
Thanks.
Hey Syed,
Fairly speaking, I'm not aware how Laravel migrations work, but if they work like you described - you're right, they work different from Doctrine migrations. And yes, you're right, Doctrine migrations create raw SQL queries at the moment you generate a migration, and then the system will execute those queries when you run the migrate command. From this, yes, if you generate the migration for MySQL - you won't be able to run this migration on other SQL platforms, i.e. Doctrine migrations are platform-specific. And it makes sense, because migrations are supposed to help with changing production DB without loosing data, not changing the DB provider, etc.
I hope this helps!
Cheers!
Thanks man. <3
I'm thinking that we can now use the 'createdAt' field in place of the askedAt field.
Hey Sherri
You're right, in this case, it would render basically the same date.
Cheers!
Alternate approach, you can set a default value in the migration for MySQL version 5.6.5+:
$this->addSql('ALTER TABLE question ADD created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, ADD updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP');Hey Sherri,
Thanks for sharing this tip with others. Hm, fairly speaking I'm not sure you will have the valid mapping after this migration, you can check if Doctrine is happy with this running:
$ bin/console doctrine:schema:validate
The problem is that in your example you change the schema, i.e. you will have a slightly different schema than we have in the video, and Doctrine may not like this. In our example we use the schema that is generated by Doctrine, i.e. Doctrine is happy about that schema, and then we just modify the data in that table, no schema is affected.
Cheers!
So if I wanted the default value set in the table schema (for example if there are other applications accessing the table and I want to force the default value)... would I have to copy the trait into my entity and set the default values on the entity properties too?
Answered my own Question. To keep the entity and migration in sync.... just override the properties of the trait in your entity and add the default value to the annotations:
This gives you a valid mapping and schema when running
php bin/console doctrine:schema:validate.This is useful <b>if</b> you want SQL to also enforce the default values.
Hey Sherri,
Thanks for sharing your solution with others! Yes, you have to reflect any changes to the schema in the entity's mapping, i.e. in PHP annotations in your case, so you did it correct :)
Cheers!
Thanks for the three tutorials about fundamentals ! It was very nice to build the code with you from scratch. From now, what topic could I follow that starts with more or less the same code than this one ?
Hey Antoine R.!
Excellent! I'm so glad you found them useful! From here, unfortunately, we don't (yet) continue *this* exact code in any tutorial. I've gotten a bit delayed (my fault... and the fault of other tutorials) in releasing other Symfony 5 tutorials. However, though they use a different project, the Symfony 4 versions of the next few tutorials - are still quite good and relevant. They are:
* Doctrine relations: https://symfonycasts.com/sc...
* Security: https://symfonycasts.com/sc...
* Forms https://symfonycasts.com/sc...
And from there, the topics get a bit more specific depending on what you're looking for. Sorry I can't get you a better answer about tutorials with THIS exact code, but I hope these will serve you well :).
Cheers!
Quick question: after adding the TimestampableEntity trait, I checked the generated migration file, and noticed, it actually didn't add the default current_timestamp and on update current_timestamp definitions that are set in the annotations of its two properties. I checked Ryan's video again, they're not there either. So how is it going to work? Is there some mechanism behind Symfony's curtains that will take care of this? Why is it better than defining the columns in MySQL and let it handle these things? I have my doubts. :-P
Hey Balázs S.
IIRC this timestampable works via listeners defined in bundle, that's why it's not necessary to configure MySQL structure for it. Honestly I can't say why it's done so, my guess is that you have more control on DateTime objects and timezones.
Cheers!
Thanks, Vladimir. Well, I don't like it too much because it assumes that only our beloved Symfony app will access and write into the database. If data could come from different sources, and those other sources are not prepared to handle timestamps themselves, it will lead to inconsistencies in the database. I'm not saying that it could not happen if you leave this to MySQL to handle, it certainly could, but you have to be really determined to screw this up. So went back to manually edit migrations and addig ON UPDATE CURRENT_TIMESTAMP() into certain field declarations.
Love you guys. Cheers.
I guess your schema is not synced with this manual changes, and
make:migrationwill always generate query to remove this configuration. Also you should configure correct timezones for mysql and php to be sure that you will always have correct dates =)Cheers!
Ho Rayan! Thank you for this wonderful tutorial! I have question why you use always
sprintfrather than the oldecho?Thank you!
Hey @Houssem Zitoun!
> Thank you for this wonderful tutorial
Really happy you enjoyed it!
> have question why you use always sprintf rather than the old echo?
The true answer is that sprintf() is used a lot in Symfony's code base, and I learned to use it from there ;). But in general, it's definitely better than concatenating strings and variables. And, I'm not sure if there is a good reason why, but you don't see "Double quotes with $variables inside" very often in the Symfony world. But overall, it doesn't really matter - do what you like - no *real* advantage/disadvantage between these options :).
Cheers!
Thank you so much for yet another fantastic tutorial! So glad I came across this website!
I just have a question. How can I save my database?
I'm so new to Docker, I tried to commit my container into a new image to run it whenever I need my data but it didn't work.
I googled for a solution and I did find this:
`
# Backup
docker exec CONTAINER /usr/bin/mysqldump -u root --password=root DATABASE > backup.sql
# Restore
cat backup.sql | docker exec -i CONTAINER /usr/bin/mysql -u root --password=root DATABASE
`
It works great, but I just want to know if there's a better solution.
Thank you again for your amazing work.
Hey Mohammed!
Yes, this is an excellent question - and when we designed make:docker:databse, we thought about "should we make the data persistence"? So the answer is "volumes" - it's basically a way where you can make a directory from inside the container (the directory that actually holds the MySQL data) be shared onto your local file system. The result is that, when you shutdown and even delete your container, that directory is still there in your project. Then when you start your container again, that directory will be inside the container.
I have an example here; https://github.com/weaverryan/js-workshop-qro/blob/3b0111abb6425df36ab17d4cf9753e706df6c36d/docker-compose.yaml#L12 - the
docker/database/datadirectory is the local directory (in your project) where you will see the directory. The other path is the directory in the container.Let me know if this helps! I'm also not a Docker expert - but I've really been enjoying this new way of using it :).
Cheers!
Please make the next courses about events and how to persist vars with sessions or pass them to different controllers.
Hey n0
Thanks for your feedback. Could you detail a bit more what you picture about working with events? Do you mean Symfony events, right?
Working with the session variables in Symfony is quite simple, there is a service that allows you to set/get variables from the session, I recommend you to read this docs to get a better idea https://symfony.com/doc/cur...
By the way, controllers should not communicate between each other. They should work independently
Cheers!
Just for reference, I talked to MolloKhan on a different thread and (actually) he solved his own issue. The answer was basically storing things in the session or using Doctrine cache (to avoid queries on each page) :)
Hi, thanks for a great course, now I am yet more thrilled about Symfony! Still, I have one question: should I wait for the Symfony 5 Doctrine Relations tutorial (if you are planning one) or start with the one for Symfony 4? Are there many differences regarding these two versions?
Hey Tomik,
Unfortunately, no any plans to release it soon, so probably if you watched the Doctrine tutorial for Symfony 5 course: https://symfonycasts.com/sc... - I'd recommend you to go with the old Doctrine relations tutorial. Nothing much is changed in it, and what exactly changed to Symfony 5 and Doctrine were covered in the https://symfonycasts.com/sc... so you should be aware of it I think. Just do things in Symfony 5 ways and it should be enough.
And as always, if you stuck on something following the tutorial on Symfony 5 - just let us know in comments and we will help you to figure out the problem. And if you discover a BC break - we will even add a note to the tutorial that would help other people to follow it on Symfony 5.
Cheers!
Please weaverryan, I need help.
I try to work with redux and react router dom. I have two projects api part at https://github.com/petre-symfony/api-part-biblioteca and redux part at https://github.com/petre-symfony/redux-part-biblioteca-. In api part you have a folder named Dump20200830. There are SQL dumps made with MysqlWorkbenck.
Run
and you should have around 2200 books there. They are my parent's books.
I created a interface in react and redux side to display these books and their details. In api side I created the posibility to look them by author.
If you write in adress bar localhost:3000 you'll see under authors paragraph some links. If you hover over them you'll see something like localhost:3000/1/author=whateverAuthor and click on them suppose to list only the books written by this respective author. When I write localhost:3000 all books are listed with pagination. The second task works. First task is built on the skeleton of the second and uses the same action creator. And doesn't work by intended design. Somehow I see the Ajax Request over the network that returns the correct response. So the books property should exist in redux store. But in this second case it gets null when render the BookListContainer component. It's peculiar because seems to follow the same flow as localhost:3000 follows. In localhost:3000 is no problem: books property,returned by bookListReducer state, contains the list of books that's displayed at the time of rendering the component. But when I put the author part I see the book in bookListReducer state , that contains the correct response (I checked that), but dissapears at the time of rendering BookListContainer. I don't know why and where to modify to make this work. I put some console.logs in the code to see this.
I tried to be as explicit as I can in my commits.
Thank you for your effort.
Hi Diaconescu!
Sorry for my slow reply! Ok, let's see what we can figure out! First, while I know React pretty well, I do *not* know Redux. So if this is more of a question about Redux... I don't think I'm going to be able to help. If this is a question more about Symfony or ApiPlatform, I might be able to. But right now, it looks like most of the problem is inside Redux somewhere? Of so, like I said, I don't think I'll be much help :/. Let me know.
Cheers!
Ok, I really think that the problem is in Redux. Thank you anyway. I'll look somewhere else for help
"Houston: no signs of life"
Start the conversation!