Doctrine 2 "Behaviours" in a Nutshell

Posted about 1 year ago by beberlei

This blog entry relates to an outdated Doctrine 2 Alpha version. Please see the documentation for the most up to date behavior.

One of the most common fallacies out there about Doctrine 2 abandoning Behaviours is that developers now have to implement fancy logic to re-implement them yourself. Doctrine 2's approach to completly separate ORM from your domain classes allows to build behaviours in a very clean, unobstrusive and simple object-oriented way. This article shows you how to implement some of the Doctrine 1 behaviours in your Doctrine 2 code. For this I will rewrite the Doctrine 1.x manuals examples for each Behaviour.

This example uses Annotations as example, yet this of course works with YAML and XML mappings. Additionally Doctrine 2 allows constructors to have required arguments as of a commit of the last week. This allows for some pretty slick enforcements in user-land code as you will see in this post.

Straightforward combination of "Behaviours"

All the code listed below is somehwat more verbose than the Doctrine 1 code, however much more bound to the domain of your model and very straightforward. There is no magic involved and you will fully understand what will be happening in each of the behaviours. The best of all, although not a simple trick, you will be able to combine ALL behaviours in one model class and still be completly on top of their inner workings.

Timestampable

Timestampable is a behaviour that requires you to hook into the pre-update event which is called whenever an entity is updated:

<?php

/**
 * @Id
 * @HasLifecycleCallbacks
 */
class BlogPost
{
    /**
     * @Column(type="DateTime")
     */
    private $created;

    /**
     * @Column(type="DateTime")
     */
    private $updated;

    public function __construct()
    {
        // constructor is never called by Doctrine
        $this->created = $this->updated = new DateTime("now");
    }

    /**
     * @PreUpdate
     */
    public function updated()
    {
        $this->updated = new DateTime("now");
    }
}

Sluggable

The sluggable behaviour is trivial to implement in Doctrine 2:

<?php

class BlogPost
{
    /** @Column(type="string") */
    private $slug;

    /** @Column(type="string") */
    private $title;

    public function setTitle($title)
    {
        if ($this->slug == null) {
            $this->slug = MyStringHelper::slugize($title);
        }
        $this->title = $title;
    }

    /**
     * Put this method in if your slug should be "editable"
     */
    public function setSlug($slug)
    {
        $this->slug = $slug;
    }
}

See how its much more explicit in your code how and why the slug is generated.

NestedSet

This is one of the more complex behaviours in Doctrine 1 and it won't be necessarily more easy in Doctrine 2. However as this is an important feature we will provide an implementation as a DoctrineExtensions namespaced package that will be maintained by Doctrine Devs.

Searchable

There is currently no plan to port the Searchable behaviour to Doctrine 2, but the possibility to instantiate objects using new allows a very simple integration of a Doctrine 2 model with Apache Solr or Lucene with a little wrapper that re-creates detached instances from this powerful search engines.

For example using ezcSearch we can make our BlogPost accessible for Solr:

<?php

class BlogPost implements ezcBasePersistable, ezcSearchDefinitionProvider 
{
    public function getState()
    {
        return array(
            'id' => $this->id,
            'title' => $this->title,
            'body' => $this->body,
            'slug' => $this->slug,
        );
    }

    public function setState($state)
    {
        foreach ($state AS $k => $v) {
            $this->$k = $v;
        }
    }

    static public function getDefinition() 
    {
        // define search schema
        return $def;
    }
}

ezcSearch can then index a blog post whenever it is changed by hooking an EventListener into the Doctrine PreUpdate Event:

<?php

class EzcSearchListener
{
    private $_searchSession;

    public function __construct(ezcSearchSession $searchSession)
    {
        $this->_searchSession = $searchSession;
    }

    public function preUpdate(LifecycleEventArgs $args)
    {
        if ($args->getEntity() instanceof ezcBasePersistable) {
            $this->_searchSession->index($args->getEntity());
        }
    }
}

You can now hook this event into Doctrine's EntityManager:

<?php

$searchListener = new EzcSearchListener(...);
$em->getEventManager()->addEventListener(
    array(Doctrine\ORM\Events::preUpdate), $searchListener
);

Now when you search for your entities you get returned BlogPost instances from ezcSearchs Solr interface:

<?php

// initialize a pre-configured query
$q = $session->createFindQuery( 'BlogPost' );
$searchWord = 'test';

// where either body or title contains thr $searchWord
$q->where(
    $q->lOr(
        $q->eq( 'body', $searchWord ),
        $q->eq( 'title', $searchWord )
    )
);
$searchedBlogPosts = $session->find( $q ); 

These instances are detached from the EntityManager when they get returned from ezcSearch and can be merged back into the persistence context:

<?php

$searchedBlogPosts[0]->setTitle("ChangeFoo");
$em->merge($searchedBlogPosts[0]);

Read about Merging, Detached instances and other cool stuff of Doctrines object model in the Working with Objects chapter of the manual.

Versionable

By default Doctrine 2 comes with a way to set a version column that is automatically incremented on each update. Using the event system it is easy to use this information to implement a versionable audit-log behaviour. The required code is more verbose than the simple configuration of Doctrine 1, however there is much less magic involved and you can implement this behaviour in a way that is trivial to understand for someone new looking at your code:

<?php

/**
 * @Entity
 * @HasLifeCycleCallbacks
 * @generatedValue(strategy="AUTO")
 */
class BlogPost
{
    /**
     * @Id
     * @Column(type="integer")
     */
    private $id;

    /**
     * @Column(type="string")
     */
    private $title;

    /**
     * @Column(type="text")
     */
    private $body;

    /**
     * @Column(type="integer")
     * @version
     */
    private $version;

    /**
     * @OneToMany(targetEntity="BlogPostVersion", mappedBy="post")
     */
    private $auditLog = array();

    /**
     * @PrePersist
     * @PreUpdate
     */
    public function logVersion()
    {
        $this->auditLog[] = new BlogPostVersion($this);  
    }
    // getters
}

/**
 * @Entity
 */
class BlogPostVersion
{
    /**
     * @Id
     * @Column(type="integer")
     * @generatedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @Column(type="string")
     */
    private $title;

    /**
     * @Column(type="text")
     */
    private $body;

    /**
     * @Column(type="integer")
     */
    private $version;

    /**
     * @ManyToOne(targetEntity="BlogPost")
     */
    private $post;

    public function __construct(BlogPost $post)
    {
        $this->post = $post;
        $this->title = $post->getTitle();
        $this->body = $post->getBody();
        $this->version = $post->getCurrentVersion();       
    }
}

I18N

Multi-Language content is an important topic and can be implemented in Doctrine 2, since its just a fancy name for a One-To-Many relation. However currently Doctrine 2 does not allow to persist keys by name, which makes a OneToMany implementation a bit more intensive then it could be. We plan to implement primitive value collections however which would simplify any attempt to implement nested structured content, that is not an entity by itself.

Soft Delete

We won't support soft-delete at all. If you want to implement a soft-delete alike behaviour its probably a good idea to look into the State pattern instead.

Blameable

Implementing this behaviour is just a matter of adding two fields createdByUserId and modifiedByUserId fields and setting them whenever one of your relevant fields change by hooking into setter methods:

<?php

/**
 * @Entity
 */
class BlogPost
{
    /**
     * @Column(type="string")
     */
    private $title;

    /**
     * @Column(type="integer")
     */
    private $modifiedByUserId;

    public function updateBlogPost($title, ..., User $user)
    {
        $this->title = $title;
        $this->modifiedByUserId = $user->getId();
    }
}

Sortable

Same as I18N, we are planning to support persistence of collection keys in the Doctrine 2 Core. This would allow to sort collections by using the possibilities of the Doctrine\Common\Collections\Collection interface.

Conclusion

Although slightly more complex than Doctrine 1s simple configuration options, most "behaviours" are still way easy to implement in Doctrine 2. The additional benefit of this straightforward approach: You can combine behaviours in any way, inside your domain model, without having to wonder how the magic works together, you are completly on top of it.


Comments (47) [ add comment ]

Re-use Posted by Jonathan about about 1 year ago.

So if I want to have more than one model be "Timestampable", I have to duplicate that code there too? Is there any kind of pseudo-multiple inheritance like templates are in version 1?

better way? Posted by jphilip about about 1 year ago.

The whole purpose of behaviors is not to have to hard code them in each model which is what I understand is mostly done here. There has to be a better way. ActAs came with ROR I believe as Ruby supports mixins. Could we use interfaces and dependency injection through a DI container?

comment Posted by beberlei about about 1 year ago.

@Jonathan:

Yes, there is the concept of @MappedSuperclass, that is you can introduce an abstract base class that everyone inherites from, which contains this information. (@PreUpdate Callback and the two $created and $updated) fields.

However using this approach your domain model becomes unclear, since every class is a "timestampable" or something.

@jphilip:

You can use @postLoad to inject dependencies via setter methods into entities.

If desired, you can use the event system, an abstract base class, with EntityManager dependency to make Doctrine 2 as magic as Doctrine 1 and work almost like it. Its just nothing that we would support out of the box

. Posted by LC about about 1 year ago.

I'm quite new to Doctrine and when I think of it, "Timestampable" was one of the things that teased me to learn and use this ORM.

I understand it is part of a bigger refactoring but I don't see the benefits of removing or complicate the good stuffs.

Stepback Posted by hm about about 1 year ago.

It's just like a big step backwards comparing to Doctrine 1 (however other parts of Doctrine 2 I really like) which won't allow to migrate our applications to Doctrine2 relatively easy in the future.

great Posted by romanb about about 1 year ago.

@Jonathan: If you have forgotten how to achieve simple code reuse in plain OOP, then "actAs" has really messed with your mind.

This is a step forward and not backwards because it acknowledges that actAs in PHP is a heavily broken approach to horizontal code reuse. If you want to complain about that, complain to PHP for not having traits/mixins (and for not having first-class metadata, a syntax that doesnt suck, ... ;-).

If you so desire, build your own base class with your own broken actAs and you can even share it as an extension with others, so that others get the advantage of the broken actAs magic, too!

Sorry, but I have just nothing good to say about this actAs/behavior stuff. Its conceptually fragile and broken and it messes with your mind.

An example Posted by esycat about about 1 year ago.

Could you please provide an example how exactly two or more entity classes can share the functionality required to reproduce Timestampable behaviour as well as the same mappings defined in YAML/XML?

Simple code reuse in OOP Posted by Jonathan about about 1 year ago.

Re @MappedSuperclass: OK, every class now extends Timestampable. What if I want my models to be Sluggable too?

I don't see how it is possible to "reuse" class members and methods that need to be explicitly added to the class (like the columns).

I also don't see how actAs is "broken". Is there a ticket about some specific problem? Templates stepping on each others' toes? You seem to feel that mixins in PHP would be a solution, but isn't that a potential problem in any multiple inheritance/mixin situation?

If I write a behavior and want to share it, it seems kind of ridiculous to instruct my users to "add these columns and methods to your class". What if they want to upgrade? What if I deploy a behavior to 10 models and then find a bug? I have to fix it in 10 places?

Maybe I'm missing something obvious but how can I add Timestampable/Sluggable behaviors to two different models with "simple code reuse in plain OOP".

Maybe I just don't know Doctrine 2 well enough. If both models maybe have a $timestampable variable to hold a Timestampable behavior object (simple code reuse in plain OOP via composition), how could it add its columns and preSave hooks?

Thanks for tips Posted by Andy about about 1 year ago.

Thanks for the info, I was wondering what was happening about the missing behaviours.

My main interest is in not losing functionality like the nested sets.

but @romanb, completely ignoring the original user base and telling them to get ****ed doesn't sound like a smart move, whether or not the new course of Doctrine is going in the right direction.

Nested Set Posted by Andy about about 1 year ago.

I'm very curious about how Nested Sets will be implemented above the object layer, whether it will be able to use database indexes and depend on the entity manager.

I still can't get my head around how to use Doctrine 2 for complex functions that for example requires querying the database for values within a date range.

The only 2 options I see is implement the lookup in OOP which is sub-optimial in that instance, doing what would in effect be a full table scan, or requiring dependency on the entity manager in order to do a query.

code reuse Posted by romanb about about 1 year ago.

@Jonathan: Code reuse has nothing to do with Doctrine. How you avoid code duplication in object-oriented code has nothing to do with Doctrine. If you want to know how good actAs works, search through the trac and jira tickets.

Sorry, but I find the way you demand answers to questions you should really try to answer yourself pretty strange. I/We are not here to solve all your problems for you or to teach object-oriented programming. We provide an open source project that you can use or not, free of charge.

If you want to learn more about horizontal code reuse through traits, and how it differs from multiple inheritance and deals with problems like the diamond problem, just do a google search. That will answer your questions. It's usually solved differently in different implementations.

@Andy: I dont see how we are ignoring anyone. We just move the project in a direction of our choice, one that we think is the best and we certainly can't make everyone happy, you never can. Whatever direction you go, for some people its the right one and for others the wrong one. It doesnt really matter. What matters is that we move into a direction that we (the constributors) believe in, because thats what moves a project forward.

We found the "behavior" implementation to be ugly, error-prone, in code-reuse limited and bound to doctrine (you cant reuse a "behavior" without using doctrine), a mess when multiple behaviors step on each others toes, etc. So we dropped it. End of story.

You're not dependant on Doctrine. If you like the actAs concept of Doctrine 1, just write it yourself (or just use Doctrine 1), I said that previously. You can even share it with others as a Doctrine extension if you want.

Hope that clarifies things a bit. We just do what we think is right. You can think otherwise, of course.

@Andy Posted by romanb about about 1 year ago.

Nested set will not be developed as part of the Doctrine project. Noone has started development yet. A nested set would certainly need to work together with an EntityManager, at least thats the easiest way.

"I still can't get my head around how to use Doctrine 2 for complex functions that for example requires querying the database for values within a date range."

Well, DQL has not been dropped :-)

@romanb Posted by Jonathan about about 1 year ago.

You wrote an entire blog post, all about "simple object-oriented" solutions, and you can't be bothered to respond to additional criticism or concerns from your community with anything but RTFM and cracks about how you're not going to teach "simple object-oriented" solutions.

You act like re-use of the above code is trivial. Tell us, how is it possible to "re-use" the updated and created columns when they are hard-coded as class members?

@Jonathan Posted by romanb about about 1 year ago.

1) I didn't write the blog post.

2) Inheritance, interfaces, composition (in which case the properties are on another class), ... And yes, this can mean duplicating properties eventually (i.e. when using interfaces). You think "virtual" properties and actAs/behaviors are a good solution to this? Fine, We don't. We think the only good solution to this would be language-level constructs as discussed previously.

Just experiment a bit with some simple classes and interfaces, playing with ways to combine them and you'll see all the possible solutions, where properties would have to be duplicated, etc. There are probably more possibilities than you think :-)

Again, you can do what you want, just because there is no "actAs" in Doctrine does not mean you can't just do this yourself.

wrong post title Posted by Tom about about 1 year ago.

The title of this post is very misleading, as one assumes to find solutions for doctrine 2 which somehow "emulate" the behaviour concept of doctrine 1.x.

The main idea behind the doctrine 1.x behaviours are their reusability. In the end they are a concept to inject funtionality in a very DRY way into new or existing entities.

Now reading the title of this post "Doctrine 2 "Behaviours" in a Nutshell" - I espected a similar solution for doctrine 2.

This is where this post fails - the shown solutions are in no way portable to another entity. The examples simple show how one could implement a desired functionality - not how this functionality can be mady available to a number of other entities.

In my opinion: good post (althoug trivial) - wrong post title!

@Tom Posted by romanb about about 1 year ago.

How to make the solutions reusable if you need them several times is a task that is left to the reader :-)

I am glad you found this post trivial because then it should be similarly trivial for you to make such functionality reusable.

@romanb Posted by Tom about about 1 year ago.

well.. its trivial to write a function which generates a slug of a string and saves it - it is not trivial to find a good approach to share such functionality (and other) with lots of objects.

As far is I understand your argument - you are saying, that you dont like the "actAs" pattern and therefore you dropped the support. I respect this decision - i have to :-) - and i understand, that doctrine2 has a more basic approach towards ORM - concentrating on the core ORM functionality.

My concern is, that projects that use doctrine (example symfony plugins) are less portable now, because every plugin developer now has to decide how he will implement basic behaviours like "timestamp"/"softdelete" etc. Most other ORM do have a way to share such behaviours and with doctrine being the exception it could hurt the adoption of doctrine.

Furthermore I think you have to understand one thing: You have made this design decision for doctrine a looong time ago.. probably you have thought about it and argued about it a lot. For users like me (or Jonathan) this decision new and therefore we ask questions "why/whynot".

And one last thing :-) A blog post that actually showed how one could share functionality between entities would help people understand your point.

Why Open Source Sucks Posted by Jon about about 1 year ago.

This is exactly why using "free" software sucks.

I remember reading similar comments from the creator of Ruby about how he didn't care what the users wanted/needed. Go right ahead and design your software however you want while the rest of us stand around in disbelief, scratching our heads as to why we thought Doctrine would be better than our old ways. You should name your next version Vista.

Frustration? Posted by romanb about about 1 year ago.

@Jon: You sound very frustrated and you're really not making sense.

You can never always get what you want, whether the software is free or paid or open or closed source.

Drawing the conclusion that we dont care about what users want/need "in general" from the fact that we dropped a feature that you apparently liked very much but that was never really part of the core functionality of the project, in the sense that it really does not have much to do with ORM, is very short-sighted.

In fact, it is a very bad idea to always just blindly do anything users want/demand. You can make everyone happy for a little while, until they start to complain about the bloated mess that is the result of adding any feature anyone has ever asked for.

@Tom: You have a valid point, of course. And you're spot on about the facts that a) we didnt do this decision overnight and b) that its not part of core ORM functionality, on which we'd like to concentrate.

The "problem" with combining and/or reusing the "behaviors" mentioned here is that there is more than one way, instead of just the one "actAs"-way. And what way is most appropriate would heavily depend on the domain model of the project. We are considering showing some examples of combinations in a future post.

Another "problem" we see with a generalized and Doctrine-specific "behavior" approach is that the reusability is really limited to using Doctrine. Outside of a Doctrine-based project its not reusable at all.

A major problem is another one. One of the main goals in Doctrine 2 is to not couple your domain objects to the persistence layer. That way they're easily testable and reusable. A doctrine-specific behavior implementation would very quickly get us back to where we started in that regard.

Not understanding Posted by jwage about about 1 year ago.

I think the main point people are not understanding here is that it's not that we are against "behaviors" or wanting to give you what you want, it's that we KNOW that it cannot be implemented in PHP properly given the current state of the language. Please don't confuse this. By all means, if someone can prove us wrong and implement this functionality within the limits of the PHP OO architecture, then we would love to be proven wrong. But no offense, we've gone round and round on this topic and it's impossible to do with PHP.

@jwage Posted by coding horror about about 1 year ago.

it's that we KNOW that it cannot be implemented in PHP properly given the current state of the language

I assume you don't like multiple inheritance. So what what language feature would solve this problem?

An additional question: would you subclass the entity manager to get something like Versionable behaviour? I was thinking how I would solve it. It is inevitable that you don't call $entityManager->persist($blogPost) anymore, but would you use a BlogPostService that instantiates a VersioningEntityManager? When reading my own question I think no, but you ever know :D

Second: would you consider it is bad if the versioning logic is duplicated across domain objects?

@doctrine-devs: please be patient with your community. Even a very beautifully architected framework needs some momentum in order to be succesfull. Your decisions seem to be right, but keep in mind that the skillset of the average php programmer was way less not so long a time ago.

Domain vs structural behaviours Posted by Andy about about 1 year ago.

I agree that it doesn't make sense for an ORM to handle domain behaviours such as Sluggable and Timestampable, but structural behaviours such as NestedSet's (allowing optimized ancestor and decendant searches) have a analog in other object-orientated databases e.g. XPath's \

Would it not then be benefitial for Doctrine to support structural behaviours?

The common domain behaviours could instead be provided by the Delegate design pattern without dependency on Doctrine DBAL/ORM.

As you already require us to become dependent on Doctrine\Common, there could be the possibility of putting them in that namespace.

@coding horror Posted by jwage about about 1 year ago.

Traits would allow for more proper horizontal code re-use. This feature almost made it into php 5.3, but we'll have to wait until another version.

@coding horror Posted by beberlei about about 1 year ago.

You can use an EventListener that implements a completly generic audit log behaviour. This is a very good idea to show a more complex behavior replacement for Doctrine 2.

Versionable as in optimistic locking and version increments is already a core feature of DC2.

Versionable Posted by alexey_baranov about about 1 year ago.

First of all I want to thank the developers and the author. Doctrine 2 is much slimmer than the first version because of the separation of models from MPAs.

About versioning. Can I get Entity Manager at the time $em->persist($post) check annotations @versioning table="post_log" and save the current post state in the specified table? If versioning is not included in your circle of work, how do you think is it possible to expand EntityManager by VersionedEntityManager this way myself?

Thanks.

about trees Posted by alexey_baranov about about 1 year ago.

When I think about the fact that the models are separated from the ORM, the trees seem like this

//simple tree structure $car = new Car(); $cdPlayer = new CDPlayer(); $cdPlayer-> parent = $car; $cd = new CD(); $cd-> parent = $cdPlayer;

// storing all $em->persist ($car); $em->persist ($cdPlayer); $em->persist ($cd);

//fetching root object $car = $query->getSingleResult("car id = 1");

// working with tree structure foreach ($car->childs as $child) ( echo $child->parent; )

foreach ($car->directChilds as $child) ( echo $child->parent; )

again, is it possible to add into the model annotation @tree annotation and ask EntityManager to return for such models proxies with additional propertys childs and directChilds, which like other properties of the proxy objects do lazy download?

Thanks

trees Posted by alexey_baranov about about 1 year ago.

When I think about the fact that the models are separated from the ORM, the trees seem like this

//simple tree structure

$car = new Car();

$cdPlayer = new CDPlayer();

$cdPlayer-> parent = $car;

$cd = new CD();

$cd-> parent = $cdPlayer;

// storing all

$em->persist ($car);

$em->persist ($cdPlayer);

$em->persist ($cd);

//fetching root object

$car = $query->getSingleResult("car id = 1");

// working with tree structure

foreach ($car->childs as $child) (

echo $child->parent;

)

foreach ($car->directChilds as $child) (

echo $child->parent;

)

again, is it possible to add into the model annotation @tree annotation and ask EntityManager to return for such models proxies with additional propertys childs and directChilds, which like other properties of the proxy objects do lazy download?

Thanks

Custom fields? Posted by Silence about about 1 year ago.

Hi! I haven't read all the comments here, but, how about custom fields? It will be available in doctrine 2, no?

Maybe we can use them to create a timestampable field, etc.

I get it Posted by Matt about about 1 year ago.

PHP is broken. That's a fact, I went through the new presentation Jonathan created and I'm trying to keep an open mind but in the end I feel like we're sacrificing functionality for the sake of perfect code. Behaviors are an awesome feature and I realize there are some complications with using them and writing code in the most correct purest form, but sometimes sacrificing some purity for functionality is acceptable in my opinion.

For the time being I'm going to sit back and watch. I've got an app half finished using doctrine 1 and using behaviors quite successfully.

I get the feeling that doctrine 1 will be completely abandoned once doctrine 2 goes live. I don't know if I feel comfortable supporting it alone. Leaving me to wonder if I should strip out all the doctrine 1 in my current app. It would take me ages to duplicate that functionality however, and my code would not be of the same quality level so I will most likely keep it and deal with any security issues/bugs that come up as time goes on.

@beberlei you said it best when you said: "However using this approach your domain model becomes unclear, since every class is a "timestampable" or something."

That puts things into perspective for me and I agree that's kind of a problem unless you set the expectation that you'll deal with it ahead of time. Is there any hope of continued maintenance for Doctrine 1? Maybe call it 'Doctrine 1: Flawed but fun' as it captures the fact that it's not a perfect solution but also that it is quite useful.

All that being said, I really appreciate all the great work you guys do. I'm sure doctrine 2 will be great but it is going to be a painful switch for me.

Painful to switch Posted by Pokky about about 1 year ago.

I am fairly new to Dcotrine, and just as Matt above I have a half finished application (my first using Doctrine).

I happily started using Timestampable, Sluggable and Soft Delete, and now to my horror I find out that Soft Delete won't be supported in Doctrine 2.

What I am reading into the above is that although some of the other Behaviours can be easily done, the Soft Delete behaviour can't be.

That effectively boxes me in to a dead-end path, and I think the frustration from others here is from that same fact.

Let's face it - if your application works perfectly with behaviours using Doctrine 1.2, then it's easy to stay with the current version.

And that's what I will have to do - unless Soft Delete can relatively easily be implemented.

But that effectively stops me providing full support for the application when v1.2.1 is not supported by the developers anymore. I'm a decent programmer, but there's no way I am currently capable of producing something like Doctrine. Normally,I would be able to continue support by upgrading to the next version, but by losing backwards compatibility this is no longer easily achieved.

My problem here is that the person who is paying for the project wouldn't really know if I spend an hour to implement a feature that already existed, just so I can use the newest and greatest version of the library. So that would be fine. Therefore losing the Sluggable behaviour doesn't seem to be such a big thing as from the code above it seems trivial to implement.

However, they would know for sure if I had to spend 10 hours implementing Soft Delete - and if I have to change every single query, test it, and check the impact on every part of the system, as well as implementing the Soft Delete functionality itself in all parts of the application then it would take quite a while.

And I am not interested in funding the move to a new library myself.

Is there no chance that a "Compatibility Library" that would ease the transition into v2?

Or someone providing a set of extra Listeners etc that simulates the behaviours of the past?

My advice would be, if you are willing to take it, is to have prepared by launch of v2 a comprehensive set of blog posts to explain how to implement each of the existing behaviours using v2, even for difficult to implement features such as Soft Delete.

As an example, take a look at http://jquery14.com/ where they went through changes using code and video presentations and more for the transition from v1.3 to v1.4.

I quite understand you developers are far to busy, so why not offer it out to the community and work with someone who has heavily invested in the current behaviours and therefore will have a very vested interest in creating a smooth transistion?

Based on my experience of communities, I am sure that people are venting their frustration here (and probably elsewhere) as they feel their needs are being completely ignored. By being able to communicate that their needs are being taken care of with a set of comprehensive migration instructions I am sure the community will be rallying around the move to v2 rather than complaining and fighting it.

Just my 2 cents.

make it easy to share code Posted by Lukas about about 1 year ago.

i also agree that behaviors are an incredibly attractive feature (even though i find many current behaviors to be lacking in terms of scalability). its also clear that with D2 its alot easier to do things yourself. that being said, if we want people to be able to share (and stack) "behaviors" in D2 then for now it would make sense for D2 to offer a more or less defined way to implement things in a somewhat consistent manner especially because there is no obvious way to do it natively in PHP yet (i take partial blame for the lack of traits in 5.3).

blog spam Posted by Matt about about 1 year ago.

Jonathan,

I sent you an email regarding cleaning up the blog spam. Let me know if you need help.

On another note, I think the fundamental problem is that without built in behaviors, Doctrine isn't the same thing it was.

While this is not necessarily a bad thing, a big part of why I bought into Doctrine was the magic of behaviors. I understand Roman doesn't like the magic. I understand Doctrine 2 is going to be a ton faster than Doctrine 1. That being said, I'm willing to sacrifice a great deal of speed for the magic.

Why not use code generation to simplify the recurring tasks? Posted by David about about 1 year ago.

I know that the whole "code generation" topic is quite a difficult one, because it has its flaws, too. But I think it can be combined in a way that keeps D2 clean (no ORM-dependencies) and reduces the amount of things people have to write themselves repeatedly.

So why not define the "behaviours" like timestampable or softdelete in the class annotations and automatically generate a base class for this class, containing all the duplicated stuff? This shouldn't be too hard to implement, and it would solve most problems that mainly consist of horizontal copying of stuff.

I actually wouldn't consider this being part of D2, but more of a separate project that only aims to combine code from separate classes into 1 class. This could actually replace the missing traits feature in a very clean and simple way und just be omitted as soon as traits are available.

Perhaps there already is something like this. Does anyone know?

Just an exercise Posted by Frufru about about 1 year ago.

It appears that a java purest has infiltrated this project and is using it as an exercise in how to perfectly program something instead of how to make something perfectly useful.

Way to go team.

Just an exercise Posted by Frufru about about 1 year ago.

It appears that a java purest has infiltrated this project and is using it as an exercise in how to perfectly program something instead of how to make something perfectly useful.

Way to go team.

Nested ? Posted by dave about about 1 year ago.

don't understand for NestedSet what you'll do, it's a big topic for me if you stop it I return to Java ! :)

I think we also need "black box", components with basic behaviors so we can focus on our business logic, not all basic model

Extensible Doctrine? Posted by aNj about about 1 year ago.

Im also very frustrating about your future plans. The reason why we all love Doctrine is not that standard functionality of an orm, but the special features you "invented" in version 1.

So, please provide some examples on how to implement an extension in Doctrine or how to implement similar "behaviour" like the actAs or the preDQLSelect event.

I understand that you can't give full support for behaviours and the negative part of this "style". But maybe you can provide a good hint on how to do it, please do not IGNORE this wish, we COUNT on YOU guys!

An example Translatable behavior Posted by gediminas about about 1 year ago.

a Translatable behavior which translates and loads the translations automatically depending on locale used: http://github.com/l3pp4rd/DoctrineExtensions

RunKit FTW Posted by anonymous about about 1 year ago.

@jwage: have you looked into using runkit? it's 'possible', but not practical.

Why D2 is better than D1 Posted by MvL about about 1 year ago.

I see a lot of misunderstandings in the comments about Doctrine2 on this page and in other discussions as well.

First, like mentioned by 'Stepback': "It is hard to migrate a D1 based app to D2, therefore D2 must be bad".

A: It's probably hard to migrate a D1 based app to any other framework, because D1 uses the ActiveRecord pattern. Although this seems to be an attractive pattern it creates a horrible dependency by tight coupling your database to the domain-tier in your application. Every domain class represents a database table definition, and every domain object is equivalent to a record in that database table. The two can't live without each other. D2 recognizes he fact that the relational world and the object world are not the same and uses a different approach in which persistence is important, but only when modelling mappings, not for modelling your domain. Therefore the domain implementations in D2 can represent the real world instead of a database. Those two approaches are incompatible. D1, by using the ActiveRecord pattern, knows only of the relational world. D2 knows the relational world and the domain, or to put it stronger: the real world. Migrating can be a serious effort, but if done properly it can make your life, and the life of your customers, a lot easier.

The second is about soft-deletes. Several people complain about D2 not supporting them. Although I can imagine that soft-deletes can be pretty convenient, i.m.h.o. they are the wrong solution for a very real problem. Where it all boils down to is:

"Soft-deletes break referential integrity"

You can't have soft-deleted records in your database and have that same databases ignore them for maintaining the data integrity that your domain requires. Soft-deletes and referential integrity cannot live together.

Roman... Posted by Gavin about 10 months ago.

A year later, and Roman still comes off as a enormous flaming douchebag in these comments. It might do the project well to just delete his unprofessional nonsense, so as to keep people from getting the impression that he represents the project.

something unclear Posted by Senador about 10 months ago.

[php]

$searchListener = new EzcSearchListener(...); $em->getEventManager()->addEventListener( array(Doctrine\ORM\Events::preUpdate), $searchListener );

Im not quite sure where to put this fragment of code. I guess somewhere in the startup of doctrine. But the searchlistener requires a searchsession, which I haven't at that point. Those three dots which a written there instead doesn't really work.

@Gavin Posted by jwage about 10 months ago.

Hi, Roman does represent the project. We fully support all the decisions and explanations about Doctrine2.

XML Posted by dpk about 9 months ago.

"This example uses Annotations as example, yet this of course works with YAML and XML mappings."

Where could I find examples of using Timestampable in the XML mappings?

Posted by leopard lover about 3 months ago.

https://github.com/l3pp4rd/DoctrineExtensions

Still not convinced Posted by Attila Fulop about 3 months ago.

From time to time I'm coming back to see how I could switch to Doctrine 2.x from 1.2.

From my perspective, all what I see is that at the end of the day I've been reading for 30+ minutes thru lines and lines of code, thru pages to achieve what I can do today with 2 lines of yaml code.

I don't doubt the you've removed behaviors with reason (even if I don't see that now), but I think the transition path just sucks. At least for me.

I'll be back in a couple of months to read over again :)

Versionable not working on preUpdate Posted by Niels Krijger about 2 months ago.

The versionable example shows the following:


/** * @PrePersist * @PreUpdate */ public function logVersion() { $this->auditLog[] = new BlogPostVersion($this);

}

This does not work because the preUpdate-event does not detect changes in associations. The manual says the following about preUpdate:

"Changes to associations of the updated entity are never allowed in this event, since Doctrine cannot guarantee to correctly handle referential integrity at this point of the flush operation." http://www.doctrine-project.org/docs/orm/2.1/en/reference/events.html

Use "postLoad"-event instead to create the "entityVersion"-object.

Create Comment