承接 jimmyahalpara/laravel-activitylog-enricher 相关项目开发

从需求分析到上线部署,全程专人跟进,保证项目质量与交付效率

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

jimmyahalpara/laravel-activitylog-enricher

最新稳定版本:1.1.0

Composer 安装命令:

composer require jimmyahalpara/laravel-activitylog-enricher

包简介

A Laravel package to enrich Spatie ActivityLog entries by resolving foreign key IDs to readable labels

README 文档

README

Latest Version on Packagist Total Downloads GitHub Tests Action Status PHP Stan

A Laravel package that enriches Spatie ActivityLog entries by resolving foreign key IDs to human-readable labels, making your audit trails more meaningful and user-friendly.

Features

  • 🔄 Automatic Foreign Key Resolution: Convert foreign key IDs to readable labels
  • 🏗️ Nested Array Support: Handle complex nested structures like items.*.material_id
  • 🎯 Flexible Label Sources: Support for model attributes, accessor methods, or custom methods
  • 🗂️ Predefined Configurations: Reusable mapping configurations via config file
  • 🛡️ Soft Delete Awareness: Handles soft-deleted models gracefully
  • 🚫 Error Resilient: Continues enrichment even when some models are missing
  • 🧪 Fully Tested: Comprehensive test suite with 100% coverage
  • 📋 Type Safe: Full PHP 8.1+ type declarations and PHPStan level 8 compliance

Requirements

  • PHP 8.2 or higher
  • Laravel 9.0, 10.0, 11.0, or 12.0
  • Spatie Laravel ActivityLog 4.0+

Installation

Install the package via Composer:

composer require jimmyahalpara/laravel-activitylog-enricher

The package will automatically register its service provider.

Optionally, publish the configuration file:

php artisan vendor:publish --provider="JimmyAhalpara\ActivityLogEnricher\ActivityLogEnricherServiceProvider" --tag="activity-log-enricher-config"

Quick Start

Basic Usage

Use the enricher in your model's tapActivity method:

<?php

use JimmyAhalpara\ActivityLogEnricher\Facades\ActivityLogEnricher;
use Spatie\Activitylog\LogOptions;
use Spatie\Activitylog\Models\Activity;
use Spatie\Activitylog\Traits\LogsActivity;

class Job extends Model
{
    use LogsActivity;

    public function tapActivity(Activity $activity, string $eventName): void
    {
        ActivityLogEnricher::enrichActivity($activity, [
            'customer_id' => [
                'class' => Contact::class,
                'label_attribute' => 'name',
                'new_key' => 'customer_name',
            ],
            'design_id' => [
                'class' => Design::class,
                'label_attribute' => 'title',
                'new_key' => 'design_title',
            ],
        ]);
    }

    public function getActivitylogOptions(): LogOptions
    {
        return LogOptions::defaults()
            ->logAll()
            ->logOnlyDirty()
            ->dontSubmitEmptyLogs();
    }
}

Before and After Comparison

Before enrichment:

{
  "old": {
    "customer_id": 1,
    "design_id": 5
  },
  "attributes": {
    "customer_id": 2,
    "design_id": 5
  }
}

After enrichment:

{
  "old": {
    "customer_id": 1,
    "customer_name": "ABC Corporation",
    "design_id": 5,
    "design_title": "Premium Package Design"
  },
  "attributes": {
    "customer_id": 2,
    "customer_name": "XYZ Industries",
    "design_id": 5,
    "design_title": "Premium Package Design"
  }
}

Configuration Options

Field Mapping Configuration

Each field mapping supports the following options:

'foreign_key_name' => [
    'class' => ModelClass::class,           // Required: The model class to resolve
    'label_attribute' => 'attribute_name',  // Optional: Attribute/method for label (default: 'label')
    'new_key' => 'enriched_key_name',       // Optional: New key name (default: guessed from foreign_key)
]

Label Attribute Resolution

The enricher tries multiple methods to resolve labels in this order:

  1. Direct method call: $model->getLabelWithoutClass()
  2. Accessor method: $model->getLabelAttribute()
  3. Direct attribute: $model->label
  4. Appended attribute: If label is in the model's $appends array
  5. Fallback: (string) $model

Advanced Usage

Nested Array Enrichment

Perfect for enriching line items or nested structures:

ActivityLogEnricher::enrichActivity($activity, [
    'items.*.product_id' => [
        'class' => Product::class,
        'label_attribute' => 'name',
        'new_key' => 'product_name',
    ],
    'items.*.category_id' => [
        'class' => Category::class,
        'label_attribute' => 'title',
        'new_key' => 'category_title',
    ],
]);

Input:

{
  "attributes": {
    "items": [
      {"product_id": 1, "category_id": 2, "quantity": 5},
      {"product_id": 3, "category_id": 1, "quantity": 2}
    ]
  }
}

Output:

{
  "attributes": {
    "items": [
      {
        "product_id": 1,
        "product_name": "Laptop Computer",
        "category_id": 2,
        "category_title": "Electronics",
        "quantity": 5
      },
      {
        "product_id": 3,
        "product_name": "Office Chair",
        "category_id": 1,
        "category_title": "Furniture",
        "quantity": 2
      }
    ]
  }
}

Using Predefined Configurations

Define reusable configurations in config/activity-log-enricher.php:

'mappings' => [
    'orders' => [
        'customer_id' => [
            'class' => Customer::class,
            'label_attribute' => 'company_name',
            'new_key' => 'customer',
        ],
        'items.*.product_id' => [
            'class' => Product::class,
            'label_attribute' => 'name',
            'new_key' => 'product',
        ],
    ],
],

Then use in your model:

public function tapActivity(Activity $activity, string $eventName): void
{
    ActivityLogEnricher::enrichActivityWithConfig($activity, 'orders');
}

Custom Label Methods

Use any model method for label generation:

class Material extends Model
{
    public function getLabelWithoutClass(): string
    {
        return $this->name . ' (' . $this->code . ')';
    }
    
    public function getFullDescriptionAttribute(): string
    {
        return $this->name . ' - ' . $this->description;
    }
}

// Usage
ActivityLogEnricher::enrichActivity($activity, [
    'material_id' => [
        'class' => Material::class,
        'label_attribute' => 'getLabelWithoutClass', // Direct method
    ],
    'secondary_material_id' => [
        'class' => Material::class,
        'label_attribute' => 'full_description', // Accessor attribute
    ],
]);

Automatic Key Guessing

When new_key is not specified, the enricher automatically removes _id suffix:

ActivityLogEnricher::enrichActivity($activity, [
    'customer_id' => [
        'class' => Customer::class,
        'label_attribute' => 'name',
        // new_key will be automatically set to 'customer'
    ],
    'shipping_address_id' => [
        'class' => Address::class,
        'label_attribute' => 'full_address',
        // new_key will be automatically set to 'shipping_address'
    ],
]);

Real-World Examples

E-commerce Order System

class Order extends Model
{
    use LogsActivity;

    public function tapActivity(Activity $activity, string $eventName): void
    {
        ActivityLogEnricher::enrichActivity($activity, [
            'customer_id' => [
                'class' => Customer::class,
                'label_attribute' => 'full_name',
                'new_key' => 'customer',
            ],
            'billing_address_id' => [
                'class' => Address::class,
                'label_attribute' => 'formatted_address',
                'new_key' => 'billing_address',
            ],
            'shipping_address_id' => [
                'class' => Address::class,
                'label_attribute' => 'formatted_address',
                'new_key' => 'shipping_address',
            ],
            'items.*.product_id' => [
                'class' => Product::class,
                'label_attribute' => 'name',
                'new_key' => 'product',
            ],
            'items.*.variant_id' => [
                'class' => ProductVariant::class,
                'label_attribute' => 'display_name',
                'new_key' => 'variant',
            ],
        ]);
    }
}

Project Management System

class Task extends Model
{
    use LogsActivity;

    public function tapActivity(Activity $activity, string $eventName): void
    {
        ActivityLogEnricher::enrichActivity($activity, [
            'project_id' => [
                'class' => Project::class,
                'label_attribute' => 'name',
                'new_key' => 'project',
            ],
            'assigned_to' => [
                'class' => User::class,
                'label_attribute' => 'full_name',
                'new_key' => 'assignee',
            ],
            'created_by' => [
                'class' => User::class,
                'label_attribute' => 'full_name',
                'new_key' => 'creator',
            ],
            'attachments.*.document_id' => [
                'class' => Document::class,
                'label_attribute' => 'filename',
                'new_key' => 'document',
            ],
        ]);
    }
}

Manufacturing System (Based on Original Usage)

class Job extends Model
{
    use LogsActivity;

    public function tapActivity(Activity $activity, string $eventName): void
    {
        ActivityLogEnricher::enrichActivity($activity, [
            'customer_id' => [
                'class' => Contact::class,
                'label_attribute' => 'label',
                'new_key' => 'customer',
            ],
            'design_id' => [
                'class' => Design::class,
                'label_attribute' => 'label',
                'new_key' => 'design',
            ],
            'plate_id' => [
                'class' => Plate::class,
                'label_attribute' => 'label',
                'new_key' => 'plate',
            ],
            'laminate_material_id' => [
                'class' => Material::class,
                'label_attribute' => 'getLabelWithoutClass',
                'new_key' => 'laminate_material',
            ],
            'cap_material_id' => [
                'class' => Material::class,
                'label_attribute' => 'getLabelWithoutClass',
                'new_key' => 'cap_material',
            ],
            'should_material_id' => [
                'class' => Material::class,
                'label_attribute' => 'getLabelWithoutClass',
                'new_key' => 'shoulder_material',
            ],
            'pvc_sleeve_material_id' => [
                'class' => Material::class,
                'label_attribute' => 'getLabelWithoutClass',
                'new_key' => 'pvc_sleeve_material',
            ],
            'liner_material_id' => [
                'class' => Material::class,
                'label_attribute' => 'getLabelWithoutClass',
                'new_key' => 'liner_material',
            ],
            'granules_material_id' => [
                'class' => Material::class,
                'label_attribute' => 'getLabelWithoutClass',
                'new_key' => 'granules_material',
            ],
            'cold_foil_material_id' => [
                'class' => Material::class,
                'label_attribute' => 'getLabelWithoutClass',
                'new_key' => 'cold_foil_material',
            ],
        ]);
    }
}

Error Handling

The enricher is designed to be resilient and will continue processing even when encountering errors:

  • Missing models: If a foreign key references a non-existent model, the enrichment for that field is skipped
  • Invalid classes: Throws InvalidModelException for configuration errors
  • Missing attributes: Falls back to string representation of the model
  • Soft deleted models: Automatically included using withTrashed()

Exception Types

use JimmyAhalpara\ActivityLogEnricher\Exceptions\InvalidModelException;

try {
    ActivityLogEnricher::enrichActivity($activity, $invalidMapping);
} catch (InvalidModelException $e) {
    // Handle configuration errors
    Log::error('ActivityLog enrichment failed: ' . $e->getMessage());
}

Performance Considerations

Eager Loading

To optimize performance, ensure your models are properly eager loaded before enrichment:

// In your model or controller
$activities = Activity::with(['subject', 'causer'])->get();

Caching

Consider implementing model caching for frequently accessed labels:

class Product extends Model
{
    public function getLabelAttribute(): string
    {
        return Cache::remember(
            "product_label_{$this->id}",
            3600,
            fn() => $this->name . ' (' . $this->sku . ')'
        );
    }
}

Testing

Run the test suite:

composer test

Run tests with coverage:

composer test-coverage

Run static analysis:

composer analyse

Run code formatting:

composer format

Run all quality checks:

composer quality

Contributing

Contributions are welcome! Please follow these steps:

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/amazing-feature
  3. Make your changes and add tests
  4. Ensure all tests pass: composer quality
  5. Commit your changes: git commit -m 'Add amazing feature'
  6. Push to the branch: git push origin feature/amazing-feature
  7. Submit a pull request

Development Setup

git clone https://github.com/jimmyahalpara/laravel-activitylog-enricher.git
cd laravel-activitylog-enricher
composer install
composer quality

Security

If you discover any security-related issues, please email jimmy@ahalpara.in instead of using the issue tracker.

Credits

License

The MIT License (MIT). Please see License File for more information.

Changelog

Please see CHANGELOG for more information on what has changed recently.

Support

If you find this package useful, please consider:

  • ⭐ Starring the repository
  • 🐛 Reporting issues
  • 💡 Contributing improvements
  • 📢 Sharing with others

For support, questions, or feature requests, please open an issue on GitHub.

统计信息

  • 总下载量: 2
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 0
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2025-10-21