PHPackages                             hostnet/entity-tracker-component - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. hostnet/entity-tracker-component

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

hostnet/entity-tracker-component
================================

Provides an event when a Tracked entity changes

2.2.3(1mo ago)16158.1k↓13.5%64MITPHPPHP ^7.3|^8.0CI passing

Since Aug 12Pushed 1y ago5 watchersCompare

[ Source](https://github.com/hostnet/entity-tracker-component)[ Packagist](https://packagist.org/packages/hostnet/entity-tracker-component)[ RSS](/packages/hostnet-entity-tracker-component/feed)WikiDiscussions master Synced 1mo ago

READMEChangelog (10)Dependencies (18)Versions (20)Used By (4)

README
======

[](#readme)

- [What is the Entity Tracker?](#what-is-the-entity-tracker)
- [Requirements](#requirements)
- [Installation](#installation)

### Documentation

[](#documentation)

- [How does it work?](#how-does-it-work)
- [Setup](#setup)
    - [Registering the Events](#registering-the-events)
    - [Creating the Listener](#creating-the-listener)
    - [Creating an Interface for the Entity](#creating-an-interface-for-the-entity)
    - [Registering the Annotation on the Entity](#registering-the-annotation-on-the-entity)
    - [What's Next?](#whats-next)
- [Extending the Tracker Annotation](#extending-the-tracker-annotation)
    - [Example Annotation](#example-annotation)
    - [Custom Annotation Resolvers](#custom-annotation-resolvers)
    - [Custom entityChanged Listener](#custom-entitychanged-listener)

What is the Entity Tracker?
---------------------------

[](#what-is-the-entity-tracker)

The Entity Tracker Component is a library used to track changes within an Entity during a flush of the EntityManager. This makes it possible to do all sorts of things you want to automated during the `preFlush` event.

Entities become tracked when you implement the `@Tracked` annotation or a sub-class of `@Tracked`. You have total control over what happens next and which events you will use to listen to the `entityChanged` event.

Let's say that every time you flush your User, you want to set when it was updated. By default, you would have to call `$user->setUpdatedAt()` manually or create a custom listener on preFlush that sets the updated at timestamp. Both are a lot of extra work and you have to write extra code to determine changes. Listening to preFlush will always trigger your listener and you don't want to make a huge if statement nor create a listener for each Entity.

Requirements
------------

[](#requirements)

The tracker component requires at least php 5.4 and runs on Doctrine2. For specific requirements, please check [composer.json](../master/composer.json)

Installation
------------

[](#installation)

Installing is pretty easy, this package is available on [packagist](https://packagist.org/packages/hostnet/entity-tracker-component). You can register the package locked to a major as we follow [Semantic Versioning 2.0.0](http://semver.org/).

#### Example

[](#example)

```
    "require" : {
        "hostnet/entity-tracker-component" : "^2.0.1"
    }
```

> Note: You can use dev-master if you want the latest changes, but this is not recommended for production code!

Documentation
=============

[](#documentation-1)

How does it work?
-----------------

[](#how-does-it-work)

It works by putting an annotation on your Entity and registering your listener on our event, assuming you have already registered our event to doctrine. That's all you need to do to start tracking the Entity so it will be available in the `entityChanged` event.

Setup
-----

[](#setup)

#### Registering The Events

[](#registering-the-events)

Here's an example of a very basic setup. Setting this up will be a lot easier if you use a framework that has a Dependency Injection Container.

> Note: If you use Symfony, you can take a look at the [hostnet/entity-tracker-bundle](https://github.com/hostnet/entity-tracker-bundle). This bundle is designed to configure the services for you.

```
use Acme\Component\Listener\ChangedAtListener;
use Hostnet\Component\EntityTracker\Listener\EntityChangedListener;
use Hostnet\Component\EntityTracker\Provider\EntityAnnotationMetadataProvider;
use Hostnet\Component\EntityTracker\Provider\EntityMutationMetadataProvider;

/* @var $em \Doctrine\ORM\EntityManager */
$event_manager = $em->getEventManager();

// default doctrine annotation reader
$annotation_reader = new AnnotationReader();

// setup required providers
$mutation_metadata_provider   = new EntityMutationMetadataProvider($annotation_reader);
$annotation_metadata_provider = new EntityAnnotationMetadataProvider($annotation_reader);

// pre flush event listener that uses the @Tracked annotation
$entity_changed_listener = new EntityChangedListener(
    $mutation_metadata_provider,
    $annotation_metadata_provider
);

// our example listener
$listener = new ChangedAtListener(new DateTime());

// register the events
$event_manager->addEventListener('preFlush', $entity_changed_listener);
$event_manager->addEventListener('prePersist', $entity_changed_listener);
$event_manager->addEventListener('entityChanged', $listener);
```

#### Creating the Listener

[](#creating-the-listener)

The listener needs to have 1 method that has the same name as the event name. This method will have 1 argument which is the `EntityChangedEvent $event`. The event contains the used EntityManager, Current Entity, Original (old) Entity and an array of the fields which have been altered -or mutated.

> Note: The Doctrine2 Event Manager uses the event name as method name, therefore you should implement the entityChanged method as listed below.

```
namespace Acme\Component\Listener;

use Hostnet\Component\EntityTracker\Event\EntityChangedEvent;

class ChangedAtListener
{
    private $now;

    public function __construct(\DateTime $now)
    {
        $this->now = $now;
    }

    public function entityChanged(EntityChangedEvent $event)
    {
        if (!($entity = $event->getCurrentEntity()) instanceof UpdatableInterface) {
            // uses the tracked but might not have our method
            return;
        }

        $entity->setUpdatedAt($this->now);
    }
}
```

#### Creating an Interface for the Entity

[](#creating-an-interface-for-the-entity)

Additionally to the `@Tracked` annotation, we want to determine if we can set and updated\_at field within our Entity. This can be done by creating the following interface for our Entity.

```
namespace Acme\Component\Listener;

interface UpdatableInterface
{
   public function setUpdatedAt(\DateTime $now);
}
```

#### Registering the Annotation on the Entity

[](#registering-the-annotation-on-the-entity)

All we have to do now is put the `@Tracked` annotation and Interface on our Entity and implement the required method

```
use Acme\Component\Listener\UpdatableInterface;
use Doctrine\ORM\Mapping as ORM;
use Hostnet\Component\EntityTracker\Annotation\Tracked;

/**
 * @ORM\Entity
 * @Tracked
 */
class MyEntity implements UpdatableInterface
{
    /**
     * @ORM\...
     */
    private $changed_at;

    public function setUpdatedAt(\DateTime $now)
    {
        $this->changed_at = $now;
    }
}
```

#### What's Next?

[](#whats-next)

Change the value of a field and flush the Entity. This will trigger the preFlush, which in turn will trigger our listener, which then fires up the entityChanged event.

```
$entity->setName('henk');
$em->flush();
// Voila, your changed_at is filled in
```

### Extending the Tracker Annotation

[](#extending-the-tracker-annotation)

You might want to extend the `@Tracker` annotation. This allows you to add options and additional checks within your listener.

#### Example Annotation

[](#example-annotation)

In the following example, you will see how using a creating a custom annotation works.

- You have to add `@Annotation`
- You have to add `@Target({"CLASS"})`
- It has to extend `Hostnet\Component\EntityTracker\Annotation\Tracked`

Using this annotation will give us specific access to options within our listener. We can now attempt to get this annotation in the listener and we get can call `getIgnoredFields()`. This example will ignore certain fields for entities using the annotation.

```
use Hostnet\Component\EntityTracker\Annotation\Tracked;

/**
 * @Annotation
 * @Target({"CLASS"})
 */
class Changed extends Tracked
{
    public $ignore_fields = [];

    public function getIgnoredFields()
    {
        if (empty($this->ignore_fields)) {
            return ['id'];
        }

        return $ignore_fields;
    }
}
```

#### Custom Annotation Resolvers

[](#custom-annotation-resolvers)

To obtain the Annotation, we have implemented resolvers. The example below shows how you could implement it yourself.

```
use Doctrine\ORM\EntityManagerInterface;
use Hostnet\Component\EntityTracker\Provider\EntityAnnotationMetadataProvider;

class ChangedResolver
{
    private $annotation = 'Changed';

    private $provider;

    public function __construct(EntityAnnotationMetadataProvider $provider)
    {
        $this->provider = $provider;
    }

    public function getChangedAnnotation(EntityManagerInterface $em, $entity)
    {
        return $this->provider->getAnnotationFromEntity($em, $entity, $this->annotation);
    }
}
```

#### Custom entityChanged Listener

[](#custom-entitychanged-listener)

The listener can now use the resolver to obtain the annotation so possible do something extra when a specific set of fields is changed.

```
use Hostnet\Component\EntityTracker\Event\EntityChangedEvent;

class ChangedListener
{
    private $resolver;

    public function __construct(ChangedResolver $resolver)
    {
        $this->resolver = $resolver;
    }

    public function entityChanged(EntityChangedEvent $event)
    {
        $em     = $event->getEntityManager();
        $entity = $event->getCurrentEntity();

        if (null === ($annotation = $this->resolver->getChangedAnnotation($em, $entity))) {
            return;
        }

        $preferred_changes = array_diff($annotation->getIgnoredFields(), $event->getMutatedFields());

        // do something with them
    }
}
```

###  Health Score

55

—

FairBetter than 98% of packages

Maintenance63

Regular maintenance activity

Popularity41

Moderate usage in the ecosystem

Community25

Small or concentrated contributor base

Maturity77

Established project with proven stability

 Bus Factor2

2 contributors hold 50%+ of commits

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~235 days

Recently: every ~766 days

Total

19

Last Release

54d ago

Major Versions

0.0.2 → 1.0.02014-09-09

1.2.3 → 2.0.02016-08-09

PHP version history (4 changes)0.0.1PHP 5.\*, &gt;=5.4

1.2.3PHP &lt;8,&gt;=5.4

2.0.0PHP 5.\*,&gt;=5.6||7.\*

2.2.0PHP ^7.3|^8.0

### Community

Maintainers

![](https://www.gravatar.com/avatar/65433e1ef6edb2e8f96e18614b6618c84eed20ad0de6de0d0da36a9032ec7a18?d=identicon)[hostnet](/maintainers/hostnet)

---

Top Contributors

[![yannickl88](https://avatars.githubusercontent.com/u/5468127?v=4)](https://github.com/yannickl88 "yannickl88 (19 commits)")[![linaori](https://avatars.githubusercontent.com/u/1754678?v=4)](https://github.com/linaori "linaori (17 commits)")[![janlam7](https://avatars.githubusercontent.com/u/5459235?v=4)](https://github.com/janlam7 "janlam7 (7 commits)")[![nicoschoenmaker](https://avatars.githubusercontent.com/u/1469323?v=4)](https://github.com/nicoschoenmaker "nicoschoenmaker (6 commits)")[![nutama](https://avatars.githubusercontent.com/u/9001392?v=4)](https://github.com/nutama "nutama (3 commits)")[![stefanlenselink](https://avatars.githubusercontent.com/u/834562?v=4)](https://github.com/stefanlenselink "stefanlenselink (2 commits)")[![StijnKlopper2k](https://avatars.githubusercontent.com/u/198602351?v=4)](https://github.com/StijnKlopper2k "StijnKlopper2k (1 commits)")[![haroldiedema](https://avatars.githubusercontent.com/u/567518?v=4)](https://github.com/haroldiedema "haroldiedema (1 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/hostnet-entity-tracker-component/health.svg)

```
[![Health](https://phpackages.com/badges/hostnet-entity-tracker-component/health.svg)](https://phpackages.com/packages/hostnet-entity-tracker-component)
```

###  Alternatives

[ec-cube/ec-cube

EC-CUBE EC open platform.

78527.0k1](/packages/ec-cube-ec-cube)[neos/flow

Flow Application Framework

862.0M451](/packages/neos-flow)[wallabag/wallabag

open source self hostable read-it-later web application

12.6k2.2k](/packages/wallabag-wallabag)[neos/flow-development-collection

Flow packages in a joined repository for pull requests.

144179.3k3](/packages/neos-flow-development-collection)[concrete5/core

Concrete core subtree split

19159.3k48](/packages/concrete5-core)[yosimitso/workingforumbundle

A complete forum bundle

425.3k](/packages/yosimitso-workingforumbundle)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
