The Stream Blog

Adding Support for Doctrine ORM

Doctrine is one of the most popular object-relational mapping libraries in the PHP community. It ships with Symfony, but is also used in a lot of non-Symfony applications. Previously, we built ORM integrations with Laravel’s Eloquent, Rails’ ActiveRecord and many others, so adding Doctrine to the list seemed like a no-brainer.

With that said, we are happy to announce our new Doctrine ORM integration library! You may check out the project here, or install it with `composer require get-stream/stream-doctrine`. With its’ `1.0.0` tag, it is now officially released.

Example app

To show you how it works, we created an example app. This is the easiest way to understand the way we intend this package to be used.

The example app is a very basic site where users sign up, log in, find people to follow, post updates and like posts by you or other individuals. When signed in, you’ll find a feed of recent posts by you and other profiles you follow. You can also check other user’s activities by visiting their profiles. On their profile pages, you’ll see a feed of their activities which includes posts they’ve recently liked and posted.

To see the app in action, checkout the code here, and click around in the app, which is running on Heroku (so it may idle from time to time).

Every ORM integration we make has the same structure: we tap into the life cycles of your entities. We hook into your ORM when an object is created, as well as when an object is deleted. For Doctrine, this meant having a `ModelListener` class, which you can register using a single annotation that goes on top of your “activity” entity class:

/**
 * @ORM\EntityListeners({"GetStream\Doctrine\ModelListener"})
 */
class Entity

Our example app has two of these activity entity classes: “Post” and “Like”. Both represent an activity performed by a user and, in turn, have a many-to-one relations to the “User” entity which performed it. In Stream, this is called the “actor”.

Both “Post” and “Like” activity entities have the model listener configured.The ModelListener, (part of the ORM integration package) will use the low-level PHP client, which is the `getstream/stream` composer dependency. Whenever it receives a call that a new activity entity is created, it will use the client to call Stream’s add activity endpoint. Conversely, when an existing activity is deleted (using Doctrine’s EntityManager::remove method) the activity is being removed from Stream’s feeds.

For an activity entity to be recognized by the ModelListener it must implement the ActivityInterface. The ModelListener requires the methods provided by that interface to be able to generate activity data to create a Stream activity. The interface defines 8 methods such as activityActorId, activityVerb and activityTime. You can either implement these yourself or you may use the ActivityTrait which provides a default implementation for most of these methods.

/**
 * @return string
 */
public function activityVerb();

/**
 * @return string
 */
protected function activityId();

/**
 * @return string
 */
public function activityActorId();
/**
 * @return string
 */
public function activityActor();

/**
 * @return DateTimeInterface
 */
public function activityTime();

For example, in the example application we use activityNotify to copy the newly created activities to multiple feeds. For more information on this subject, see https://getstream.io/docs/#targetting.

Our example app sends all “Post” activities to both “user:{actor_id}” feed and “timeline:{actor_id}”. All “Like” activities get added to “user:{actor_id}” feeds. This way, when you get activities for a “user:{actor_id}”, you will see the like or post a user has performed. In other words: the exact feed we show on a user profile. When a user follows another user, we add a follow relationship between a user’s timeline “timeline:{actor_id}” and a “timeline:{target_id}”. In other words, when you view a “timeline:{actor_id}”, you’ll see all posts by the user itself, and all posts by people this user follows. A user’s homepage timeline feed is a great example.

Of course, if you need to do more fine-grained operations, you can use the FeedManager::getClient method to get a fully configured GetStream\Stream\Client object. Operations such as update activities, batch follows and batch activity operations can be done using the client. Every application is unique and can’t be entirely configured automatically like with the ORM Entity ModelListener. For best results, use the client and ModelListener wherever possible.

If you’re new to Stream, please visit our Getting Started page for a 5 minute walkthrough that outlines how our technology allows you to build scalable news feeds and activity streams in hours instead of months. And, as always, we would greatly appreciate any feedback, bug reports or pull requests!