This tutorial has a new version, check it out!

Activating Timestampable

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.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Ok, 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.

The TimestampableEntity Trait

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!

Up Next: Relations!

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.

Leave a comment!

  • 2020-05-07 Diego Aguiar

    Hey Deniz Cetin

    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!

  • 2020-05-06 Deniz Cetin

    Hi weaverryan ! 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?

  • 2020-04-21 Victor Bocharsky

    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!

  • 2020-04-21 Satheesh Sankar S

    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!

  • 2020-02-04 weaverryan

    Hey Mike!

    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/sc... 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!

  • 2020-02-01 Mike

    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.

  • 2019-11-05 Victor Bocharsky

    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!

  • 2019-11-05 Bagar Davidyan

    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

  • 2019-11-05 Victor Bocharsky

    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!

  • 2019-11-04 Bagar Davidyan

    Hi Victor! I have thoughts about updatedAt field default value: isn't it need to be NULL from start, before making any actions more than creation?

  • 2019-08-21 Victor Bocharsky

    Hey Ozornick,

    Oh, we're sorry for killing your own lovely code :) But less code - less bugs ;)

    Cheers!

  • 2019-08-19 Ozornick

    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

  • 2019-03-11 Victor Bocharsky

    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!

  • 2019-03-09 Ahmedbhs

    Does stof_doctrine_extensions Bundle need Doctrine extension Bundle ?

  • 2018-12-17 weaverryan

    That’s the greatest compliment :). Cheers!

  • 2018-12-17 Victor Bocharsky

    Hey Edin,

    Glad you discovered something new ;) And thank you for your feedback!

    Cheers!

  • 2018-12-16 Edin

    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!

  • 2018-07-10 Diego Aguiar

    Hey Skylar Scotlynn Gutman

    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!

  • 2018-07-10 Skylar Scotlynn Gutman

    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?

  • 2018-06-13 Gilles Hoarau

    Thanks! Looking forward to learn even more and high five an ice cream!