Symfony ActivityLog Component (loggable bundle)

ActivityLogBundle — Extended doctrine loggable (StofDoctrineExtensionsBundle)

What’s inside

ActivityLogBundle uses Loggable extension from StofDoctrineExtensionsBundle and DoctrineExtensions

This bundle extend Gedmo\Loggable\Entity\MappedSuperclass\AbstractLogEntry with below fields:

  • parentId — store depedency to «main entity»
  • parentClass — store «main entity» type
  • oldData — data that were changed
  • name — entry name (to show in activity log)
  • user — associations mapping with user who changed data

Bundle contain extended listener (LoggableListener) to process above fields.

Also available formatter to preprocessing activity log before show in view (html).


Pretty simple with Composer, run:

composer require madmis/activity-log-bundle

Then enable the bundle in the kernel:

public function registerBundles()
    $bundles = [
        // ...
        new ActivityLogBundle\ActivityLogBundle(),
        // ...

Configure bundle:

# app/config/config.yml
            Symfony\Component\Security\Core\User\UserInterface: AppBundle\Entity\User
                type: annotation
                prefix: Gedmo\Loggable\Entity
                dir: "%kernel.root_dir%/../src/AppBundle/Entity/"
                alias: GedmoLoggable
                is_bundle: false

        loggable: ActivityLogBundle\Listener\LoggableListener
            loggable: true

    # namespace prefix for custom formatters 
    formatter_prefix: "AppBundle\\Service\\ActivityFormatter"

Create entity and make it loggable:

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
use ActivityLogBundle\Entity\Interfaces\StringableInterface;

 * @package AppBundle\Entity
 * @ORM\Entity(repositoryClass="ProjectRepository")
 * @ORM\Table
 * @Gedmo\Loggable(logEntryClass="ActivityLogBundle\Entity\LogEntry")
class Project implements StringableInterface
     * @var int
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
    private $id;

     * @var string
     * @ORM\Column(type="string", length=128)
     * @Gedmo\Versioned
    private $name;

     * @var string
     * @ORM\Column(type="string", length=16)
     * @Gedmo\Versioned
    private $key;


StringableInterface required to save LogEntry::name.

Then run command to update database schema:

php bin/console doctrine:schema:update --force

Using formatter to data view

Formatter class: ActivityLogBundle\Service\ActivityLog\ActivityLogFormatter Formatter service: activity_log.formatter

required: LoggerInterface, EntityManager and formatter_prefix parameter as dependencies

By default entity without custom formatter class formatted byActivityLogBundle\Service\ActivityLog\EntityFormatter\UniversalFormatter

But you can implement custom formatter for each entity.

As example formatter for AppBundle\Entity\Project entity:

namespace AppBundle\Service\ActivityFormatter;

class Project extends AbstractFormatter implements FormatterInterface
     * @param LogEntryInterface $log
     * @return array
    public function format(LogEntryInterface $log)
        $result = $log->toArray();

        if ($log->isCreate()) {
            $result['message'] = sprintf('The <b>Project <span class="font-green-jungle">"%s"</span></b> was created.', $log->getName());
        } else if ($log->isRemove()) {
            $result['message'] = sprintf('The <b>Project <span class="font-red-flamingo">"%s"</span></b> was removed.', $log->getName());
        } else if ($log->isUpdate()) {
            $result['message'] = '<dl><dt>The <b>Project <span class="font-yellow-gold">"%s"</span></b> was updated.</dt>%s</dl>';
            $data = $log->getData();
            $oldData = $log->getOldData();

            $text = '';
            foreach ($data as $field => $value) {
                $value = $this->normalizeValue($field, $value);

                if (array_key_exists($field, $oldData)) {
                    $oldValue = $this->normalizeValue($field, $oldData[$field]);
                    $subText = sprintf('from "<b>%s</b>" to "<b>%s</b>".', $oldValue, $value);
                } else {
                    $subText = sprintf('to "<b>%s</b>".', $value);
                $text .= sprintf('<dd>Property "<b>%s</b>" was changed: %s</dd>', $field, $subText);

            $result['message'] = sprintf($result['message'], $log->getName(), $text);
        } else {
            $result['message'] = "Undefined action: {$log->getAction()}.";

        return $result;

If entity has association with other entity it can be resolved by AbstractFormatter::normalizeValue. This method call method from the entity formatter class, which named as appropriate property.

For example, Project entity has association mapping ManyToOne to Type entity. To get Type name we can add methodtype to Project formatter:

namespace AppBundle\Service\ActivityFormatter;

class Project extends AbstractFormatter implements FormatterInterface

     * @param array $value
     * @return string
    protected function type(array $value)
        if (isset($value['id'])) {
            /** @var Type $entity */
            $entity = $this->entityManager->getRepository('AppBundle:Type')

            if ($entity) {
                return $entity->getName();

        return '';

As result we have formatted response to show in view


Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:


Для комментария используется ваша учётная запись Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s

%d такие блоггеры, как: