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

Gally Standard Laravel Package

gally/gally-standard

Gally Standard is the baseline Laravel package for Gally projects, bundling standard configuration, conventions, and core building blocks so teams can bootstrap a Gally-powered application quickly. It provides a clean, extensible foundation for custom modules and long-term maintenance.

View on GitHub
Deep Wiki
Context7

Getting Started

First, reset expectations: gally/gally-standard is not a Laravel package. It is a Symfony application / distribution metapackage that boots a full searchandising service (Elasticsearch/OpenSearch-backed catalog, search API, rule engine, admin UI). There is no service provider, no facade, no config/gally.php to publish. Your integration surface is HTTP — a GraphQL API and a set of CLI commands — plus, optionally, the Symfony extension points if you self-host a fork.

Minimal path to first value:

  1. Run it as a sidecar service, not as a dependency. Use the project's Docker Compose (Elasticsearch/OpenSearch + PHP app) or deploy it standalone. Do not try to composer require gally/gally-standard inside your Laravel composer.json — you'll drag Symfony runtime deps and kernel bootstrapping into a Laravel request lifecycle.
  2. Look here first, in this order:
    • config/ — the whole product lives in YAML here: sources, metadata, search config, rule definitions, scopes/stores.
    • The GraphQL endpoint (/graphql) — introspect the schema (__schema) before writing any query; query/mutation names shift between releases.
    • The admin UI (/admin) — the fastest way to understand what a "source," "metadata," "rule," and "scope" actually are before automating them.
  3. Feed it a catalog. Define a source (the connector describing where products come from and how fields map) and a metadata config (attribute types, searchable/filterable facets). Run the indexer CLI (bin/console gally:index..., plus the Elasticsearch index-creation command) to push documents in.
  4. First use case: storefront search. Your Laravel app proxies a search box to Gally's GraphQL endpoint, gets back product documents (id, sku, score, aggregation buckets), and hydrates full Eloquent models from your own database by SKU. Gally is the index and ranking brain; Laravel remains the source of truth for pricing, stock, and checkout.

Do not start by writing merchandiser rules. Start by getting a clean index and a search query returning sensible facets — rules are meaningless until the underlying documents and mappings are right.

Since v2.3.1, metadata definitions carry is_system and is_internal flags. Read those before modelling your attribute set: they distinguish platform-managed attributes from ones merchandisers are meant to touch, and they affect what shows up in the admin UI. Treat them as an editorial boundary, not just a UI toggle — decide per-attribute up front whether it is a merchandiser-facing facet or internal plumbing, because flipping the flag later is a metadata config change with reindex implications.

Implementation Patterns

Wrap the HTTP surface in one Laravel service. A single GallyClient built on Illuminate\Support\Facades\Http (with a base URI, bearer token, and a sane timeout) beats scattering Http::post('/graphql') calls around:

final class GallyClient
{
    public function __construct(private readonly array $cfg) {}

    public function query(string $gql, array $vars = []): array
    {
        $res = Http::baseUrl($this->cfg['url'])
            ->withToken($this->cfg['token'])
            ->timeout($this->cfg['timeout'] ?? 3)
            ->retry(2, 100)
            ->post('/graphql', ['query' => $gql, 'variables' => $vars]);

        // Gally returns 200 with an `errors` key for validation problems — check both.
        return $res->throw()->json();
    }
}

Treat GraphQL errors as first-class: a malformed filter usually comes back as a 200 with a populated errors array, so $response->successful() is not sufficient.

Hydration pattern (the one you'll use most). Gally returns search documents, not your models. Fetch SKUs from Gally, then batch-hydrate:

$hits = $gally->query($searchQuery, ['search' => $term])['data']['...']['items'];
$skus = array_column($hits, 'sku');
$products = Product::whereIn('sku', $skus)->get()->keyBy('sku');
// Re-order by the ranked SKU list — do not trust DB ordering.

Re-sort by Gally's returned order; that ordering is the merchandising value. Cache the Gally response (short TTL, keyed by normalized query + filters + scope) and cache the hydration separately so a rule change only invalidates the first layer.

Catalog ingestion as a scheduled job. Keep Gally in sync from Laravel rather than making your product tables the direct source:

  • Nightly (or on-change via queued job): push deltas to the configured source, then trigger the indexer.
  • Keep a gally_synced_at column and a dead-letter table for documents that failed mapping — mapping failures are silent-ish and you will need to replay them.
  • Full reindex on schema change; incremental on document change.
  • Always clear the mapping cache as part of any source-field/mapping change deploy. v2.3.1 fixed a bug where source-field edits did not invalidate Gally's mapping cache, so pre-2.3.1 deployments could index against a stale mapping and silently drop or mistype fields. If you're upgrading, re-verify your source field mappings and do a full reindex rather than trusting the existing index.

Metadata flags as a modelling tool. Use is_system for attributes Gally populates/handles itself and is_internal for attributes your integration needs but merchandisers shouldn't see (internal scoring signals, sync bookkeeping, cost/margin fields used in boosts). This keeps the admin UI legible as your attribute count grows, and reduces the chance a merchandiser edits a field your queries depend on. Audit existing metadata for attributes that should have been flagged internal — retrofitting is a config change plus reindex, not a live toggle.

Scopes for multi-store / multi-locale. Model each store view as a Gally scope. This is the cleanest way to get per-locale relevance, per-store rules, and per-store facet sets without duplicating queries in Laravel. Resolve the scope from your request context (locale + store) in middleware and inject it as a variable into every query.

Merchandising rules as config, reviewed like code. Rules (boost / pin / exclude / re-sort, gated by conditions) are YAML. Keep them in Git next to the app, deploy them via a build step, and test with the preview mode before publishing — otherwise "why is this product #1?" becomes an archaeology exercise.

Graceful degradation. Search should never take down the storefront. Wrap calls in a circuit breaker; on failure, fall back to a LIKE-based query or a cached result set. Log the Gally request ID/correlation if the API exposes one.

When you need custom logic, you're writing Symfony, not Laravel: decorators/resolvers registered in the Gally app's container. Budget for maintaining a fork; if you can express the need as a pre/post-processing step in Laravel, do that instead. Note that v2.3.1 moves the distribution to Symfony 7.4 — if you maintain a fork, rebase your decorators and adapt your DI config before pulling the tag, and re-check any code that relied on config value denormalization or generated OpenAPI docs.

Gotchas and Tips

  • It is a full application, not a library. No artisan commands, no Laravel config publishing, no queued-job integration, no Eloquent. Everything you "integrate" is HTTP + YAML + a separate container. If your team expects a drop-in package, this is the single biggest expectation gap.
  • Version-lock Elasticsearch/OpenSearch. Gally targets specific ES major versions; a mismatched ES in your infrastructure will fail at index creation, not at install. Pin the image.
  • Mapping changes require a reindex. Adding a searchable/filterable attribute is a mapping change — no in-place migration. Plan the alias/reindex window and expect a period where old and new indices disagree.
  • Mapping cache: fixed in 2.3.1, but don't rely on it silently. Source-field changes now invalidate the mapping cache. That also means an upgrade from ≤2.3.0 can surface mapping drift you previously "worked around" — indices built against a stale mapping may need rebuilding. Run the clear-cache/reindex sequence once after upgrading and compare document counts and field types before and after.
  • Symfony 7.4 bump is a fork-only concern (mostly). If you only speak HTTP to Gally, you get this for free. If you self-host a fork or a custom bundle, expect deprecated DI/config patterns to break, and re-validate that configuration values still denormalize as you expect — 2.3.1 ships fixes in exactly that area, which is a signal the upgrade had behavioural fallout upstream.
  • GraphQL schema drift between releases. Query/mutation/field names and filter input shapes change; the introspection schema is the only reliable contract. Regenerate your client-side query constants from it and pin the Gally version in your deploy manifest.
  • Rule precedence is the #1 debugging sink. Rules have an ordering/specificity notion; a "boost" that doesn't fire is usually shadowed by a broader rule or filtered out by a condition that evaluates against the wrong scope/locale. Use preview + the smallest possible rule to bisect.
  • Empty results are usually scope, locale, or metadata-flag mismatches, not relevance problems. Check is_internal/is_system on the attribute you're filtering by — an internal attribute may not be exposed where you assume. Verify the document exists in the index for that scope before touching scoring.
  • Caching layers stack badly. HTTP cache, Gally's own cache (including the mapping cache), and your app cache can each serve stale rankings or stale schemas after a config publish. Know the invalidation command (gally:cache:clear-style) and wire your deploy to run it.
  • Auth and exposure. API tokens are per-user; disable the GraphQL playground/introspection in production and put the Gally origin behind your network boundary, not on the public internet.
  • Extension points are Symfony-shaped: DI decorators, custom resolvers, custom normalization. Fine if you have Symfony depth; a real cost if you don't. Prefer solving problems in the ingestion/query layer.
  • Maturity signal: low stars and zero dependents means thin community answers — you'll be reading source and config rather than finding blog posts. Pin versions, keep the Docker Compose reproducible, and write down your own runbook for reindex + rule publish + cache clear. Patch releases (2.3.0 → 2.3.1) still carry features and behaviour fixes, so read the notes and re-run your runbook on every bump, not just minor versions.
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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource