Skip to content

Commit

Permalink
Add Auditable behavior
Browse files Browse the repository at this point in the history
  • Loading branch information
mringler committed May 23, 2024
1 parent 530eded commit c824859
Show file tree
Hide file tree
Showing 10 changed files with 1,328 additions and 0 deletions.
112 changes: 112 additions & 0 deletions src/Propel/Generator/Behavior/Auditable/AuditableBehavior.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
<?php

/**
* MIT License. This file is part of the Propel package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Propel\Generator\Behavior\Auditable;

use Propel\Generator\Behavior\SyncedTable\TableSyncer;
use Propel\Generator\Behavior\Util\InsertCodeBehavior;
use Propel\Generator\Builder\Om\ObjectBuilder;
use Propel\Generator\Model\PropelTypes;
use Propel\Generator\Model\Table;

class AuditableBehavior extends AuditableBehaviorDeclaration
{
/**
* @see \Propel\Generator\Model\Behavior::getObjectBuilderModifier()
*
* @return $this|\Propel\Generator\Behavior\Auditable\AuditableObjectModifier
*/
public function getObjectBuilderModifier()
{
return new AuditableObjectModifier($this);
}

/**
* @param \Propel\Generator\Model\Table $syncedTable
* @param bool $tableExistsInSchema
*
* @return void
*/
public function addTableElements(Table $syncedTable, bool $tableExistsInSchema): void
{
parent::addTableElements($syncedTable, $tableExistsInSchema);
$auditedAtColumn = TableSyncer::addColumnIfNotExists($syncedTable, $this->getAuditedAtFieldName(), [
'type' => 'TIMESTAMP',
'defaultExpr' => 'CURRENT_TIMESTAMP',
]);
$auditEventColumn = TableSyncer::addColumnIfNotExists($syncedTable, $this->getAuditEventFieldName(), [
'type' => PropelTypes::ENUM,
'valueSet' => 'insert, update, delete',
'required' => true,
]);
$changedValuesColumn = TableSyncer::addColumnIfNotExists($syncedTable, $this->getChangedValuesFieldName(), [
'type' => $this->getChangedValuesFieldType(),
'size' => $this->getChangedValuesFieldSize(),
]);

$fk = $this->findSyncedRelation($syncedTable->getForeignKeys());

InsertCodeBehavior::addToTable($this, $syncedTable, [
'preInsert' => '$this->' . $auditedAtColumn->getName() . ' ??= (new DateTime())->format(\'Y-m-d H:i:s.u\');',
'objectAttributes' => fn (ObjectBuilder $builder) => $this->renderLocalTemplate('auditObjectAttributes', [
'auditObjectFullyQualifiedClassName' => $builder->getObjectClassName(true),
'changedValuesColumnPhpName' => $changedValuesColumn->getPhpName(),
]),
'objectMethods' => fn (ObjectBuilder $builder) => $this->renderLocalTemplate('auditObjectMethods', [
'changedValuesColumnPhpName' => $changedValuesColumn->getPhpName(),
'auditObjectName' => $syncedTable->getPhpName(),
'auditIdColumnName' => $this->addPkAs(),
'auditedAtColumnName' => $auditedAtColumn->getName(),
'syncedPkColumns' => $this->getSyncedPrimaryKeyColumns($syncedTable),
'relationToSourceName' => $builder->getFKPhpNameAffix($fk, false),
'auditEventColumnPhpName' => $auditEventColumn->getPhpName(),
'queryClassName' => $builder->getQueryClassName(),
]),
]);
}

/**
* @return array<array{column: \Propel\Generator\Model\Column, isOmited: bool}>
*/
public function selectAuditedFields(): array
{
$ignoredFields = $this->getIgnoredFieldNames();
$omitedFields = $this->getOmitValueFields();
$omitedTypes = $this->getOmitValueFieldTypes();
$auditedFields = [];

foreach ($this->table->getColumns() as $column) {
$fieldName = $column->getName();
if (in_array($fieldName, $ignoredFields)) {
continue;
}
$isOmited = (in_array($fieldName, $omitedFields) || in_array($column->getType(), $omitedTypes));
$auditedFields[] = [
'column' => $column,
'isOmited' => $isOmited,
];
}

return $auditedFields;
}

/**
* @see Propel\Generator\Model\Behavior\Behavior::renderTemplate()
*
* @param string $filename
* @param array $vars
*
* @return string
*/
public function renderLocalTemplate(string $filename, array $vars = []): string
{
$templatePath = $this->getDirname() . '/templates/';

return $this->renderTemplate($filename, $vars, $templatePath);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
<?php

/**
* MIT License. This file is part of the Propel package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Propel\Generator\Behavior\Auditable;

use Propel\Generator\Behavior\SyncedTable\SyncedTableBehavior;
use Propel\Generator\Behavior\SyncedTable\SyncedTableBehaviorDeclaration;
use Propel\Generator\Behavior\SyncedTable\SyncedTableException;
use Propel\Generator\Model\PropelTypes;

class AuditableBehaviorDeclaration extends SyncedTableBehavior
{
/**
* @see \Propel\Generator\Behavior\SyncedTable\SyncedTableBehavior::DEFAULT_SYNCED_TABLE_SUFFIX
*
* @var string DEFAULT_SYNCED_TABLE_SUFFIX
*/
protected const DEFAULT_SYNCED_TABLE_SUFFIX = '_audit';

/**
* @var string
*/
public const PARAMETER_KEY_ADD_PK = 'id_field_name';

/**
* @var string
*/
public const PARAMETER_KEY_AUDITED_AT_FIELD_NAME = 'audited_at_field_name';

/**
* @var string
*/
public const PARAMETER_KEY_AUDIT_EVENT_FIELD_NAME = 'audit_event_field_name';

/**
* @var string
*/
public const PARAMETER_KEY_CHANGED_VALUES_FIELD_NAME = 'changed_values_field_name';

/**
* @var string
*/
public const PARAMETER_KEY_CHANGED_VALUES_FIELD_TYPE = 'changed_values_field_type';

/**
* @var string
*/
public const PARAMETER_KEY_CHANGED_VALUES_FIELD_SIZE = 'changed_values_field_size';

/**
* @var string
*/
public const PARAMETER_KEY_IGNORE_FIELDS = 'ignore_fields';

/**
* @var string
*/
public const PARAMETER_KEY_OMIT_VALUE_FIELDS = 'omit_value_fields';

/**
* @var string
*/
public const PARAMETER_KEY_OMIT_VALUE_TYPES = 'omit_value_types';

/**
* Placeholder for omited column values (defaults to 'changed')
*
* @var string
*/
public const PARAMETER_KEY_OMIT_VALUE = 'omit_value';

/**
* By default, the insert audit is empty, this parameter allows to specify
* columns that should be added.
*
* @var string
*/
public const PARAMETER_KEY_AUDITED_COLUMNS_ON_INSERT = 'audited_columns_on_insert';

/**
* Remove audit when source row is deleted.
*
* The default (false) implies audit_on_delete=true.
*
* @var string
*/
public const PARAMETER_KEY_CASCADE_DELETE = 'cascade_delete';

/**
* @see \Propel\Generator\Behavior\SyncedTable\SyncedTableBehavior::getDefaultParameters()
*
* @return array
*/
protected function getDefaultParameters(): array
{
// set SyncedTableBehavior values
return [
static::PARAMETER_KEY_ADD_PK => 'audit_id',
static::PARAMETER_KEY_SYNC_PK_ONLY => 'true',
static::PARAMETER_KEY_COLUMN_PREFIX => 'true',
];
}

/**
* @throws \Propel\Generator\Behavior\SyncedTable\SyncedTableException
*
* @return void
*/
public function validateParameters(): void
{
$disallowedParameters = [
SyncedTableBehaviorDeclaration::PARAMETER_KEY_EMPTY_ACCESSOR_COLUMNS,
SyncedTableBehaviorDeclaration::PARAMETER_KEY_IGNORE_COLUMNS,
SyncedTableBehaviorDeclaration::PARAMETER_KEY_INHERIT_FOREIGN_KEY_CONSTRAINTS,
SyncedTableBehaviorDeclaration::PARAMETER_KEY_INHERIT_FOREIGN_KEY_RELATIONS,
SyncedTableBehaviorDeclaration::PARAMETER_KEY_RELATION,
SyncedTableBehaviorDeclaration::PARAMETER_KEY_SYNC_INDEXES,
SyncedTableBehaviorDeclaration::PARAMETER_KEY_SYNC_UNIQUE_AS,
];

foreach ($disallowedParameters as $disallowedParameter) {
if (array_key_exists($disallowedParameter, $this->parameters)) {
throw new SyncedTableException($this, "Use of parameter '$disallowedParameter' is not allowed.");
}
}

parent::validateParameters();
$this->checkColumnsInParameterExistInTable(static::PARAMETER_KEY_IGNORE_FIELDS, true);
$this->checkColumnsInParameterExistInTable(static::PARAMETER_KEY_OMIT_VALUE_FIELDS, true);
$this->checkColumnsInParameterExistInTable(static::PARAMETER_KEY_AUDITED_COLUMNS_ON_INSERT, true);
}

/**
* @return string
*/
public function getAuditedAtFieldName(): string
{
return $this->getParameter(static::PARAMETER_KEY_AUDITED_AT_FIELD_NAME, 'audited_at');
}

/**
* @return string
*/
public function getAuditEventFieldName(): string
{
return $this->getParameter(static::PARAMETER_KEY_AUDIT_EVENT_FIELD_NAME, 'audit_event');
}

/**
* @return string
*/
public function getChangedValuesFieldName(): string
{
return $this->getParameter(static::PARAMETER_KEY_CHANGED_VALUES_FIELD_NAME, 'changed_values');
}

/**
* @return string
*/
public function getChangedValuesFieldType(): string
{
return $this->getParameter(static::PARAMETER_KEY_CHANGED_VALUES_FIELD_TYPE, PropelTypes::JSON);
}

/**
* @return int|null
*/
public function getChangedValuesFieldSize(): ?int
{
return $this->getParameterInt(static::PARAMETER_KEY_CHANGED_VALUES_FIELD_SIZE);
}

/**
* @return array<string>
*/
public function getIgnoredFieldNames(): array
{
return $this->getParameterCsv(static::PARAMETER_KEY_IGNORE_FIELDS);
}

/**
* @return array<string>
*/
public function getOmitValueFields(): array
{
return $this->getParameterCsv(static::PARAMETER_KEY_OMIT_VALUE_FIELDS);
}

/**
* @return array<string>
*/
public function getOmitValueFieldTypes(): array
{
return $this->getParameterCsv(static::PARAMETER_KEY_OMIT_VALUE_TYPES, ['BLOB, CLOB']);
}

/**
* @return string
*/
public function getOmitValue(): string
{
return $this->getParameter(static::PARAMETER_KEY_OMIT_VALUE, 'changed');
}

/**
* @return array<string>
*/
public function getAuditedColumnsOnInsert(): array
{
return $this->getParameterCsv(static::PARAMETER_KEY_AUDITED_COLUMNS_ON_INSERT);
}

/**
* @see \Propel\Generator\Behavior\SyncedTable\SyncedTableBehaviorDeclaration::getRelation()
*
* Relation is set by auditable behavior.
*
* @return array|true
*/
public function getRelation()
{
return $this->isCascadeDeletes() ? ['onDelete' => 'cascade'] : true;
}

/**
* @return bool
*/
protected function isCascadeDeletes(): bool
{
return $this->getParameterBool(static::PARAMETER_KEY_CASCADE_DELETE, false);
}
}
Loading

0 comments on commit c824859

Please sign in to comment.