PHP code example of gsebastiao / laravel-auditable

1. Go to this page and download the library: Download gsebastiao/laravel-auditable library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

gsebastiao / laravel-auditable example snippets




namespace App\Models;

use Gsebastiao\Auditable\Concerns\Auditable;
use Illuminate\Database\Eloquent\Model;

class Produto extends Model
{
    use Auditable;

    protected $fillable = ['nome', 'preco', 'status_id'];
}

$produto = Produto::create(['nome' => 'Café', 'preco' => 20, 'status_id' => 1]);

$produto->update(['preco' => 25]);

$produto->delete();

$produto->audits()->latest()->get();   // as auditorias deste produto, das mais novas para as mais antigas

App\Models\Produto::auditsFor(1)->get(['event', 'changes']);

Produto::where('categoria_id', 3)->auditedUpdate(['ativo' => false]);
Produto::where('categoria_id', 3)->auditedDelete();



namespace App\Models;

use Gsebastiao\Auditable\Concerns\Auditable;
use Gsebastiao\Auditable\Support\AuditOptions;
use Gsebastiao\Auditable\Support\ResolveMap;
use Illuminate\Database\Eloquent\Model;

class Produto extends Model
{
    use Auditable;

    public function getAuditOptions(): AuditOptions
    {
        return AuditOptions::defaults()
            ->resolveMap([
                'status_id' => ResolveMap::direct('Status', 'status', 'nome'),
            ]);
    }
}

'status_id'    => ResolveMap::direct('Status', 'status', 'nome'),
'categoria_id' => ResolveMap::direct('Categoria', 'categorias', 'titulo'),

// Parâmetros: (nome no log, tabela, coluna com o texto = 'nome', coluna procurada = 'id', filtros extra = [])
'tipo_id'      => ResolveMap::direct('Tipo', 'tabelas_gerais', 'descricao', scope: ['grupo' => 'tipo_cliente']),

'estado_id' => ResolveMap::join([
    // 1º item: a tabela onde está o valor gravado no campo
    ['table' => 'estados', 'key' => 'id'],
    // itens seguintes: cada ligação (LEFT JOIN); no último, o que mostrar
    ['table' => 'paises',
     'on' => ['paises.id', '=', 'estados.pais_id'],
     'column' => 'nome',
     'label' => 'País'],
]),

'preco_venda' => ResolveMap::alias('Preço de venda'),

public function getAuditOptions(): AuditOptions
{
    return AuditOptions::defaults()
        ->events(['created', 'updated'])          // não registrar exclusões
        ->except(['observacao_interna'])          // nunca mostrar este campo
        ->resolveMap([
            'status_id' => ResolveMap::direct('Status', 'status', 'nome'),
        ]);
}

$audit->user?->name;   // null quando a ação foi do sistema

$pedido = Pedido::create($dados)->audit(createdBy: $vendedor->id);

use Gsebastiao\Auditable\Audit;

$pedido = Audit::transaction(function () use ($dados) {
    $pedido = Pedido::create(['cliente_id' => $dados['cliente_id']]);

    foreach ($dados['itens'] as $item) {
        $pedido->itens()->create($item);
        Produto::find($item['produto_id'])->decrement('estoque', $item['quantidade']);
    }

    return $pedido;
});

DB::transaction(fn () => Audit::batch(function () {
    // ...
}));

$pedido->operation()->get();    // a ÚLTIMA operação em que o pedido participou (todas as tabelas)
$pedido->operations()->get();   // TODAS as operações do pedido, com todas as linhas de cada uma
$pedido->audits()->get();       // só as linhas do próprio pedido

Pedido::operationFor(42)->get();    // o mesmo, sem carregar o pedido
Pedido::operationsFor(42)->get();

Audit::inBatch($batch)->get();      // tudo de um batch

Audit::transaction(function () use ($dados) {
    $pedido = Pedido::create($dados);

    EmitirNotaFiscal::dispatch($pedido->id);   // uma job comum, sem código de auditoria
});

$pedido->auditAction('aprovado');

$pedido->auditAction('email_reenviado', ['para' => $cliente->email]);

$relatorio->auditAction('exportado', ['formato' => 'PDF']);

// Outro autor (ex.: num job da fila)
$pedido = Pedido::create($dados)->audit(createdBy: $this->userId);

// Outro nome de evento: em vez de "created", grava "importado"
$produto = Produto::create($linha)->audit(event: 'importado');

// Informação técnica extra
$produto->update($dados);
$produto->audit(debugInfo: ['origem' => 'api-parceiro']);

use Gsebastiao\Auditable\Audit;
use Gsebastiao\Auditable\Support\ResolveMap;
use Illuminate\Support\Facades\DB;

DB::table('pessoas')->where('id', $id)->update(['estado_id' => 7]);

Audit::for('pessoa', $id, 'updated')
    ->changes(['estado_id' => ['old' => 3, 'new' => 7]])
    ->resolveMap(['estado_id' => ResolveMap::direct('Estado', 'estados')])
    ->save();

DB::connection('legado')->table('pessoas')->audit('pessoa', $id, 'updated')
    ->changes(['estado_id' => ['old' => 3, 'new' => 7]])
    ->resolveMap(['estado_id' => ResolveMap::direct('Estado', 'estados')])
    ->save();

Produto::where('categoria_id', 3)->auditedUpdate(['ativo' => false]);

Produto::where('created_at', '<', now()->subYears(5))->auditedDelete();

use Gsebastiao\Auditable\Audit;

// De um registro
$produto->audits()->latest()->get();
Produto::auditsFor(42)->get();                      // sem carregar o produto (já vem do mais novo para o mais antigo)
Produto::auditsFor(42)->action('updated')->get();

// Da aplicação inteira
Audit::byUser(auth()->id())->latest()->limit(20)->get();
Audit::action(['aprovado', 'reprovado'])->get();
Audit::failures()->whereDate('created_at', today())->get();
Audit::forRecord(Produto::class, 42)->get();

$audit->user;            // o usuário (ou null)
$audit->subject;         // o registro auditado (null se foi apagado) — só em auditorias de models
$audit->changes;         // array com o que mudou
$audit->changeLines();   // o mesmo, em texto: ['Preco: 20 → 25', 'Status: Ativo → Bloqueado']
$audit->isFailure();     // foi uma falha?

public function show(Produto $produto)
{
    $historico = $produto->audits()->with('user')->latest()->get();

    return view('produtos.show', compact('produto', 'historico'));
}

use Illuminate\Support\Facades\Gate;

Gate::define('ver-auditoria', fn ($user) => $user->is_admin);

use Gsebastiao\Auditable\Audit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

Route::get('/auditoria', function (Request $request) {
    $audits = Audit::filter($request->all())
        ->with('user')
        ->latest()
        ->orderByDesc('id')
        ->paginate(30)
        ->withQueryString();

    return view('auditoria.index', ['audits' => $audits]);
})->middleware('auth')->can('ver-auditoria');

use App\Models\Produto;
use Gsebastiao\Auditable\Support\AuditColumnJoiner;

$query = Produto::query()->where('ativo', true);

AuditColumnJoiner::apply($query, Produto::class);

$produtos = $query->paginate(20);

AuditColumnJoiner::apply(
    $query,
    Produto::class,
    actions: ['created', 'updated', 'aprovado'],   // cada ação vira audit_<acao>_by e audit_<acao>_at
    userColumn: 'email',                           // coluna da tabela de usuários a mostrar
    dateFormat: 'DD/MM/YYYY',                      // ou null para a data crua
);

use App\Models\Produto;
use Gsebastiao\Auditable\Support\AuditWidget;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

Route::middleware('auth')->group(function () {
    // Para o GaAudit.full
    Route::post('/auditoria/produtos/completo', function (Request $request) {
        $id = $request->integer('id');

        return AuditWidget::full(Produto::operationsFor($id), recordId: $id);
    });

    // Para o GaAudit.simple
    Route::post('/auditoria/produtos/resumo', function (Request $request) {
        return AuditWidget::simple(Produto::auditsFor($request->integer('id')));
    });
});

> use Illuminate\Support\Facades\Gate;
>
> Gate::define('ver-auditoria', fn ($user) => $user->is_admin);
> 

$audit = Produto::auditsFor($id)->action('deleted')->first();

$produto = $audit->restore();

$audit->restore(withId: false);   // recria com um id novo

// Campos de neverSnapshot() (como password) não foram guardados.
// Se a coluna for obrigatória, informe um valor:
$audit->restore(attributes: ['password' => Hash::make(Str::random(16))]);

$audit->isRestorable();           // esta auditoria pode ser restaurada?

try {
    $gateway->cobrar($fatura);
} catch (\Throwable $e) {
    $fatura->auditFailure('cobranca', $e, ['gateway' => 'mpesa'], 'Não foi possível concluir o pagamento.');

    throw $e;
}

Audit::failures()->latest()->get();
$audit->isFailure();

> try {
>     Audit::transaction(function () use ($fatura) {
>         // ...
>     });
> } catch (\Throwable $e) {
>     $fatura->auditFailure('cobranca', $e);   // fora da transação: fica gravada
>
>     throw $e;
> }
> 

Schema::table('audit_table', function (Blueprint $table) {
    $table->unsignedBigInteger('tenant_id')->nullable()->index();
});

use Gsebastiao\Auditable\Audit;

public function boot(): void
{
    Audit::resolveTenantUsing(fn () => auth()->user()?->empresa_id);
}

use Gsebastiao\Auditable\Concerns\Auditable;
use Gsebastiao\Auditable\Concerns\BelongsToTenant;

class Produto extends Model
{
    use Auditable, BelongsToTenant;
}

Produto::withoutTenantScope()->get();
Audit::withoutTenantScope()->get();

  // app/Support/TenantAtual.php
  namespace App\Support;

  class TenantAtual
  {
      public function __invoke(): ?int
      {
          return auth()->user()?->empresa_id;
      }
  }
  

  // config/auditable.php
  'resolver' => App\Support\TenantAtual::class,
  

use Gsebastiao\Auditable\Audit;

// Só um trecho de código (seeders, importações, correções em massa)
Audit::withoutAuditing(function () {
    Produto::factory()->count(500)->create();
});

namespace App\Models;

use Gsebastiao\Auditable\Models\Audit as BaseAudit;

class Auditoria extends BaseAudit
{
    // opcional: outra tabela (senão usa config('auditable.table'))
    // protected $table = 'historico';
}

// config/auditable.php
'model' => App\Models\Auditoria::class,

use Gsebastiao\Auditable\Contracts\AuditRepository;

$this->app->bind(AuditRepository::class, \App\Auditoria\GravarNaFila::class);

private string $subjectKeyType = 'uuid';   // ids dos seus models
private string $userKeyType = 'integer';   // ids dos usuários
private string $tenantKeyType = 'integer'; // ids dos tenants (se usar)

use Gsebastiao\Auditable\Audit;
use Gsebastiao\Auditable\Concerns\Auditable;
use Gsebastiao\Auditable\Concerns\BelongsToTenant;
use Gsebastiao\Auditable\Support\AuditColumnJoiner;
use Gsebastiao\Auditable\Support\AuditOptions;
use Gsebastiao\Auditable\Support\AuditWidget;
use Gsebastiao\Auditable\Support\ResolveMap;

// ── No model ──────────────────────────────────────────────────────────
use Auditable;                                  // auditoria automática
use BelongsToTenant;                            // (opcional) filtro por tenant

public function getAuditOptions(): AuditOptions
{
    return AuditOptions::defaults()
        ->events(['created', 'updated', 'deleted'])
        ->only([...])  ->except([...])  ->neverSnapshot([...])
        ->onlyDirty()  ->logEmpty()  ->logTimestamps()  ->fullSnapshotOnDelete()
        ->resolveMap([
            'status_id' => ResolveMap::direct('Status', 'status', 'nome'),
            'estado_id' => ResolveMap::join([...]),
            'preco'     => ResolveMap::alias('Preço'),
        ]);
}

// ── Registrar ─────────────────────────────────────────────────────────
$model->auditAction('aprovado', ['motivo' => '...']);           // linha nova
$model->auditFailure('cobranca', $e, ['extra' => 1], 'Mensagem'); // falha
$model->audit(event: 'importado', createdBy: 5);                 // ajusta a entrada automática
Audit::for('pessoa', $id, 'updated')->changes([...])->save();   // sem model
Model::where(...)->auditedUpdate(['campo' => 'valor']);          // update em massa, auditado
Model::where(...)->auditedDelete();                              // delete em massa, auditado

// ── Operações ─────────────────────────────────────────────────────────
Audit::transaction(fn () => ...);      // transação + mesmo batch
Audit::batch(fn () => ...);            // só o mesmo batch
Audit::currentBatch();                 // batch atual (ou null)
Audit::useBatch($batch, fn () => ...); // usar um batch específico (manual)
Audit::withoutAuditing(fn () => ...);  // não auditar este trecho

// ── Consultar ─────────────────────────────────────────────────────────
$model->audits();         Model::auditsFor($id);      // linhas do registro
$model->operation();      Model::operationFor($id);   // última operação
$model->operations();     Model::operationsFor($id);  // todas as operações
$model->batchOf();                                    // id da última operação

Audit::action('updated')  Audit::byUser($id)  Audit::inBatch($b)
Audit::failures()         Audit::forRecord(Model::class, $id)  Audit::withoutTenantScope()
Audit::filter($request->all())                        // vários filtros (telas de pesquisa)

$audit->user   $audit->subject   $audit->changes   $audit->changeLines()
$audit->isFailure()   $audit->isRestorable()   $audit->restore()

// ── Telas ─────────────────────────────────────────────────────────────
AuditColumnJoiner::apply($query, Model::class);           // colunas numa listagem
AuditWidget::full(Model::operationsFor($id), recordId: $id); // JSON do GaAudit.full
AuditWidget::simple(Model::auditsFor($id));                  // JSON do GaAudit.simple

// ── Multitenancy ──────────────────────────────────────────────────────
Audit::resolveTenantUsing(fn () => auth()->user()?->empresa_id);
Model::withoutTenantScope();
bash
php artisan vendor:publish --tag=auditable-config   # opcional
bash
php artisan vendor:publish --tag=auditable-migrations   # opcional
bash
php artisan migrate
bash
php artisan tinker
bash
php artisan auditable:publish-js
bash
php artisan vendor:publish --tag=auditable-config       # config/auditable.php
bash
php artisan vendor:publish --tag=auditable-migrations   # (opcional) copia a migration, para editá-la
bash
php artisan auditable:publish-js [--path=...] [--force] # widget JS em public/