Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Smoothie Laravel Package

baril/smoothie

Smoothie is a Laravel package by baril that brings a smooth, streamlined developer experience to PHP projects. It offers a clean, practical API for simplifying common application tasks, helping you write maintainable code and build Laravel apps faster with less boilerplate.

View on GitHub
Deep Wiki
Context7

Getting Started

Smoothie is a grab-bag of four Eloquent behaviors rather than a single feature, so start by deciding which one you actually need:

  • Fuzzy dates — partial-precision dates (1950, March 1950, 12 March 1950).
  • Mutual many-to-many — reciprocal pivot relationships (friends, follows, related products).
  • Orderable — explicit position columns with move helpers for drag-and-drop UIs.
  • Closure tables — adjacency + closure table trees (categories, threads, org charts).

Minimal install:

composer require baril/smoothie

The package was last released in July 2020 and is archived, so check composer.json constraints before assuming it installs on Laravel 10/11. If you're on an older Laravel it will auto-discover Baril\Smoothie\SmoothieServiceProvider; on legacy versions register the provider manually in config/app.php. Trait names and namespaces have shifted across the package's history — open src/ and confirm the exact use statements rather than trusting older blog posts.

Where to look first:

  1. README.md — one section per feature.
  2. src/ — the traits themselves; they are small and readable.
  3. tests/ — the most reliable usage examples, especially for mutual relations and closure-table moves.
  4. migrations/ (if you publish them) — the expected column names and indexes.

First use case: make an existing model orderable. Add a position integer column (indexed, ideally unique per scope), add the trait, and you have reordering in minutes:

use Baril\Smoothie\Orderable;

class Slide extends Model
{
    use Orderable;
}

$slides = Slide::ordered()->get();   // position ASC
$slide->moveAfter($other);           // persisted reorder
$slide->moveToStart();

That is the lowest-risk entry point: if it works, the rest of the package likely works too on your Laravel version.

Implementation Patterns

Fuzzy dates

Declare which attributes are fuzzy and let the trait cast them to a value object instead of a raw Carbon instance:

use Baril\Smoothie\FuzzyDates;

class Artwork extends Model
{
    use FuzzyDates;

    protected $fuzzyDates = ['created_at'];
}

$artwork->created_at->year;       // 1950
$artwork->created_at->precision;  // year | month | day
$artwork->created_at->format('Y'); // "1950"

Day-to-day pattern: keep two concepts separate in your UI — the display string ("c. 1950") and the sortable value (a normalized start/end range). Store precision alongside the date so users can correct 1950 to March 1950 without losing the original. For search, expose range scopes (whereFuzzyBetween) rather than comparing fuzzy objects directly; partial dates are inherently intervals.

Mutual many-to-many

For symmetric relationships (friends, follows, "related"), a single pivot row is ambiguous. The package's mutual relation writes both directions so $a->friends and $b->friends stay consistent. Pattern:

class User extends Model
{
    use MutualBelongsToMany;

    public function friends()
    {
        return $this->mutualBelongsToMany(User::class, 'friends', 'user_id', 'friend_id');
    }
}

$alice->friends()->attach($bob);
// Bob now sees Alice without a second insert.

Keep the pivot schema symmetric: two FKs plus a unique index on the ordered pair (or a canonical ordering enforced in code). Use the trait's attach/detach helpers everywhere — a raw DB::table('friends')->insert() bypasses the reciprocity and silently desynchronizes the graph.

Orderable

The common workflow is a sortable admin list:

Route::post('/slides/{slide}/move', function (Slide $slide, Request $request) {
    DB::transaction(function () use ($slide, $request) {
        match ($request->input('direction')) {
            'up'   => $slide->moveBefore($slide->previous()),
            'down' => $slide->moveAfter($slide->next()),
        };
    });
});

Tips that matter in practice:

  • Scope ordering by parent or tenant ($table->unique(['list_id', 'position'])) and always call the scoped ordered() query, never a bare orderBy('position').
  • Wrap multi-row moves in a transaction. Naïve position swaps can transiently violate a unique index; the package may or may not handle this, so verify with a unique constraint enabled.
  • For large reorders, batch the operation and reindex at the end rather than moving items one at a time.

Closure tables

Use this when you need cheap ancestor/descendant queries and frequent subtree moves (menus, categories, comment threads). The migration creates both the adjacency columns (parent_id) and a closure table (ancestor, descendant, depth).

class Category extends Model
{
    use ClosureTable;

    public function children()
    {
        return $this->hasMany(Category::class, 'parent_id');
    }
}

$root = Category::create(['name' => 'Root']);
$child = Category::create(['name' => 'Child']);
$child->appendTo($root);

$root->descendants;   // entire subtree, one join
$child->ancestors;    // breadcrumb trail
$child->makeRoot();   // detach subtree

Pattern: treat the closure table as derived data. After bulk imports or raw SQL updates, run the package's rebuild/repair routine (or your own recursive pass) before trusting descendants. For tree rendering, eager-load children recursively or fetch descendants once and build the tree in PHP — the latter is usually faster for deep trees.

Combining traits

The features compose well. A comment thread is the canonical example: a ClosureTable for nesting, Orderable scoped by parent_id for sibling order, and FuzzyDates for "posted around 2019". Add the traits in that order and make sure the orderable scope matches the tree scope, otherwise moveAfter will renumber siblings across different parents.

Gotchas and Tips

Archived and unmaintained. No fixes for new PHP/Laravel versions. Pin the exact version in composer.json, or fork it internally. Expect deprecation noise on PHP 8.1+ (implicit nullable parameters, dynamic properties, ReturnTypeWillChange). If you only need one behavior, reimplementing orderable or closure tables is often less work than keeping the package alive.

Verify trait names against src/. This package predates consistent naming conventions; older examples on the web use different trait names than current releases. A failing use statement is the most common installation problem.

Orderable concurrency. Position columns are not concurrency-safe by default. Two simultaneous drags can produce duplicate or skipped positions. Wrap moves in DB::transaction with lockForUpdate() on the affected rows, and consider a periodic reindex job.

Unique position indexes fight with swaps. If you add unique(['list_id', 'position']), a simple swap will fail mid-transaction. Either use a deferred constraint (Postgres), a temporary negative offset, or drop uniqueness and enforce it in application code.

Mutual relations and sync/detach. Symmetric writes mean sync() semantics are not always what you expect — detaching one direction should detach the inverse, but double-check with tests. Also guard against self-relations and duplicate pairs; a canonical ordering plus a unique index is the reliable fix.

Closure table write amplification. A subtree move rewrites one closure row per ancestor-descendant pair. Moving a large subtree is O(n·depth). For bulk restructuring, disable the trait's hooks, do the raw updates, then rebuild. Watch the depth column — an off-by-one there breaks ancestors/descendants silently.

Fuzzy date storage. Partial dates are easy to store badly. Never rely on MySQL's zero-date behavior (1950-00-00) unless you control sql_mode; storing precision explicitly is portable. Be careful with timezones — a fuzzy date should generally be timezone-naive.

Debugging. Use DB::listen() to inspect the SQL the traits generate; closure and mutual operations produce more queries than you'd guess. The package's own test suite is the best reference when behavior surprises you — read the test for the method before opening an issue that will never be answered.

Extension points. Most traits expose overridable accessors for column names (getOrderColumnName, parent/child key names on the closure trait) and the fuzzy date formatting. Prefer overriding these over editing vendor code; it keeps a future fork cheap.

New projects. For actively maintained alternatives, look at spatie/eloquent-sortable or kalnoy/nestedset (closure/nested-set trees), and model fuzzy dates as a small custom cast with explicit precision and date columns. Smoothie is best treated as a reference implementation or a legacy dependency, not a greenfield choice.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo