Symfony ActivityLog Component
![Build Status][testing-image]
[][coverage-link]
, (*1)
ActivityLogBundle - Extended doctrine loggable (StofDoctrineExtensionsBundle), (*2)
What's inside
ActivityLogBundle uses Loggable extension from StofDoctrineExtensionsBundle and DoctrineExtensions, (*3)
This bundle extend Gedmo\Loggable\Entity\MappedSuperclass\AbstractLogEntry with below fields:, (*4)
- 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., (*5)
Also available formatter to preprocessing activity log before show in view (html)., (*6)
Installation
Pretty simple with Composer, run:, (*7)
``` bash
composer require madmis/activity-log-bundle, (*8)
Then enable the bundle in the kernel:
``` php
public function registerBundles()
{
$bundles = [
// ...
new ActivityLogBundle\ActivityLogBundle(),
// ...
];
...
}
Configure bundle:, (*9)
``` yml, (*10)
app/config/config.yml
doctrine:
dbal:
#...
orm:
#...
resolve_target_entities:
Symfony\Component\Security\Core\User\UserInterface: AppBundle\Entity\User
mappings:
gedmo_loggable:
type: annotation
prefix: Gedmo\Loggable\Entity
dir: "%kernel.root_dir%/../src/AppBundle/Entity/"
alias: GedmoLoggable
is_bundle: false, (*11)
stof_doctrine_extensions:
class:
loggable: ActivityLogBundle\Listener\LoggableListener
orm:
default:
loggable: true, (*12)
Create entity and make it loggable:
```php
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., (*13)
Then run command to update database schema:, (*14)
``` bash
php bin/console doctrine:schema:update --force, (*15)
Using formatter to data view
------------
Formatter class: **ActivityLogBundle\Service\ActivityLog\ActivityLogFormatter**
Formatter service: **activity_log.formatter**
required: **LoggerInterface** as dependency
By default entity without custom formatter class formatted by **ActivityLogBundle\Service\ActivityLog\EntityFormatter\UniversalFormatter**
But you can implement custom formatter for each entity.
To register a custom formatter, add a service tag with the following (required) properties:
* name: 'activity_log.formatter'
* entity: Class name of the entity that should be formatted by the registered formatter
Example:
```php
services:
app.formatter.project:
class: AppBundle\Service\ActivityFormatter\Project
tags:
- { name: activity_log.formatter, entity: 'Project'}
As example formatter for AppBundle\Entity\Project entity:, (*16)
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., (*17)
For example, Project entity has association mapping ManyToOne to Type entity.
To get Type name we can add method type to Project formatter:, (*18)
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')
->find($value['id']);
if ($entity) {
return $entity->getName();
}
}
return '';
}
As result we have formatted response to show in view., (*19)
Using activity log in controller
$em = $this->getDoctrine()->getManager();
// get log entries for entity
$entries = $em
->getRepository('AppBundle:LogEntry')
->getLogEntriesQueryBuilder($entity)
->getQuery()
->getResult();
// format log entries to show in the view
$entries = $this
->get('activity_log.formatter')
->format($entries);
For $entity
should be configured Entity formatter., (*20)