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

Lynx Laravel Package

phpanonymous/lynx

Lynx is a Laravel package for rapid backend scaffolding, offering tools for CRUD resources, forms, and admin-style data management to reduce boilerplate and speed up common application development tasks.

View on GitHub
Deep Wiki
Context7

Getting Started

Verify the package's real surface before committing to it — it's a 17-star, zero-dependent package with a thin README, so the source is the documentation.

  1. Install and discover the entry point.

    composer require phpanonymous/lynx
    php artisan list | grep -i lynx
    php artisan vendor:publish --provider="PhpAnonymous\\Lynx\\LynxServiceProvider"
    

    Laravel's package discovery should register the service provider. If php artisan list shows no lynx:* command, the package is likely route- or trait-based instead — open vendor/phpanonymous/lynx/src/ and read the service provider first.

  2. Read these four files before writing code: the service provider (what it registers), the config file (naming, prefixes, model paths, and the new storage/disk settings), the generator command class (the exact flags it accepts), and the storage facade class added in 1.0.20. Skipping this is the main reason people bounce off small packages like this.

  3. First use case: scaffold CRUD for an existing model. Point the generator at one model with a simple table and inspect what lands on disk:

    php artisan lynx:generate User   # confirm exact signature from `php artisan list`
    git status                       # see exactly which files were created
    php artisan route:list --path=api
    

    Then hit the endpoint:

    curl -s http://localhost:8000/api/users | jq
    
  4. Validate against a throwaway database first. Run it on a fresh SQLite or a scratch schema — never on a production-connected .env — because generated routes and controllers may be immediately live.

  5. Wire up storage before testing the new upload path. 1.0.20 adds file uploads backed by a storage facade, which means generated controllers/files may now reference a disk that doesn't exist in your app:

    php artisan storage:link
    # confirm config/lynx.php (or config/filesystems.php) has the disk the package expects
    grep -rn "Storage\|disk" vendor/phpanonymous/lynx/src/ | head -40
    

    Then exercise an upload end-to-end with a real multipart request rather than trusting the generated code:

    curl -s -X POST http://localhost:8000/api/posts \
      -F "title=Hello" \
      -F "image=@./fixtures/photo.jpg" | jq
    

Implementation Patterns

Scaffold-once, own-forever. The dominant pattern for auto-generators is to run them at project kickoff, commit the output, and treat it as hand-written code afterwards. Re-running the generator on an already-customized controller is where data loss happens.

# One-time bootstrap for a new resource
php artisan lynx:generate Post
git add app/Http/Controllers app/Models routes/
git commit -m "chore: scaffold Post API via lynx"

Generate into a versioned, middleware-wrapped route group. The package will likely emit routes at whatever prefix its config specifies. Normalize that immediately so auth and throttling apply uniformly:

// routes/api.php
Route::prefix('v1')
    ->middleware(['auth:sanctum', 'throttle:api'])
    ->group(base_path('routes/lynx.php')); // or wherever it writes routes

If the package registers routes itself, override the prefix in config/lynx.php rather than editing generated files.

Table-driven generation for admin CRUD. For back-office APIs with many simple lookup tables, loop the generator over a list and review the diff in bulk:

for m in Category Tag Status Country Currency; do
  php artisan lynx:generate "$m"
done
git diff --stat

This is the package's strongest use case: high-volume, low-complexity resources where the boilerplate-to-value ratio is worst if written by hand.

Route file uploads through the package's storage facade, then wrap it. Since 1.0.20, generated upload handling goes through a facade provided by the package. Treat it as an adapter: call it from your own action/FormRequest rather than scattering facade calls through controllers, so a future version bump only touches one seam.

// app/Actions/StorePostImage.php
final class StorePostImage
{
    public function __construct(private StorePostImageRequest $request) {}

    public function handle(): string
    {
        // Confirm the exact method names in the package source before copying this.
        return \PhpAnonymous\Lynx\Facades\LynxStorage::putFile(
            'posts',
            $this->request->file('image')
        );
    }
}

Persist paths, not URLs. Whatever the facade returns, store the relative disk path in the database. Derive URLs at read time (Storage::disk('public')->url($path) or temporaryUrl() for private disks) so switching to S3 later doesn't require a data migration.

Validate uploads in a FormRequest, not the generated controller. The generator's validation is schema-shaped and rarely covers MIME/size. Add explicit rules and keep them out of the generated file:

public function rules(): array
{
    return [
        'title' => ['required', 'string', 'max:255'],
        'image' => ['required', 'file', 'mimes:jpg,jpeg,png,webp', 'max:5120'],
    ];
}

Layer customizations outside the generated files. Keep business logic in a service or action class, and validation in FormRequests, so the generated controller stays a thin shell you can regenerate safely:

// app/Http/Controllers/Api/PostController.php (generated)
public function store(StorePostRequest $request, CreatePost $createPost)
{
    return new PostResource($createPost->handle($request->validated()));
}

Shape output with API Resources. Generated controllers typically return models or raw arrays. Wrap responses in JsonResource / ResourceCollection so your contract is decoupled from the schema — critical if the package emits return $model; directly, and even more so when a stored file path leaks into the payload verbatim.

Pair with Sanctum and policies. Generated routes almost never ship with authorization. This matters more for uploads: an unauthenticated or unauthorized POST with a file is an unbounded storage-write primitive. Add $this->authorize() or policy middleware before exposing anything beyond a dev environment.

Gotchas and Tips

  • Maintenance risk is real. Zero dependents and 17 stars means no ecosystem pressure keeping it current with Laravel majors. Pin an exact version ("phpanonymous/lynx": "1.0.20"), and check the composer.json illuminate/* constraints against your Laravel version before upgrading. The new storage facade in 1.0.20 is exactly the kind of addition that can shift between patch releases — pin harder, not looser.
  • The storage facade is brand new; treat it as unproven. A single-line changelog ("solve the file upload with a new storage facade") gives you no API contract. Read src/Facades/ and its resolved class before writing against it, and don't assume it honours config/filesystems.php disks, visibility, or Storage::fake() in tests.
  • Disk resolution fails silently or confusingly. If uploads land in storage/app/private when you expected public, the facade is likely defaulting to a disk name from config/lynx.php. Publish the config, set the disk explicitly, then php artisan config:clear.
  • php artisan storage:link is not optional. Uploads that succeed but 404 on retrieval are almost always a missing symlink or a public vs private visibility mismatch, not a code bug.
  • Orphaned files. Generated CRUD rarely cleans up on destroy or on replace-during-update. Add deletion to your action layer — otherwise storage grows forever and you'll be writing a backfill script later.
  • Never trust the client filename or extension. Verify the facade sanitizes/randomizes names. If it preserves originals, path traversal (../../) and .php in a web-served directory become real. Validate with mimes: (content-based) rather than extensions:.
  • Upload limits bite before your validation does. upload_max_filesize, post_max_size, and client_max_body_size will reject a 6 MB file with an empty $_FILES payload, and you'll debug your validator instead of your php.ini / nginx config.
  • Regeneration clobbers edits. Assume any lynx:generate run overwrites. Commit or stash before re-running, and diff the output. If the package has a --force flag, treat it as destructive — and re-check after any update that the 1.0.20 generator now emits upload code into controllers you'd already customized.
  • Route ordering and collisions. Generated routes can shadow hand-written ones, especially catch-all {id} patterns. Run php artisan route:list after every generation and check ordering.
  • Mass-assignment and column leakage. Generators frequently derive $fillable and response payloads straight from the schema. That means auto-increment IDs, password, remember_token, soft-delete columns, internal flags — and now the file-path column — can end up writable or visible. Audit $fillable, $hidden, and every Resource before shipping.
  • php artisan config:cache masks config edits. After publishing or editing config/lynx.php, run php artisan config:clear — otherwise your changes silently don't apply and you'll debug the wrong thing.
  • N+1 queries are the default. Generated index methods rarely eager-load. Add ->with([...]) or a $with property on the model; verify with Telescope or DB::listen.
  • Naming inference is where it breaks. Irregular plurals, non-standard table names, and pivot tables often defeat auto-detection. If a generated route or model points at the wrong table, set protected $table explicitly on the model rather than fighting the convention.
  • Extension points to look for first: publishable stubs (vendor:publish --tag=lynx-stubs), a config array of model paths and disk names, and any Lynx::macro() or generator-callback hooks in the service provider. Stub overrides are usually the cleanest way to bend output to team conventions without forking — including making the generated upload code use your own action class.
  • Debugging workflow: php artisan list | grep lynx → route:list → tinker the generated controller's dependencies → dd() inside the generator command to see what it resolved. With a package this small, adding a temporary ray()/dump() in the command's handle() — or in the storage facade's put method — is faster than reading docs that likely don't exist.
  • Write a smoke test per generated resource. One feature test asserting 200 on index, 201 on store, and — new for 1.0.20 — 201 plus a successful Storage::fake() assertion on an upload catches broken routes, guard mismatches, mass-assignment errors, and disk misconfiguration immediately after generation, and gives you a safety net the package doesn't provide.
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.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console