If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeOk, let's add Timestampable! First, we need to activate it, which again, is described way down on the bundle's docs. Open config/packages/stof_doctrine_extensions.yaml
, and add timestampable: true
:
... lines 1 - 2 | |
stof_doctrine_extensions: | |
default_locale: en_US | |
orm: | |
default: | |
sluggable: true | |
timestampable: true |
Second, your entity needs some annotations. For this, go back to the library's docs. Easy enough: we just need @Gedmo\Timestampable
.
Back in our project, open Article
and scroll down to find the new fields. Above createdAt
, add @Timestampable()
with on="create"
:
... lines 1 - 10 | |
class Article | |
{ | |
... lines 13 - 55 | |
/** | |
* @ORM\Column(type="datetime") | |
* @Gedmo\Timestampable(on="create") | |
*/ | |
private $createdAt; | |
... lines 61 - 190 | |
} |
Copy that, paste above updatedAt
, and use on="update"
:
... lines 1 - 10 | |
class Article | |
{ | |
... lines 13 - 61 | |
/** | |
* @ORM\Column(type="datetime") | |
* @Gedmo\Timestampable(on="update") | |
*/ | |
private $updatedAt; | |
... lines 67 - 190 | |
} |
That should be it! Find your terminal, and reload the fixtures!
php bin/console doctrine:fixtures:load
No errors... but, let's make sure it's actually working. Run:
php bin/console doctrine:query:sql 'SELECT * FROM article'
Yes! They are set! And each time we update, the updated_at
will change.
I love Timestampable. Heck, I put it everywhere. And, fortunately, there is a shortcut! Yea, we did way too much work.
Check it out: completely delete the createdAt
and updatedAt
fields that we so-carefully added. And, remove the getter and setter methods at the bottom too:
... lines 1 - 10 | |
class Article | |
{ | |
... lines 13 - 55 | |
/** | |
* @ORM\Column(type="datetime") | |
* @Gedmo\Timestampable(on="create") | |
*/ | |
private $createdAt; | |
/** | |
* @ORM\Column(type="datetime") | |
* @Gedmo\Timestampable(on="update") | |
*/ | |
private $updatedAt; | |
... lines 67 - 167 | |
public function getCreatedAt(): ?\DateTimeInterface | |
{ | |
return $this->createdAt; | |
} | |
public function setCreatedAt(?\DateTimeInterface $createdAt): self | |
{ | |
$this->createdAt = $createdAt; | |
return $this; | |
} | |
public function getUpdatedAt(): ?\DateTimeInterface | |
{ | |
return $this->updatedAt; | |
} | |
public function setUpdatedAt(?\DateTimeInterface $updatedAt): self | |
{ | |
$this->updatedAt = $updatedAt; | |
return $this; | |
} | |
} |
But now, all the way on top, add use TimestampableEntity
:
... lines 1 - 6 | |
use Gedmo\Timestampable\Traits\TimestampableEntity; | |
... lines 8 - 11 | |
class Article | |
{ | |
use TimestampableEntity; | |
... lines 15 - 157 | |
} |
Yea! Hold Command
or Ctrl
and click to see that. Awesome: this contains the exact same code that we had before! If you want Timestampable, just use this trait, generate a migration and... done!
And, talking about migrations, there could be some slight column differences between these columns and the original ones we created. Let's check that. Run:
php bin/console make:migration
No database changes were detected
Cool! The fields in the trait are identical to what we had before. That means that we can already test things with:
php bin/console doctrine:fixtures:load
Thank you TimestampableEntity
!
Ok guys! I hope you are loving Doctrine! We just got a lot of functionality fast. We have magic - like Timestampable & Sluggable - rich data fixtures, and a rocking migration system.
One thing that we have not talked about yet is production config. And... that's because it's already setup. The Doctrine recipe came with its own config/packages/prod/doctrine.yaml
config file, which makes sure that anything that can be cached easily, is cached:
doctrine: | |
orm: | |
metadata_cache_driver: | |
type: service | |
id: doctrine.system_cache_provider | |
query_cache_driver: | |
type: service | |
id: doctrine.system_cache_provider | |
result_cache_driver: | |
type: service | |
id: doctrine.result_cache_provider | |
services: | |
doctrine.result_cache_provider: | |
class: Symfony\Component\Cache\DoctrineProvider | |
public: false | |
arguments: | |
- '@doctrine.result_cache_pool' | |
doctrine.system_cache_provider: | |
class: Symfony\Component\Cache\DoctrineProvider | |
public: false | |
arguments: | |
- '@doctrine.system_cache_pool' | |
framework: | |
cache: | |
pools: | |
doctrine.result_cache_pool: | |
adapter: cache.app | |
doctrine.system_cache_pool: | |
adapter: cache.system |
This means you get nice performance, out-of-the-box.
The other huge topic that we have not talked about yet is Doctrine relations. But, we should totally talk about those - they're awesome! So let's do that in our next tutorial, with foreign keys, join queries and high-fives so that we can create a really rich database.
Alright guys, seeya next time.
Hey Ahmed,
Could you clarify what exactly "Doctrine extension Bundle"?
Yes, StofDoctrineExtensionsBundle requires "gedmo/doctrine-extensions" package as the bundle is just a wrapper for that library that has integration with Symfony if you mean this.
Cheers!
Hi guys, how does it work if I want to create a timestampable deleted_at ?
does the @Gedmo\Timestampable(on="delete") is supported too ?
I would like to have that information to keep a record when user "soft deleted" his account to keep a record for the 3 years storaged required as per law.
Many thanks in advance for your help.
https://sharemycode.fr/ni4
Can I show seconds from January 1st 1970 in Twig, like in PHP time()? I cant find it on the documentation.
Hey Farry7,
Sure, you can... Well, you need a datetime object first :) With the "date()" Twig function you can create a datetime object right in the template, and since it's a datetime object - print the timestamp as "{{ date().timestamp }}"
Another way would be to format a string with "|date" filter, e.g. "{{ 'now'|date('U') }}" will format the current time to the UNIX timestamp.
If both you don't like, pass the time() value from the controller to the template, or even create your own Twig filter for that PHP time() call :)
Cheers!
Hi there ! It's me a gain, as promised! :D
Now, I am not quite sure what to google for, that's why I thought I'd ask here. I want my app to do the following thing:
A user defines on a page a certain "object". Let's say there's a form and the form can take a variable amount of inputs. Now those inputs are here to define the database column names. Let's say my user wants to create a "Delivery address"-object/table (whatever you want to call it) and fills out the form with the fields "name", "address", "phone number", etc. ...
Aussming the user submitted the data correctly- what would be the best practice to generate dynamic database tables from here? Every user can have multiple, different tables, since the objects he creates can be different each time.
I have no clue how to do some research on this topic? Where do I begin?
To clarify the situation: Currently I am stuck at a point, where I am generating entities and I noticed, that some of my entities can have "sub"-entities, which I don't know how they look like yet.
Thank you very much!
EDIT: From there the generated objects need to have a relationship to mentioned entity from earlier. I can't wrap my head around this, since we're talking about things, that don't exist yet.
EDIT2: Is the approach of "let the user create his object/table, then somehow make a migration automatically" a thing? If so, how?
Hey denizgelion
That's a very good question and to be honest I'm not an expert when talking about dynamic tables in the Database but I googled around and found this SO question that may help you out or at least give you some ideas https://stackoverflow.com/q...
If you'll stick with a relational database (as it's MySql) I think you can build that system up on top of Doctrine DBAL
Cheers!
hi,
i am using symfony 5. i got this error while running php bin/console doctrine:fixtures:load
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'created_at' in 'field list'
please help!
Hey Satheesh,
Basically, if you're running this project locally, I suppose you just need to run "bin/console doctrine:schema:update --force" - this will update your schema to match entities mapping. In case you wonder what queries will be executed - run "bin/console doctrine:schema:update --dump-sql" instead to see the actual queries, and then run with "--force" flag to make those changes.
But the reason behind this error might be different, it's difficult to say something more without understanding of what exactly you're doing. Did you download the course code and started from start/ folder? Did you follow the steps in start/README.md file? Probably you missed some steps from README, or didn't run migrations, etc.
Cheers!
I use SF 5.0.4 with the latest package/recipe updates and have a problem using timestampable with SF Forms.
I use the timestampable trait inside my entity:
use TimestampableEntity;
And inside SF Forms, I bind that form to my entity via:
'data_class' => Article::class,
But when I submit the form, I get an error that "data.createdAt and data.updatedAt should not be null".
Afte Googling the problem, the reason seems to be the new feature of SF since 4.3 "automatic validation" (Source).
I found two solutions:
1.) A Workaround:
Setting the value manually: Source
2.) A "real" fix via the new Annotation (Source):
/**
* @Assert\DisableAutoMapping()
* ...
*/
protected $createdAt;
...
It works, when I insert this Annotation inside the Trait.
But I shouldn't edit the trait itself, because its a vendor file, and if an update of timestampable is released, my code change will be overwritten.
So my question:
How do I tell SF Form that createdAt and updatedAt from the timestampable trait should be "ignored"/"will be automatically filled" in the validation process?
It seems to be my best option to copy the "original" trait to /src/Trait/TimestampableEntity.php with my changes and use my own trait instead?
Or what is the most professional solution?
Sidenote:
I have the same error for "sluggable should not be null", but the fix is easy here, I just use the annotation.
Hey Mike P.!
Boooooo. I mean, boooo because you've identified a really annoying situation where the auto-mapping stuff works really poorly! I'll say that IU like the auto-mapping feature a lot - we talk about it here https://symfonycasts.com/screencast/symfony5-upgrade/auto-mapping for people who are not familiar - and I also really like this use of traits. However, I can't think of a way for them to work cleanly together.
About your solutions, I don't see any real issue with (1) - initializing the values in the constructor. That shouldn't be necessary, so it's kind of annoying, but it's really a solid way to do it. For solution (2), it's in some ways, hackier than (1) - you're re-declaring the property just to disable auto-mapping. That's a bummer.
Unfortunately, the library author also can't fix things by adding the @Assert\DisableAutoMapping()
to their class as this would cause an error in any projects that aren't using Symfony's validator component.
So...
It seems to be my best option to copy the "original" trait to /src/Trait/TimestampableEntity.php with my changes and use my own trait instead?
Or what is the most professional solution?
This is what I would do :). You're ultimately using the trait because it's convenient. If you need to do a workaround in every entity, it's not convenient anymore. Fortunately, I don't see any big problem with this: this code is dead-simple and will never change. The trait is provided by the library as a convenience. There may be some way that Symfony allows this to be solved in the future in a more "professional" way - but I don't think there is right now.
Cheers!
Hi <b>Victor</b>! I have thoughts about <b>updatedAt
</b> field default value: isn't it need to be <b>NULL
</b> from start, before making any actions more than creation?
Hey Bagar!
Good question! Well, it depends on your strategy, you can do whatever is better for you. Though, IIRC Timestampable strategy on StofDoctrineExtensionsBundle also update updatedAt on creating, most probably for technical reasons I think. It will allow you to get correct results on ordering your DB entries. If some of them would be NULL - you would get weird ordering results, you can try and see how it works in MySQL DB. Because NULL fields in SQL databases are "special", you need to use a different where conditions for them, etc. So, sometimes avoid NULL values is a good idea. But generally, it's not too much important I think, you just need to understand how it works in your project to avoid some WTF moments later.
Does it makes sense to you?
Cheers!
Yes Victor, of course, thank you!
Unfortunately, there is another problem I've faced recently:
https://github.com/stof/Sto...
hope that @stof will back ASAP
Hey Bagar,
Ah, I see... well, since it's just a deprecation - it should still work, most probably until Symfony 5.0 where all the deprecations will be removed, but I see your point. There's another bundle called KnpLabs/DoctrineBehaviors that also has timestampable behavior, but I haven't used it for a while, not sure how active it is, but just in case you're wondering. But if you need the only one timestampable behaviour - it's easily to implement it yourself in case you want to practice even listeners, but advantage of using libraries is that they well tested and solve some edge cases you may miss in your implementation. But it's an open source, so everybody can pick that task and try to fix the problem, then suggest the fix as a PR. If you have some time - you're also welcome to try to fix those deprecations ;)
Cheers!
Check it out: completely delete the createdAt and updatedAt fields that we so-carefully added. And, remove the getter and setter methods at the bottom too:
And then I cried...
Thanks for the course
Hey Ozornick,
Oh, we're sorry for killing your own lovely code :) But less code - less bugs ;)
Cheers!
Being a sf + doctrine user for years I could not believe I would learn anything new - I just went for the badge, but you proved me wrong. I still learned a thing or two.
Great job guys!
I have used doctrine for years now and I keep learning new things that make it so much better. Thanks.
May I ask, when will the next course come out? Should I review the Symfony 3 Forms: Build, Render & Conquer! for the latest info on forms or is there something better to review?
Hey Skylar
Right now we are delivering the ReactJS tutorial and after that, we will release Symfony4 security tutorial. So, if you are looking for something specific to Symfony forms, then watching our Symfony3 tutorials about that topic would be a good idea, and anyways, it didn't change dramatically :)
Cheers!
// composer.json
{
"require": {
"php": "^7.1.3",
"ext-iconv": "*",
"composer/package-versions-deprecated": "^1.11", // 1.11.99
"knplabs/knp-markdown-bundle": "^1.7", // 1.7.0
"knplabs/knp-time-bundle": "^1.8", // 1.8.0
"nexylan/slack-bundle": "^2.0,<2.2.0", // v2.0.0
"php-http/guzzle6-adapter": "^1.1", // v1.1.1
"sensio/framework-extra-bundle": "^5.1", // v5.1.4
"stof/doctrine-extensions-bundle": "^1.3", // v1.3.0
"symfony/asset": "^4.0", // v4.0.4
"symfony/console": "^4.0", // v4.0.14
"symfony/flex": "^1.0", // v1.17.6
"symfony/framework-bundle": "^4.0", // v4.0.14
"symfony/lts": "^4@dev", // dev-master
"symfony/orm-pack": "^1.0", // v1.0.6
"symfony/twig-bundle": "^4.0", // v4.0.4
"symfony/web-server-bundle": "^4.0", // v4.0.4
"symfony/yaml": "^4.0" // v4.0.14
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.0", // 3.0.2
"easycorp/easy-log-handler": "^1.0.2", // v1.0.4
"fzaninotto/faker": "^1.7", // v1.7.1
"symfony/debug-bundle": "^3.3|^4.0", // v4.0.4
"symfony/dotenv": "^4.0", // v4.0.14
"symfony/maker-bundle": "^1.0", // v1.4.0
"symfony/monolog-bundle": "^3.0", // v3.1.2
"symfony/phpunit-bridge": "^3.3|^4.0", // v4.0.4
"symfony/profiler-pack": "^1.0", // v1.0.3
"symfony/var-dumper": "^3.3|^4.0" // v4.0.4
}
}
Does stof_doctrine_extensions Bundle need Doctrine extension Bundle ?