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

Php Vars To Js Bundle Laravel Package

hospect/php-vars-to-js-bundle

A Symfony bundle that passes PHP variables to JavaScript, letting you expose server-side data to frontend scripts in a simple, centralized way. It reduces boilerplate by keeping configuration and dynamic values in sync between PHP and JS.

View on GitHub
Deep Wiki
Context7

Getting Started

This bundle is a thin Symfony2 wrapper around laracasts/PHP-Vars-To-Js-Transformer. If you have used Laravel's Js::put() or the js() helper, the mental model is identical: you accumulate PHP values into a namespaced bag during the request, then serialize the entire bag into a single <script> block in your layout. The bundle's only real contribution is the service definition, DI config tree, and Twig plumbing. Learn the upstream API and you know 90% of this.

Minimal path to first use:

  1. composer require hospect/php-vars-to-js-bundle — verify the Symfony constraint against your lockfile first (this was written for Symfony 2.x; see Gotchas).
  2. Register the bundle in app/AppKernel.php: new Hospect\PhpVarsToJsBundle\HospectPhpVarsToJsBundle().
  3. Add a hospect_php_vars_to_js config block (namespace, JSON options). The keys are sparse — read DependencyInjection/Configuration.php in the bundle rather than guessing.
  4. Confirm the service id: bin/console debug:container php_vars (or grep Resources/config/services.* for the transformer alias). Pull it into a controller or extend a base controller with a js() helper of your own.

Where to look first, in order:

  • DependencyInjection/ and Resources/config/services.* — this bundle is small; the service alias and the Twig function name are literally the two things you need.
  • The upstream laracasts/PHP-Vars-To-Js-Transformer README — the put, putAll, forget, all API and namespace semantics live there and the bundle does not re-document them.
  • Your base layout — that's where the one output call goes.

First use case — hydrate a JS entry point with bootstrap data from a controller:

public function dashboardAction()
{
    $this->get('hospect_php_vars_to_js.transformer')
        ->put('user', [
            'id'    => $this->getUser()->getId(),
            'roles' => $this->getUser()->getRoles(),
        ])
        ->put('config', ['locale' => $request->getLocale()]);

    return $this->render('dashboard.html.twig');
}

Then in your base layout, exactly once:

<script>
  window.App = {{ php_vars_to_js() }};
</script>

Your frontend then reads window.App.user.id without an extra /api/me call.

Implementation Patterns

Collect in a kernel request listener, emit in layout. The cleanest integration is not controller-side put() at every action — that scatters coupling. Instead, register a kernel.controller or kernel.request listener that runs a set of small StateProvider classes (tags in your DI config), each returning a fragment (user, flash, flags, i18n). The layout template emits once. This gives you a single point of control, testable providers, and an easy audit for "what leaks to the client."

Extend a base controller. If your app already has an AbstractController in Symfony2 style, add protected function js() { return $this->get('hospect_php_vars_to_js.transformer'); }. Every action then reads $this->js()->put('key', $value) — same ergonomics as the Laravel facade, and when you eventually replace the package you only rewrite one method.

Compose providers per concern. UserStateProvider (id, roles, permissions), ConfigStateProvider (locale, feature flags, endpoints), FlashStateProvider (queued messages), PaginationStateProvider (cursors). Keep each under ~10 keys and under 2KB total. This is exactly the policy the product doc calls "initial-state bootstrapping" — it's the pattern worth keeping even if you drop the bundle.

Twig-first for read-only, controller-first for request-scoped. Configuration and flags that are constant per-deployment belong in the config tree (hospect_php_vars_to_js bundle config) so they land in the bag automatically. Anything $request-dependent belongs in a provider or a controller call. Mixing the two in one template is how you get stale values bleeding through full-page caches.

Integration with a JS framework boot. Serialize once into window.__INITIAL_STATE__, then hand it to your client router/reducer at mount:

const store = createStore(reducer, window.__INITIAL_STATE__);

This is the one pattern this bundle actually justifies — it replaces 3–5 XHR on first paint with a single inline script. If your client framework re-fetches anyway on navigation, the bundle adds nothing beyond the first load; don't over-invest.

Wrap it behind an interface from day one. Define App\View\JsViewData with put(string $key, mixed $value): void and render(): array. Bind the bundle's transformer behind it. This is ~20 lines and makes the eventual replacement (in-house Twig function, Inertia shared props, whatever) a one-line service rebinding instead of a grep-and-replace across every controller. Given the package's maintenance posture (0 dependents, 9 stars, single maintainer), treat this wrapper as mandatory, not nice-to-have.

Testing pattern. In a WebTestCase, assert against the parsed HTML rather than a string match: (new Crawler($client->getResponse()->getContent()))->filter('script')->text() and json_decode it. This catches both serialization regressions and escaping regressions in one test, and — critically — lets you assert that <, >, &, ', " in a user-controlled field are hex-escaped. That single test is the entire security value of this package; write it.

Gotchas and Tips

Symfony 2.x is a hard constraint. This was written against the legacy *Bundle convention with Resources/config/services.yml and manual AppKernel registration. It does not map onto Symfony Flex, autoconfiguration, or Symfony 5/6/7. Before you composer require, read the bundle's composer.json constraints and diff them against your lockfile. If you're not on 2.x, stop — do not retrofit; write the Twig function yourself (~30 lines with JSON_HEX_TAG|JSON_HEX_AMP|JSON_HEX_APOS|JSON_HEX_QUOT|JSON_THROW_ON_ERROR and a window.__STATE__ target), covered by one escaping test.

CSP will bite you. The bundle emits inline <script> blocks. Under a strict Content-Security-Policy (no unsafe-inline), every page breaks. Mitigations: append a per-request nonce to the script tag (the bundle has no built-in nonce support — you'll tag it in your layout override), or switch to a <script type="application/json" id="app-state">…</script> data island and parse it in JS. The data-island approach is CSP-safe with no nonce and is what I'd choose if you keep this pattern.

Escaping is the entire security argument — verify it, don't assume it. The upstream transformer JSON-encodes with the HEX_* flags precisely to prevent </script>-breakout stored XSS. Confirm the flags survive the bundle's config: check your hospect_php_vars_to_js options and, if in doubt, grep the transformer call. Hand-rolled {{ data|json_encode|raw }} in a template is not equivalent — that's the bug class this bundle actually fixes, so don't reintroduce it by bypassing the bundle.

Non-scalar serialization is on you. Entities, closures, resources, and Doctrine proxies will not serialize cleanly. json_encode returns false on resources and closures; without JSON_THROW_ON_ERROR (which this era of codebase likely lacks) you'll get a silent false in the output. Always hand the transformer plain arrays/scalars, or implement JsonSerializable on your DTOs. If you pass an entity, expect either a fatal or a giant object graph — pass ['id' => $entity->getId(), 'label' => $entity->getName()] instead.

Never leak one user's state to another via cache. Everything injected is baked into the HTML response. If a page is behind full-page or reverse-proxy cache and the state includes user-specific values, users see each other's data — a real, recurring incident. Rule: user-specific state ⇒ Cache-Control: private (or Vary on the session cookie); shared state only for anything that goes through a CDN. Audit any put() call that reads from the session, the request user, or CSRF tokens.

Payload size, not CPU, is the scaling concern. The transformer is trivially fast. The risk is inflating every HTML response with 50KB of JSON that the browser then blocks on. Keep the bag small (IDs, flags, i18n strings, cursors). Bulk data (lists, tables) belongs in a cacheable JSON endpoint, not inlined in the page. If your layout's window.App block grows past a few KB, that's the signal to split.

Stale values on cached pages and soft navigations. Because injection happens at render time, a cached or CDN-served HTML document will carry stale state. For SPAs with client-side navigation, only the first document's state is fresh; subsequent routes must fetch. Design for this explicitly rather than discovering it as a "why is the user id wrong after logout" bug.

Silent no-op if DI fails to register. If the bundle extension doesn't load (wrong kernel registration, config typo, a compiler-pass ordering issue), the Twig function may render an empty string and your frontend reads undefined — no exception, no log. Add a smoke test that asserts window.App is present and non-empty on a page where you expect state. This one test saves hours of debugging a blank frontend.

Debugging tips. bin/console debug:container php_vars to find the real service id. bin/console debug:twig to confirm the function name registered. bin/console debug:config hospect_php_vars_to_js to see what your config tree actually resolved to — the bundle's defaults are not obvious. If the bag is empty in output, log $transformer->all() right before render.

Extension points to prefer. The transformer is a plain service; decorate it rather than fork it. A decorator can enforce a payload-size limit, strip disallowed keys, snapshot values for tests, or add a __source: 'php' marker. That decorator survives the bundle's replacement. Avoid editing vendor code — you will forget and lose the change on the next composer update.

Deprecation triage. The honest bottom line: this is pattern-valid, package-rejected for any new work. Use it as a reference implementation — read the transformer, understand the escaping flags and namespace design — then own the ~50 lines as an in-house Twig function with tests and a lint rule banning raw json_encode in templates. You keep the security win, drop the dead-framework dependency, and delete it in an afternoon when your client stack moves to Inertia or a typed API client.

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.
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox