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

Client Helper Bundle Laravel Package

elasticms/client-helper-bundle

A Symfony bundle from the elasticMS ecosystem that provides client helper utilities to simplify integrating elasticMS services into your PHP applications. Documentation and issue tracking live in the elasticMS mono repository.

View on GitHub
Deep Wiki
Context7

Getting Started

What you are actually installing

elasticms/client-helper-bundle is a Symfony bundle, not a Laravel package. Before any code is written, make the go/no-go call explicit:

Precondition Why it matters
You have a reachable elasticMS backend The bundle is a client — its services wrap elasticMS APIs. With no backend, it does nothing.
You are (or are willing to become) a Symfony app It registers via config/bundles.php and AbstractBundle/Extension classes. Laravel has neither.
Your Symfony and PHP versions match the bundle's major line This repo is a read-only mirror; branches ship in lockstep with the elasticMS monorepo (5.x / 6.x / 7.x).

If the answer to the first two is "no", stop here. A Laravel-only project gets more value from a 200-line HTTP client than from this dependency — see Implementation Patterns → Pattern B.

Install and verify compatibility

composer why-not elasticms/client-helper-bundle   # surfaces Symfony/PSR conflicts first
composer require elasticms/client-helper-bundle
// config/bundles.php  (Symfony only — this file does not exist in Laravel)
return [
    // ...
    EMS\ClientHelperBundle\EMSClientHelperBundle::class => ['all' => true],
];

Then confirm the autoload and container actually picked it up before touching config:

bin/console debug:config ems_client_helper     # the real config tree, straight from Configuration.php
bin/console debug:container --tag=twig.extension | grep -i ems

Where to look first

The README is a stub — three links and a pointer at the monorepo. Do not try to learn this bundle from GitHub. Read, in this order:

  1. vendor/elasticms/client-helper-bundle/src/DependencyInjection/Configuration.php — the config tree is the de facto API documentation. Every knob the bundle exposes lives here, with types and defaults.
  2. vendor/elasticms/client-helper-bundle/src/Resources/config/*.yaml — the service definitions. This tells you what to inject and what to decorate.
  3. The Twig directory — the extensions are the day-to-day surface. Enumerate them rather than guessing names:
    bin/console debug:twig --filter=ems
    
  4. The upstream docs at ems-project.github.io/#/dev/client-helper-bundle/index.md — thin, but it names the concepts the rest of the code assumes.

Minimal working configuration

The bundle needs to know where elasticMS is and who it is. You will be setting, at minimum: backend base URL(s), authentication (API key / Basic), default locale, and the asset base URL used when rendering content. Discover the exact keys with debug:config rather than copying YAML from a blog post — the keys move between major versions.

Put those in a dedicated ems.yaml and drive every value from env vars:

# config/packages/ems_client_helper.yaml
ems_client_helper:
    # keys taken from `bin/console debug:config ems_client_helper`
    backend: '%env(EMS_BACKEND_URL)%'
    # ...

First use case

Pick the smallest end-to-end slice that proves the wiring: render one page whose content lives in elasticMS. In a Symfony app that is a controller → service → Twig template path where the Twig extension resolves the content type and its assets. Ship that one page before building anything else — if assets 404 or the page comes back blank, you are looking at a config/auth problem, and you want that failure isolated on day one.

If you are staying in Laravel

You cannot vendor:publish this. There is no service provider, no facade, no Blade directives. The realistic first step is a spike with a timebox (2–3 days): stand up a throwaway Symfony app, get one page rendering against your elasticMS, and then decide whether the services are worth reimplementing in Laravel or whether you only wanted the HTTP contract — which you can read straight off the requests the Symfony app makes.

Implementation Patterns

Pattern A — Symfony-native (the intended path)

Services are autowired. Inject the client helper into your controller rather than reaching for $container->get():

final class PageController extends AbstractController
{
    public function __construct(
        private readonly ClientHelper $clientHelper,   // exact FQCN: see src/Resources/config
    ) {}

    #[Route('/{slug}', requirements: ['slug' => '.+'])]
    public function __invoke(string $slug): Response
    {
        $document = $this->clientHelper->getDocument($slug); // illustrative name — verify in src/
        if (null === $document) {
            throw $this->createNotFoundException();
        }

        return $this->render('page/show.html.twig', ['document' => $document]);
    }
}

The payoff is in the templates: the Twig extensions know how to turn a content type document into URLs, asset paths and localized variants, which is the boilerplate you would otherwise write per project.

Pattern B — Laravel consuming elasticMS directly

Two sub-patterns, one recommendation.

B1 (recommended): thin HTTP client in Laravel. Read the request/response shapes off the Symfony spike, then wrap them:

final class EmsClient
{
    public function __construct(private readonly PendingRequest $http) {}

    public function document(string $slug, string $locale): ?array
    {
        return Cache::remember(
            "ems:{$locale}:{$slug}",
            now()->addMinutes(10),
            fn () => $this->http
                ->withToken(config('services.ems.token'))
                ->baseUrl(config('services.ems.base_url'))
                ->get("/api/content/{$locale}/{$slug}")
                ->throw()
                ->json(),
        );
    }
}

Bound as a singleton in a EmsServiceProvider, injected into controllers and Blade view composers. You lose the Twig extensions and gain ordinary Laravel debugging.

B2 (avoid): boot a Symfony kernel inside Laravel. Technically possible via symfony/http-kernel + bundle registration shim. It couples you to two DI containers, two config systems and two cache layers, for a helper library. Every future composer update becomes a negotiation. Don't.

Pattern C — translating the Twig surface to Blade

If you are porting templates, do it deliberately rather than mechanically. Build one Blade component per elasticMS concept you actually use:

{{-- resources/views/components/ems/asset.blade.php --}}
@props(['path'])
<img src="{{ app(\App\Services\EmsClient::class)->assetUrl($path) }}" {{ $attributes }} />

Keep the URL-building logic in one PHP class. The most common failure in a port is asset URL logic reimplemented in three templates with three different cache-busting rules.

Pattern D — draft and preview flow

Preview is where CMS clients earn their keep and where they leak content. The pattern to implement regardless of stack:

  • Preview is opt-in via a signed token, not a bare ?preview=1.
  • The token is validated against the editor's authenticated session/permission.
  • Draft responses are Cache-Control: private, no-store — never through your shared content cache.
  • A single middleware/event subscriber handles this, so there is exactly one code path where drafts can escape.
// Laravel
public function handle(Request $request, Closure $next): Response
{
    $response = $next($request);

    if ($request->attributes->get('ems.preview')) {
        $response->headers->set('Cache-Control', 'private, no-store');
    }

    return $response;
}

Pattern E — cache and invalidation

elasticMS publishes asynchronously; your cache TTL is your worst-case staleness. Prefer tagged caching + webhook invalidation over short TTLs:

Cache::tags(['ems', "ems:{$contentType}"])->remember(/* ... */);

Wire the elasticMS publish webhook to a Laravel route that calls Cache::tags(["ems:{$contentType}"])->flush(). Guard it with a shared secret.

Pattern F — local dev and tests

Never let CI or a test suite talk to a shared elasticMS instance.

  • Cache real responses as JSON fixtures once, in the Symfony spike, and replay them (Http::fake() in Laravel, MockHttpClient in Symfony).
  • Assert on the rendered output, not the client call — that catches template/asset regressions the client tests miss.
  • Keep one manual smoke test against a real backend, run on release, not on every PR.

Gotchas and Tips

It is not a Laravel package, and nothing will make it one. No bundles.php, no vendor:publish, no facade, no Blade. If a colleague says "just register it in config/app.php", that advice is wrong and will cost a day before it is abandoned.

This repository is READ-ONLY. Per the README, issues and pull requests go to the ems-project/elasticms monorepo. If you find a bug and open a PR here, nobody merges it. Fork the monorepo, and expect release versions to be synced across all the split repos — that means the changelog here is nearly empty ("Full Changelog" links only), so read the monorepo history to know what changed.

LGPL-3.0 is a real constraint on your extension strategy. Linking and using it is fine. Patching it in vendor/ is not a stable plan. If you need behaviour changes, either decorate the service in your own bundle:

# config/services.yaml
App\Ems\DecoratingClientHelper:
    decorates: 'EMS\ClientHelperBundle\...\ClientHelper'
    arguments: ['@.inner']

…or maintain a fork under a different package name with a documented diff. Do not do both.

A blank page is almost never a template bug. The failure mode of an HTTP-backed CMS client is silent emptiness: an expired token, a rotated API key, a renamed index alias, or a 403 all surface as "empty content" rather than an exception. Before debugging Twig, dump the raw transport response (HttpClientInterface in Symfony, Http::dd() in Laravel) and add a /health/ems route that does one authenticated read and checks the content-type inventory. You want this on day one, not after the first incident.

Version lines matter more than the version number. 5.x → 6.x → 7.x track Symfony and PHP support, and there are frequent patch releases (6.9.x alone has dozens). Pin to a minor and read the monorepo's upgrade notes between majors. Check the branch against your Symfony LTS before assuming the latest release is right for you.

The sparse README is a signal about support, not a temporary state. Three stars, zero dependents, a stub README and a read-only mirror. Budget accordingly: assume no community answers, no Stack Overflow coverage, and a maintainer team whose priority is their own products. Your team owns the upgrade path.

Discover the Twig function names, don't recall them. Names, signatures and even prefixes shift between majors. debug:twig --filter=<prefix> and debug:container --tag=twig.extension are faster and correct; anything you remember from a previous project is probably one rename out of date.

Preview cookies outlive the preview session. The stale-preview bug is the classic CMS-client leak: an editor previews a page, the cookie/token lingers, and later an anonymous request is served unreleased content from an edge or page cache. Treat preview state as a security boundary, not a flag.

Watch the Symfony component overlaps. The bundle pulls in symfony/http-client, symfony/cache, and possibly serializers. If your Laravel app already binds its own HTTP client or cache contracts, resolve the collision by explicit service binding rather than hoping the auto-wiring picks the one you meant:

$this->app->bind('ems.http_client', fn ($app) => $app['ems.http_client.raw']);

Know when to walk away. The honest cost/benefit: adopting this gives you elasticMS-specific content rendering for free if you are already a Symfony + elasticMS shop. Otherwise you are importing a DI container, a config tree and a Twig extension set to get an HTTP contract you could reimplement in a week and debug with tools you already know. Timebox the spike, write down the three features you actually need, and let that list — not the bundle's feature set — make the decision.

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.
andydefer/laravel-notification
adachsoft/cli-process-tester
jhaskell13/laraflow
sebastienheyd/boilerplate-media-manager
ccast/tagixo-primix
pretendtrue/laravel-builder
sawmainek/laravel-scout-tntsearch-driver
tomshaw/laravel-calendar-table
dibakar/laravel-ownership
steadrun/laravel-monitor
labyrinthe/connect-laravel
elmogy/layan
drewdan/sentinel-client
dskripchenko/laravel-llm
fatryst/nuonuo
stevenay/myanfont
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc