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

Laravel Stringer Laravel Package

giorgigrdzelidze/laravel-stringer

A Laravel package for fluent, chainable string manipulation, offering convenient helpers to transform, format, and inspect strings. Simplifies common text tasks in your apps with a clean, expressive API.

View on GitHub
Deep Wiki
Context7

Getting Started

This release makes Stringer a complete "topic → drafted, translated, SEO-tagged, illustrated article" pipeline you can wire into an existing Laravel app without adopting its storage model. Start here:

  1. Upgrade and re-seed. There are no new tables, but v0.3.0 ships 12 content fields (up from 5) and 3 prompt rows (draft, translate, cover_image). The seeders are now idempotent and upsert by name / (key, locale), so running the migration + seeders on an existing install adds the new fields (slug, og_*, twitter_*) and the cover_image prompt without touching operator edits:

    php artisan migrate
    php artisan db:seed --class="StringerDefaultContentFieldsSeeder"
    php artisan db:seed --class="StringerDefaultPromptsSeeder"
    
  2. Configure the image path before enabling it. Cover generation is off by default. Set:

    • STRINGER_IMAGE_ENABLED=true — turns on the GenerateDraftJob → GenerateCoverImageJob chain and upgrades Telegram notifications to photo cards.
    • STRINGER_IMAGE_DRIVER= — pick early. For a zero-key smoke test use Picsum; for real photos on a budget use Unsplash (free, but fill attribution); Imagen 3 reuses your existing STRINGER_GEMINI_API_KEY, so it's the least-friction default for people already using Gemini drafts.
    • STRINGER_IMAGE_MAX_REGENERATES=3 — hard cap on operator regenerate taps.
  3. Look first at these files:

    • config/stringer.php — image driver, enable flag, regenerate cap, admin URL for Telegram links.
    • ContentTarget contract — the only integration surface that matters; the package never imports Spatie Media Library or S3 directly.
    • StringerPromptResource in Filament — the three editable prompt rows (draft, translate, cover_image); this is where operators tune behavior without deploys.
    • README screenshots — five themed groups (Topics queue, Articles dashboard, Telegram pipeline, Telegram menu, Operator surfaces) give a much faster mental model than the config file alone.
  4. First use case: create a Topic, let GenerateDraftJob commit a draft, then inspect the draft in Filament. With images enabled you'll immediately see whether your attachCover() host hook is doing its job — the draft commit succeeds independently of cover generation, so a broken image path won't block the pipeline, it'll just surface as a ⚠️ Cover generation failed via {driver} Telegram message.

Implementation Patterns

Implement ContentTarget as a thin adapter. The package hands you a GeneratedImage DTO (bytes, mime, dimensions, sourceDriver, optional attribution, final prompt). Route those bytes to wherever covers live for you — Spatie, S3, local disk — and persist the DTO fields into custom_properties as an audit trail. This is also where you honour Unsplash's attribution requirement:

public function attachCover(Model $draft, GeneratedImage $image): void
{
    $draft->addMediaFromString($image->bytes)
        ->usingFileName("cover.{$image->extension()}")
        ->withCustomProperties([
            'driver'      => $image->sourceDriver,
            'prompt'      => $image->prompt,
            'attribution' => $image->attribution,
        ])
        ->toMediaCollection('cover');
}

Treat prompts as content, not code. Swap in a custom PromptBuilder for structural changes, but for day-to-day tuning use the seeded DB rows. cover_image is the big win: the DefaultPromptBuilder::IMAGE_TEMPLATE encodes scene-picking rules with five worked examples plus hard exclusions (no text, no logos, no close-up faces, no clichés), and the cover job runs title + excerpt through the LLM to produce a scene-specific prompt before hitting the driver. When covers look generic, edit the prompt row — don't touch the driver.

Extend content fields, not code. The 12 seeded fields are translatable and idempotent; adding a field is a DB row, not a migration. If you need per-channel variants beyond OG/Twitter, add rows and let the LLM populate them.

Use the backfill command as your recovery path. stringer:images:backfill --driver=unsplash --limit=50 re-drafts covers for any Drafted topic missing one. --sync is invaluable for debugging a driver failure interactively. This is also the right tool after switching drivers.

Telegram as an operations surface. The photo card (cover + host badge from config('app.name') + title + excerpt + stats + admin link) is the day-to-day view for editors. The critical change is that every error is now humanized and routed to chat — upstream "message" fields are extracted from Laravel HTTP exceptions. This means you should treat Telegram as the primary alerting channel, not email/Sentry, unless you specifically need stack traces.

Chain awareness. GenerateCoverImageJob fires automatically after a successful draft commit. Do not re-trigger it manually in normal flow; use the backfill command or the operator regenerate control (capped by STRINGER_IMAGE_MAX_REGENERATES).

Gotchas and Tips

  • Tailwind doesn't work inside vendor views. Package-shipped blade views can't rely on Tailwind utilities because the host's build doesn't scan vendor/. This release fixed the Settings Save button with inline styles — mimic that if you fork or override any views. Budget for it.
  • Prompt lookup is LIMIT 1. Creating a duplicate row is a footgun; a stale duplicate could silently win. StringerPromptResource is deliberately read-only-with-edit (canCreate() = false, duplicate action removed). If you need prompt versioning, do it outside the package.
  • slug is now LLM-generated per locale. EN is translated semantically; KA/RU are transliterated to Latin. Verify this before relying on slugs for URLs — an LLM-generated slug is not guaranteed unique, so check for collisions in the host.
  • Image driver quirks are real. DALL-E 3 snaps to its supported size set; Unsplash searches landscape orientation; Imagen and FLUX accept arbitrary sizes. Per-driver master-size + aspect-ratio mapping handles this, but cover crops will vary across drivers — don't switch drivers casually mid-run.
  • Telegram can't fetch localhost. sendPhoto now uses multipart upload from a local path rather than a URL. If you're routing covers through S3 and passing URLs, don't — pass bytes or a local path to the notification path.
  • JSON repair is a safety net, not a license. parseLlmJson now strips code fences, preamble/trailing prose, normalizes smart quotes, strips trailing commas, and escapes control chars — covering three real Gemini Flash malformation patterns under regression tests. Still log decode failures; a rising failure count means the model or prompt drifted.
  • Retry-guard fix is behaviorally significant. Previously isRecentParallelRetry would silently skip retries (the topic was already Drafting), meaning failed() never fired and operators were never notified. Now retries actually happen and failures actually surface. Expect more Telegram noise immediately post-upgrade — that's the fix working.
  • maxOutputTokens and temperature are pinned on Gemini (8192, 0.7) to stop silent truncation on long bodies. If you were overriding these, that override now lives in your fork, not config.
  • failed() is your only error hook. With the shared humanizer it emits host badge + ❌ Draft #N failed / ⚠️ Cover generation failed via {driver} + reason + exception class + admin link. Wire the admin link correctly (config) or the alert is half-useless.
  • STRINGER_IMAGE_ENABLED=false disables more than images — it also turns off photo-card notifications and the auto cover chain. If your team is wondering why Telegram went back to text-only, check this flag first.
  • Upgrade is safe. No new tables, idempotent seeders, and package never imports a media library. The main upgrade risk is prompt-row duplicates (see above) and operators needing a quick walkthrough of the new SEO fields in the Filament tab.
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.
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator