定制 edulazaro/laraterms 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

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

edulazaro/laraterms

Composer 安装命令:

composer require edulazaro/laraterms

包简介

Polymorphic taxonomies for Laravel. Define tags, categories or any custom classification in config. Hierarchical, slug-aware, attach to any model, counts cached, query scopes included.

README 文档

README

Polymorphic taxonomies for Laravel. Define tags, categories or any custom classification in config. Multi-tenant. Multi-locale. Hierarchical or flat. Spatie-compatible. Cross-locale search via auto-maintained search_text. Zero schema opinion beyond two tables.

Installation

composer require edulazaro/laraterms
php artisan vendor:publish --tag=laraterms-config
php artisan vendor:publish --tag=laraterms-migrations
php artisan migrate

Define your taxonomies

config/laraterms.php ships with tags and categories as examples. Edit or add what you need:

'taxonomies' => [
    'tags' => [
        'hierarchical'        => false,
        'max_terms_per_model' => null,
        'scope'               => 'tenant',  // or 'global'
    ],
    'categories' => [
        'hierarchical'        => true,
        'max_terms_per_model' => 1,
        'scope'               => 'tenant',
    ],
    'regions' => [
        'hierarchical' => true,
        'models'       => [\App\Models\Property::class],
    ],
],

Multi-tenant (scope-scoped)

By default taxonomies are tenant-scoped: each scope (Organization, Workspace, Team) has its own isolated terms. Define how to resolve the scope of any taxable model:

// AppServiceProvider::boot()
use EduLazaro\Laraterms\Facades\Laraterms;

Laraterms::resolveScopeUsing(fn ($model) => $model->organization ?? null);

Or per model:

class Post extends Model
{
    use HasTerms;

    public function termsScope(): ?\Illuminate\Database\Eloquent\Model
    {
        return $this->organization;
    }
}

The scope can be a Model, an array (['type' => 'organization', 'id' => 5]), a Scope value object, or null (global). It does not require a morph map: it works with FQCN as scope_type.

For taxonomies shared across all tenants (languages, countries), set scope: 'global'.

Make a model taxable

use EduLazaro\Laraterms\Concerns\HasTerms;

class Post extends Model { use HasTerms; }

API

// Attach / sync / detach
$post->attachTerm('Laravel', 'tags');                  // find-or-create
$post->attachTerms(['Laravel', 'PHP'], 'tags');
$post->syncTerms(['Laravel', 'Vue'], 'tags');           // replace in the taxonomy
$post->detachTerm('Laravel', 'tags');
$post->detachAll('tags');

// Read
$post->terms;                                           // all attached
$post->termsIn('tags');                                 // by taxonomy
$post->hasTermsIn('tags');                              // bool

// Query
Post::whereHasTerm('laravel', 'tags')->get();
Post::whereHasAnyTerm(['laravel', 'vue'], 'tags')->get();
Post::whereHasAllTerms(['laravel', 'tutorial'], 'tags')->get();
Post::whereInTaxonomy('categories')->get();

Multi-locale (i18n)

Each translatable field has two columns: the canonical one (name, description) and the translations one (name_translations, description_translations). Spatie-compatible (format: {"en": "...", "es": "..."}).

// Single-locale: use only `name`
Term::create(['name' => 'Laravel', 'taxonomy' => 'tags']);
$term->name;   // "Laravel"

// Multi-locale
Term::create([
    'name'              => 'Tag',                       // canonical fallback
    'name_translations' => ['en' => 'Tag', 'es' => 'Etiqueta'],
    'taxonomy'          => 'tags',
]);
$term->name;   // "Etiqueta" if locale=es, "Tag" if locale=en or fallback

The accessor on name and description resolves automatically: active locale, then fallback locale, then canonical column.

Spatie integration (opt-in, on your side)

If you want the full Spatie API on the *_translations columns:

class Term extends \EduLazaro\Laraterms\Models\Term
{
    use \Spatie\Translatable\HasTranslations;
    protected $translatable = ['name_translations', 'description_translations'];
}

Our accessor on name/description keeps working because it reads the raw attributes.

Cross-locale search

search_text is auto-maintained in saving() by concatenating all values across all locales. Language-agnostic LIKE search:

Term::search('impuesto')->get();                        // matches even if the user is on /en
Term::where('search_text', 'like', '%laravel%')->get();
Term::whereFullText('search_text', 'laravel')->get();   // if your engine supports FULLTEXT (default migration adds it)

Hierarchical

use EduLazaro\Laraterms\Support\TermTree;

$tree = TermTree::for('categories');                    // Collection of roots with children populated (1 query)

foreach (TermTree::flatten($tree) as [$term, $depth]) {
    echo str_repeat('. ', $depth) . $term->name . "\n";
}

$term->ancestors();                                     // Collection<Term> root to parent
$term->breadcrumb(' > ');                               // "Tech > Web > Laravel"
$term->descendantIds();                                 // all descendant ids

Model

$term = Term::findOrCreateByName('Laravel', 'tags', $organization);
Term::inTaxonomy('tags')->ordered()->get();
Term::byHandle('laravel', 'tags')->first();
Term::forScope($org)->inTaxonomy('tags')->get();
Term::forScopeOrGlobal($org)->inTaxonomy('tags')->get();
$term->refreshCount();

Activate / deactivate (soft hide)

Each term has is_active (default true). Deactivating hides the term from pickers for new attachments, but models already attached keep showing the badge. Useful for "this tag is no longer used, but old posts that have it keep showing it".

$term->deactivate();        // hide from pickers
$term->activate();          // re-activate
Term::active()->inTaxonomy('tags')->forScope($org)->get();        // only active
Term::inactive()->inTaxonomy('tags')->forScope($org)->get();      // only inactive

This is NOT soft-delete. If you want proper SoftDeletes (with withTrashed, restore, etc.), extend the model in your app:

class Term extends \EduLazaro\Laraterms\Models\Term {
    use \Illuminate\Database\Eloquent\SoftDeletes;
}
// + migration with $table->softDeletes();

Merge terms (mergeInto)

For duplicate cleanup ("we had laravel and Laravel Framework, merge them into laravel"):

$dup = Term::byHandle('laravel-framework', 'tags')->first();
$canonical = Term::byHandle('laravel', 'tags')->first();

$dup->mergeInto($canonical, deactivateSource: true);
// 1. Moves all termables from $dup to $canonical (without duplicating)
// 2. Recalculates terms_count on the canonical
// 3. Deactivates $dup (kept in DB but hidden from pickers).
//    Pass deactivateSource: false for a real delete with cascade.

Guard: both must belong to the same taxonomy and the same scope. Throws InvalidArgumentException otherwise.

Facade

use EduLazaro\Laraterms\Facades\Laraterms;

Laraterms::has('tags');
Laraterms::get('tags');                                   // TaxonomyDefinition
Laraterms::handles();                                     // ['tags', 'categories', ...]
Laraterms::register('moods', [...]);                      // runtime
Laraterms::resolveScopeUsing(fn ($m) => $m->organization);
Laraterms::scopeFor($model);                              // Scope VO

Schema

terms: id, taxonomy, scope_type, scope_id, parent_id, name, name_translations (JSON), handle, description, description_translations (JSON), search_text, color, sort_order, terms_count, meta (JSON), timestamps. Unique on (scope_type, scope_id, taxonomy, handle). FULLTEXT on search_text (best-effort, ignored if the engine does not support it).

termables: polymorphic pivot. term_id, termable_type, termable_id, sort_order, timestamps. Unique on (term_id, termable_type, termable_id).

Table names are configurable.

Exceptions

  • UnknownTaxonomyException: taxonomy handle not registered.
  • TooManyTermsException: exceeds max_terms_per_model.
  • RequiresHierarchyException: parent_id set on a flat taxonomy.

License

MIT.

统计信息

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

GitHub 信息

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

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-05-23

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固