v5: PHP 8.4+, Laravel 11+, modernized API#1452
Conversation
Breaking changes: - Rename activities() to activitiesAsSubject(), actions() to activitiesAsCauser() - Add HasActivity trait that combines LogsActivity + CausesActivity - Rename ActivityLogStatus to ActivitylogStatus (consistent casing) - Rename dontSubmitEmptyLogs() to dontLogEmptyChanges() - Rename withoutLogs() to withoutLogging() - Make getActivitylogOptions() optional (defaults to LogOptions::defaults()) - Require PHP 8.4+ and Laravel 11+ - Consolidate 3 migrations into single migration New features: - ActivityEvent enum (Created, Updated, Deleted, Restored) - CauserResolver::withCauser() for scoped causer overrides - Global default_except_attributes config option - LogOptions serialization safety (strips closures) - Boost v2 guidelines file Modernization: - Use Str::uuid() instead of ramsey/uuid - Use array_find() (PHP 8.4) instead of Arr::first() - Casts as methods on Activity model - Strict comparisons, short nullable notation, typed parameters Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Update version references from v4 to v5 - Update requirements to PHP 8.4+ and Laravel 11+ - Document optional getActivitylogOptions() (no config needed for basic usage) - Document HasActivity trait combining LogsActivity and CausesActivity - Document ActivityEvent enum - Document CauserResolver::withCauser() for scoped causer overrides - Document default_except_attributes config option - Rename dontSubmitEmptyLogs to dontLogEmptyChanges - Rename withoutLogs to withoutLogging - Rename actions() to activitiesAsCauser() - Update LogOptions API reference with new property/method names Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Provides a cleaner alternative to using CauserResolver directly: Activity::defaultCauser($admin, fn() => ...); // scoped Activity::defaultCauser($admin); // global Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add Activity::batch(fn) as friendly API for batching activities - Remove CauserResolver facade (use Activity::defaultCauser() instead) - Update tests to use CauserResolver class directly - Add v4 to v5 upgrade guide to UPGRADING.md - Update docs for new API surface - Update Boost skill guidelines Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Schema changes: - Add attribute_changes column for tracked model changes - Remove batch_uuid column (batch system dropped) - properties column now stores only custom user data Removed: - LogBatch class and LogBatch facade - Activity::batch() method - scopeHasBatch() and scopeForBatch() scopes - Batch-related tests and documentation Renamed: - getExtraProperty() to getProperty() - $activity->changes() to $activity->attribute_changes The properties column is now clean user-owned space. Model attribute tracking (attributes/old) is stored in the dedicated attribute_changes column, eliminating the previous mixing of change data and user data. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Require Laravel 12+ (for future attribute support) - Remove scopes from Activity contract interface (scopes are a query builder concern, not a model contract concern) - Contract now only requires: subject(), causer(), getProperty() - Custom Activity models no longer need to implement scope methods Note: #[Scope] attribute was investigated but doesn't work with Activity::inLog() static calls (PHP resolves the non-static method directly). Keeping scope prefix convention for now. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Update run-tests.yml matrix to only PHP 8.4/8.5 and Laravel 12/13 - Remove nesbot/carbon constraint from CI - Keep ActivityLogStatus class name (php-cs-fixer enforces it) - Update all references to use ActivityLogStatus consistently - Remove class rename from UPGRADING.md Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Update README to use getProperty() and attribute_changes - Remove CouldNotLogChanges exception class (never thrown anywhere) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Source: - Fix getProperty() signature mismatch between contract and model - Add withChanges() to Activity facade docblock - Remove duplicate import in ActivitylogServiceProvider Tests: - Remove unused getActivitylogOptions() function in DetectsChangesTest Docs: - Fix typo "litte" in introduction.md - Fix "Pretty Zonda" in logging-model-events.md - Fix key order in introduction.md attribute_changes example Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Config changes: - Rename env var ACTIVITY_LOGGER_ENABLED to ACTIVITYLOG_ENABLED - Rename delete_records_older_than_days to clean_after_days - Rename subject_returns_soft_deleted_models to include_soft_deleted_subjects - Remove table_name and database_connection options (use custom model instead) - Fix default_except_attributes comment (merged, not overridden) Activity model: - Hardcode $table = 'activity_log' instead of reading from config - Remove constructor that read config (custom table/connection via subclass) Migration: - Hardcode table name instead of reading from config Updated docs, UPGRADING.md, Boost skill, and tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Core operations are now handled by action classes with small overridable protected methods, following the Spatie action pattern. New files: - src/Actions/LogActivityAction.php (resolveDescription, tapActivity, save) - src/Actions/CleanActivityLogAction.php (getCutOffDate, deleteOldActivities) - src/ActivitylogConfig.php (resolves action classes from config, validates they extend base) Both actions are configurable via config/activitylog.php and validated by ActivitylogConfig to ensure custom classes extend the originals. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The action calls save(), accesses attributes, and reads relationships, which are all Model concerns. The Activity contract is too narrow for what the action actually needs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Clearer name that describes when it runs, following Laravel's beforeSave/beforeDelete naming convention. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Split all && and || conditions into separate if statements with early returns. Extracted helper methods for better readability: - resolveModelForLogging() - determines which model instance to log - shouldLogOnlyDirtyAttributes() - checks if dirty filtering applies - filterDirtyAttributes() - performs the actual dirty diff - isUpdatedEvent() / isDeletedEvent() - named event checks Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pipes for addLogChange() no longer need to implement an interface. Any class with a handle(EventLogBag, Closure) method works (standard Laravel pipeline convention). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Support/ -> ActivityLoggerTest, CauserResolverTest - Models/ -> ActivityModelTest, CustomActivityModelTest, CustomDatabaseConnectionActivityModelTest, CustomTableNameModelTest - Commands/ -> CleanActivitylogCommandTest - Traits/ -> LogsActivityTest, CausesActivityTest, DetectsChangesTest Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract pure logic into ChangeDetector support class: - resolveRelatedAttribute() for dot-notation relations - resolveJsonAttribute() for JSON arrow-notation - resolveRelationName() for relation method discovery - filterDirty() and compareValues() for dirty attribute diffing Simplify LogsActivity trait: - attributesToBeLogged() now uses collection pipeline - Split into fillableAttributes(), unguardedAttributes(), explicitAttributes(), excludedAttributes() - buildChanges() replaces attributeValuesToBeLogged() - runChangesPipeline() extracts pipeline logic from boot - shouldSkipEmptyLog() replaces inline check - hasChangedAttributesBeyondIgnored() extracts dirty check - formatAttributeValue() checks casts before dates (fixes custom date cast order) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Removed: - addLogChange() static method - $changesPipes static array - EventLogBag DTO - LoggablePipe contract - runChangesPipeline() method - Pipeline dependency in LogsActivity trait - 3 pipe-related tests - manipulate-changes-array.md docs page - event-bag.md API docs page Users who need to transform the changes array before saving should override transformChanges() on a custom LogActivityAction instead. This is simpler (one method override vs registering pipe classes) and consistent with the action pattern used throughout v5. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Resolve conflicts: - Remove LogBatch files (batch system removed in v5) - Use SerializableClosure version for LogOptions serialization Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Laravel 13 may touch updated_at during restore, making more than just deleted_at dirty. Instead of checking dirty count === 1, check that deleted_at was previously non-null and is now null. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
| Schema::connection(config('activitylog.database_connection'))->create(config('activitylog.table_name'), function (Blueprint $table) { | ||
| $table->bigIncrements('id'); | ||
| $table->string('log_name')->nullable(); | ||
| Schema::create('activity_log', function (Blueprint $table) { |
There was a problem hiding this comment.
I would suggest naming it activity_logs to match Laravel naming conventions
|
Would like buffer behaviour to bulk insert logs to database, just like how laravel nightwatch do |
|
We heavily rely on the batch system. Any reason it gets dropped? |
|
The batch system was removed in v5 to simplify the package. It added a lot of complexity for a feature that most users didn't need. The good news is that you can rebuild it in userland with very little code, using the 1. Add the column: Schema::table('activity_log', function (Blueprint $table) {
$table->uuid('batch_uuid')->nullable()->index();
});2. Register the hook in your service provider: use Illuminate\Support\Str;
use Spatie\Activitylog\Facades\Activity;
class AppServiceProvider extends ServiceProvider
{
public function boot(): void
{
$batchUuid = null;
Activity::beforeLogging(function ($activity) use (&$batchUuid) {
if ($batchUuid) {
$activity->batch_uuid = $batchUuid;
}
});
}
}3. Start and end batches wherever you need them: $batchUuid = (string) Str::uuid();
$author = Author::create(['name' => 'Philip K. Dick']);
$book = Book::create(['title' => 'A Scanner Darkly', 'author_id' => $author->id]);
$book->update(['title' => 'Updated title']);
$batchUuid = null;All activities between setting and clearing To query by batch: Activity::where('batch_uuid', $uuid)->get();No custom model needed. See the beforeLogging hook docs for more details. |
|
We've added this! Enable it with a single env variable: All activities are then collected in memory during the request and inserted in a single bulk query after the response is sent. No code changes needed. Docs: https://github.com/spatie/laravel-activitylog/blob/v5/docs/advanced-usage/buffering.md |
|
@freekmurze Thanks for that detailed explanation. |
Summary
v5 major release. Modernized API, simplified configuration, swappable action classes, PHP 8.4+/Laravel 12+.
Breaking Changes
Schema
attribute_changescolumn (new, json): tracked model changes ({"attributes": {...}, "old": {...}})propertiescolumn: now exclusively custom user data (viawithProperties())batch_uuidcolumn: removed (batch system dropped)Batch system removed
LogBatch,LogBatchfacade, batch scopes all removed. Group activities with custom properties if needed.Trait collision fix (#215, #115, #18, #224)
activities()->activitiesAsSubject()actions()->activitiesAsCauser()HasActivitytrait combines bothCauserResolver facade removed
Use
Activity::defaultCauser()instead.Config simplified
ACTIVITY_LOGGER_*->ACTIVITYLOG_*delete_records_older_than_days->clean_after_dayssubject_returns_soft_deleted_models->include_soft_deleted_subjectstable_nameanddatabase_connectionremoved (use custom model)actionsconfig for swappable action classesRenamed methods
$activity->changes()$activity->attribute_changesgetExtraProperty()getProperty()dontSubmitEmptyLogs()dontLogEmptyChanges()withoutLogs()withoutLogging()$model->activities(LogsActivity)$model->activitiesAsSubject$model->actions(CausesActivity)$model->activitiesAsCauserOther breaking changes
getActivitylogOptions()now optional (defaults to no attribute tracking)New Features
Action classes
Core operations extracted into overridable action classes:
LogActivityAction(protected methods:resolveDescription,tapActivity,save)CleanActivityLogAction(protected methods:getCutOffDate,deleteOldActivities)Swappable via config, validated by
ActivitylogConfighelper.ActivityEventenumActivity::defaultCauser()(#582, #649, #661)Scoped or global causer override.
Global excluded columns (#1100, #460)
default_except_attributesconfig option.LogOptions serialization fix (#1450)
Safely strips closures during serialization.
Boost v2 skill
resources/boost/guidelines/core.blade.phpfor Laravel Boost auto-discovery.Modernization
Str::uuid()replacesramsey/uuidarray_find()(PHP 8.4)casts()method$table->id(), nodown()in migrationRelated
Issues: #215, #115, #18, #224, #1371, #1450, #582, #649, #661, #460
Discussions: #1063, #1100