## Getting Started
### Minimal Setup
1. **Installation**:
```bash
composer require bobvandevijver/entity-history-bundle:^4.3.1
For Laravel (via DoctrineBundle):
composer require fruitcake/laravel-doctrine
Configuration:
Add to config/packages/doctrine.yaml (Symfony) or config/doctrine.php (Laravel):
doctrine:
orm:
entity_listeners:
Bobv\EntityHistoryBundle\Listener\HistoryListener: ~
Annotate an Entity:
use Bobv\EntityHistoryBundle\Annotation\History;
/**
* @History
*/
#[ORM\Entity]
class User
{
// ...
}
Run Migrations:
php bin/console doctrine:migrations:diff
php bin/console doctrine:migrations:migrate
Note: If using a new database API (e.g., Doctrine DBAL 3.5+), ensure the
historytable's primary key constraint is created correctly. The fix in v4.3.1 addresses potential issues with this.
First Use Case:
Test by creating/updating a User and verify the history table records changes:
$user = new User();
$user->setName('John Doe');
$entityManager->persist($user);
$entityManager->flush();
// Check history
$history = $entityManager->getRepository(\Bobv\EntityHistoryBundle\Entity\History::class)
->findBy(['entityId' => $user->getId()]);
@History or configure via YAML/XML.@History\Ignore:
/**
* @History\Ignore
*/
private $apiToken;
$history = $entityManager->getRepository(\Bobv\EntityHistoryBundle\Entity\History::class)
->findBy(['entityId' => $entity->getId()]);
$history = $entityManager->getRepository(\Bobv\EntityHistoryBundle\Entity\History::class)
->createQueryBuilder('h')
->where('h.entityId = :id')
->andWhere('h.revision = :rev')
->setParameter('id', $entity->getId())
->setParameter('rev', 1)
->getQuery()
->getResult();
public function register()
{
$this->app->register(\Fruitcake\Doctrine\DoctrineServiceProvider::class);
$this->app->make(\Fruitcake\Doctrine\DoctrineServiceProvider::class)
->registerEntityListeners();
}
use Bobv\EntityHistoryBundle\Event\HistoryEvent;
$entityManager->getEventManager()->addEventListener(
['preUpdate', 'prePersist'],
function (HistoryEvent $event) {
$event->setMetadata(['user_id' => auth()->id()]);
}
);
Gedmo/SoftDeleteable:
use Gedmo\Mapping\Annotation as Gedmo;
/**
* @Gedmo\SoftDeleteable(fieldName="deletedAt")
* @History
*/
class User { ... }
History will capture the deletedAt field change.$entityManager->getConnection()->getConfiguration()->setSQLLogger(null);
// Bulk operations here
$entityManager->flush();
History entity to add columns (e.g., user_id):
/**
* @ORM\Entity(repositoryClass="App\Repository\CustomHistoryRepository")
*/
class CustomHistory extends \Bobv\EntityHistoryBundle\Entity\History
{
/**
* @ORM\Column(type="integer", nullable=true)
*/
private ?int $userId;
}
Update the listener to populate userId.$entityManager->getEventManager()->addEventListener(
['postUpdate', 'postPersist'],
function (HistoryEvent $event) {
dispatch(new RecordHistoryJob($event->getEntity(), $event->getMetadata()));
}
);
public function getDiff(User $user, int $oldRevision, int $newRevision)
{
$oldHistory = $this->findOneBy(['entityId' => $user->getId(), 'revision' => $oldRevision]);
$newHistory = $this->findOneBy(['entityId' => $user->getId(), 'revision' => $newRevision]);
return $this->diffEntities($oldHistory->getData(), $newHistory->getData());
}
Route::get('/entities/{entity}/history', function (EntityManagerInterface $em, $entity) {
$history = $em->getRepository(\Bobv\EntityHistoryBundle\Entity\History::class)
->findBy(['entityId' => $entity]);
return response()->json($history);
});
Primary Key Constraint Issues (v4.3.1 Fix)
history table's primary key constraint might fail to create.^4.3.1 and rerun migrations:
php bin/console doctrine:migrations:migrate
Missing Annotations:
@History is applied to the entity class (not just methods/fields).Direct SQL Bypasses ORM:
DB::update()) won’t trigger history.EntityManager for all entity operations or implement a database trigger.Performance Bottlenecks:
history.entity_id, history.changed_at, and history.revision:
CREATE INDEX idx_history_entity_revision ON history(entity_id, revision);
Circular References:
@History\Ignore for non-critical fields or limit depth with a custom listener.Transaction Rollbacks:
postFlush events.Doctrine Event Conflicts:
config/packages/doctrine.yaml:
services:
Bobv\EntityHistoryBundle\Listener\HistoryListener:
tags:
- { name: doctrine.event_subscriber, priority: 255 } # Highest priority
Large Binary Data:
text, json).Enable Doctrine Logging:
$entityManager->getConnection()->getConfiguration()->setSQLLogger(new \Doctrine\DBAL\Logging\EchoSQLLogger());
INSERT queries to the history table.Inspect Events:
$entityManager->getEventManager()->addEventListener(
['preUpdate'],
function (LifecycleEventArgs $args) {
dump($args->getEntity(), $args->getObject()->getChanges());
}
);
Check History Table:
SELECT * FROM history WHERE entity_id = 123 ORDER BY revision DESC;
Validate Annotations:
doctrine:schema:validate to ensure annotations are parsed:
php bin/console
How can I help you explore Laravel packages today?