netgen/ngsymfonytools
A Symfony bundle from Netgen that bundles practical utilities for eZ Publish and eZ Platform projects, including a template-driven mailer, a configurable query type for content queries, and other reusable helpers that simplify everyday development.
This is an eZ Publish 4 (legacy) extension, not a modern Composer library. You install it by dropping it into extension/ and activating it — there is no composer require path that will do anything useful on its own.
Prerequisites
.tpl templates and fetch()).HttpKernel wired into the same process (this extension was built for Netgen's eZ Publish 4 + Symfony 2 integration; it is not a standalone bridge to Symfony 3/4/5).Minimal install
git clone https://github.com/netgen/ngsymfonytools extension/ngsymfonytools
Activate it in settings/override/site.ini.append.php:
[ExtensionSettings]
ActiveExtensions[]=ngsymfonytools
Then regenerate autoloads and clear caches (both sides matter — legacy and Symfony):
php bin/php/ezpgenerateautoloads.php
php bin/php/ezcache.php --clear-all --purge
Where to look first
settings/ngsymfonytools.ini (and any .append.php override) — this is where template paths, Twig environment options, and sub-request settings live. Read the shipped INI before writing any override.classes/ and autoloads/ — the template operators are registered here; confirm the exact operator names against the source rather than trusting blog posts (historically these are ngsymfonytools_render_twig and ngsymfonytools_subrequest).ezinfo.php / README — version compatibility notes.First use case
The intended first use is "replace one chunk of legacy markup with a Twig partial." Put a Twig template in the path configured in ngsymfonytools.ini, then call the render operator from a legacy template:
{def $data = hash(
'title', $node.name|wash(),
'items', fetch('content', 'list', hash('parent_node_id', $node.node_id))
)}
{ngsymfonytools_render_twig('AcmeSiteBundle:Parts:teaser.html.twig', $data)}
<section class="teaser">
<h2>{{ title }}</h2>
<ul>
{% for item in items %}
<li>{{ item.name }}</li>
{% endfor %}
</ul>
</section>
If that renders, the environment is wired correctly and you can start scaling the pattern.
Treat it as a migration seam, not a replacement engine
The strongest day-to-day pattern is incremental: keep the legacy pagelayout and fetch() logic, and carve out leaf components (teasers, widgets, forms, search results) as Twig templates or Symfony sub-requests. Page assembly stays in eZ; presentation moves to Twig one component at a time. This is also the lowest-risk way to introduce it into an existing site.
Explicit data handoff is the contract
Nothing is shared implicitly. Build a hash() in the legacy template, hand it to the operator, and let Twig consume named keys. Keep the shape flat and serializable — no passing content objects whose methods you then call from Twig with assumptions about the legacy attribute API. If you need rich objects, convert them in PHP (a small helper/extension function) rather than in the template.
Sub-requests for controller-backed components
Use the sub-request operator when the component already has real logic behind it (a Symfony controller, a service, form handling) and you do not want to duplicate that in legacy fetch() calls:
{ngsymfonytools_subrequest('acme_search', hash('q', $search_string))}
Keep sub-requests at the "one per page region" level. Each one is a full kernel round-trip through the Symfony stack, so putting them inside a foreach over a content list is a performance trap.
Configuration over hardcoding
Drive template locations and any Twig option flags through ngsymfonytools.ini.append.php in settings/override/ or per siteaccess, not by editing the shipped INI. That keeps the extension directory updatable and lets staging/production diverge only in configuration.
Debugging workflow
When an operator call silently renders nothing:
ActiveExtensions for the current siteaccess — eZ merges site.ini across overrides.classes/;var/log/error.log (legacy) and the Symfony log — exceptions from Twig or the kernel surface there, not in the browser.Extension points
The realistic extension points are the Symfony side: register Twig extensions/functions through your bundle's service configuration, and add helper operators by following the pattern in the extension's classes/. Anything you add there should be treated as a fork you maintain, given the package's age.
.tpl compilation and Twig compilation are independent. A change to a Twig template that "doesn't show up" is usually the legacy view cache holding the rendered fragment; a broken Twig template may keep serving the last good compiled version until the Twig cache is cleared.autoloads/ in the checked-out version before writing templates; operator registration is the most common reason a copied example throws "unknown operator."|wash() on values you pass in where they originate from user or content data, and let Twig handle output escaping.How can I help you explore Laravel packages today?