Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

History Bundle Laravel Package

baskin/history-bundle

baskin/history-bundle adds revision tracking to your PHP models, recording each change so you can audit edits, review prior versions, and see how data evolved over time. Lightweight and straightforward to integrate into existing projects.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Confirm compatibility before install. Check the package’s composer.json constraints first:

    composer show baskin/history-bundle --all
    

    This bundle is old and has no dependents/stars, so validate that it supports your Symfony/Twig/Doctrine versions before adding it to a project.

  2. Install Gedmo Loggable as the real dependency. The bundle is only a Twig presentation layer. You still need DoctrineExtensions Loggable:

    composer require gedmo/doctrine-extensions
    # or, commonly:
    composer require stof/doctrine-extensions-bundle
    
  3. Register the bundle using the style for your Symfony version:

    // Symfony 4+/5+ config/bundles.php
    return [
        Baskin\HistoryBundle\BaskinHistoryBundle::class => ['all' => true],
    ];
    
    // Older Symfony app/AppKernel.php
    $bundles = [
        new Baskin\HistoryBundle\BaskinHistoryBundle(),
    ];
    
  4. Configure the Loggable listener:

    # config/packages/stof_doctrine_extensions.yaml
    stof_doctrine_extensions:
        orm:
            default:
                loggable: true
    
  5. Annotate the entities you want to audit:

    use Gedmo\Mapping\Annotation as Gedmo;
    
    /**
     * @Gedmo\Loggable
     */
    class Product
    {
        /**
         * @Gedmo\Versioned
         */
        private $name;
    
        /**
         * @Gedmo\Versioned
         */
        private $price;
    }
    
  6. Create the log table:

    bin/console doctrine:schema:update --force
    # or generate a migration:
    bin/console doctrine:migrations:diff
    
  7. Find the bundle’s Twig API first. Look in vendor/baskin/history-bundle for the Twig extension class and its service definition. Confirm the exact function/filter names before writing templates. The usage pattern is typically:

    {% for log in history(product) %}
        <tr>
            <td>{{ log.loggedAt|date('Y-m-d H:i') }}</td>
            <td>{{ log.username ?: 'system' }}</td>
            <td>{{ log.action }}</td>
            <td>
                {% for field, value in log.data %}
                    <strong>{{ field }}</strong>: {{ value }}
                {% endfor %}
            </td>
        </tr>
    {% endfor %}
    

    If the bundle does not expose what you need, bypass it and use the Gedmo repository directly.

  8. First useful use case: add a “History” tab to an admin detail page. Pass the entity from the controller, render the log entries in reverse chronological order, and show changed fields only.

Implementation Patterns

Controller-driven history page

Fetch the entity normally, then let Twig render its log entries:

public function show(Product $product): Response
{
    return $this->render('product/show.html.twig', [
        'product' => $product,
    ]);
}

Use the bundle’s Twig function in the template, or fall back to the Gedmo repository:

use Gedmo\Loggable\Entity\LogEntry;

$logs = $em->getRepository(LogEntry::class)->getLogEntries($product);

Reusable partial

Keep history rendering in one partial so every entity gets consistent output:

{# templates/admin/_history.html.twig #}
{% if history(entity) is not empty %}
    <table class="table table-sm">
        <thead>
            <tr>
                <th>When</th>
                <th>Who</th>
                <th>Action</th>
                <th>Changes</th>
            </tr>
        </thead>
        <tbody>
            {% for log in history(entity)|reverse %}
                <tr>
                    <td>{{ log.loggedAt|date('Y-m-d H:i:s') }}</td>
                    <td>{{ log.username ?: 'system' }}</td>
                    <td>{{ log.action }}</td>
                    <td>
                        {% for field, value in log.data %}
                            <div><strong>{{ field }}</strong>: {{ value }}</div>
                        {% endfor %}
                    </td>
                </tr>
            {% endfor %}
        </tbody>
    </table>
{% else %}
    <p class="text-muted">No history yet.</p>
{% endif %}

Show only changed fields

Gedmo stores the full versioned payload per log entry, so a raw loop repeats unchanged fields. Build a diff against the previous version:

$previous = [];
$changesByLog = [];

foreach ($logs as $log) {
    $changes = [];

    foreach ($log->getData() as $field => $value) {
        $old = $previous[$field] ?? null;

        if ($old !== $value) {
            $changes[$field] = ['old' => $old, 'new' => $value];
        }
    }

    $changesByLog[] = $changes;
    $previous = $log->getData();
}

Pass changesByLog to Twig. Be careful with ordering: if you display newest first, diff against the next older entry instead.

Restore an old version

A common admin feature is “revert to this version.” Read the log entry’s data, call matching setters, and flush:

$log = $logs[$version];

foreach ($log->getData() as $field => $value) {
    $setter = 'set' . ucfirst($field);

    if (method_exists($product, $setter)) {
        $product->$setter($value);
    }
}

$em->flush();

Treat this carefully for relations, dates, and value objects. Logged relation data may be IDs or serialized values, not hydrated entities.

Paginate large histories

Do not load every log entry for entities with years of edits. Query the log repository directly:

$qb = $em->createQueryBuilder()
    ->select('l')
    ->from(LogEntry::class, 'l')
    ->where('l.objectClass = :class')
    ->andWhere('l.objectId = :id')
    ->setParameter('class', get_class($product))
    ->setParameter('id', $product->getId())
    ->orderBy('l.loggedAt', 'DESC');

Then wire the query into Pagerfanta, KnpPaginator, or your existing pagination layer.

Restrict and mask history

  • Only render history for users with an audit/admin role.
  • Do not mark sensitive fields as @Versioned if you do not want their values stored in ext_log_entries.
  • Mask fields such as tokens, password hashes, or personal data at render time if they must be logged.

Integration tips

  • Use stof/doctrine-extensions-bundle to configure the Loggable listener rather than wiring Gedmo services manually.
  • If this bundle’s Twig extension is too limited, write a tiny custom Twig extension around LogEntryRepository::getLogEntries(). That is usually less work than fighting an old bundle.
  • Prefer a shared history.html.twig partial plus a small presenter/service for diffing and label translation.
  • Map Gedmo action names to human labels:
    $labels = [
        'create' => 'Created',
        'update' => 'Updated',
        'remove' => 'Deleted',
    ];
    

Gotchas and Tips

  • Abandonment risk is the biggest gotcha. The package has no stars and no dependents. Pin it, fork it, or vendor the Twig extension into your app. Do not assume Symfony 6/7 or Twig 3 compatibility.
  • Confirm the Twig API before building templates. Check the extension class and service config in vendor/baskin/history-bundle. Function names and argument order may differ from the examples above.
  • Gedmo only logs @Versioned properties. Changes to unannotated fields are invisible in history. This is usually correct, but it surprises developers during debugging.
  • The username is often null. Gedmo’s Loggable listener needs a username resolver/callable integrated with your security token storage. Configure it explicitly; otherwise every entry shows as anonymous.
  • The log table must exist. Run schema update or migrations after enabling Loggable. The default table is usually ext_log_entries.
  • Bulk DQL updates and partial updates are not logged. Gedmo listens to entity lifecycle events. UPDATE ... SET ... via DQL bypasses history.
  • Removed entities leave log rows behind. The objectId then points to a deleted record. Render deleted-entity history defensively instead of assuming the entity still exists.
  • Dates and relations are serialized in data. Expect strings/IDs rather than live objects. Normalize values before comparing or displaying them.
  • Diff ordering matters. getLogEntries() returns entries in a defined order, but confirm it. If you reverse for display, compute diffs against the correct adjacent version.
  • Avoid N+1 queries on index pages. Do not call the history Twig function once per row unless the extension batches queries. For list screens, fetch log counts or latest entries in a single repository query.
  • Debug with the database first. If history is missing, inspect ext_log_entries directly, verify the Doctrine listener is active, check that the entity manager is flushing the entity, and clear the Symfony cache.
  • Use Twig debugging to confirm registration:
    bin/console debug:twig
    # older Symfony:
    bin/console twig:debug
    
  • Extension points: decorate or replace the bundle’s Twig extension, override its templates if provided, supply a custom username resolver, or swap in a custom LogEntry entity if you need extra metadata.
  • Recommendation: treat this bundle as a thin convenience layer. For production, the safest day-to-day approach is to depend on Gedmo Loggable directly and keep your own small Twig extension/partial for rendering. That gives you compatibility control, testability, and a clear upgrade path when the bundle is no longer maintained.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings