From 2dbab1b1d87a7f56b80701fafc1e16ffde3bac96 Mon Sep 17 00:00:00 2001 From: Artem Kriukov Date: Sun, 15 Mar 2026 01:16:29 +0200 Subject: [PATCH] Add support for View::fragment*() methods Currently, when calling the fragment() method we still render the entire template, even though Twig supports rendering individual fragments using renderBlock()[1]. This commit fixes this behaviour, but in a somewhat hacky way: instead of doing the same thing as Laravel does (mapping fragments during view parsing -> rendering the entire view -> selecting only the necessary fragment from the rendered view)[2][3][4], we override View::fragment() during Twig template rendering and use renderBlock() directly. This is more of a Proof of Concept, since I'm not sure if this is the best way to solve this problem, but at least it works. [1] https://twig.symfony.com/doc/3.x/api.html#rendering-templates [2] https://github.com/laravel/framework/tree/v12.54.1/src/Illuminate/View/Compilers/Concerns/CompilesFragments.php [3] https://github.com/laravel/framework/tree/v12.54.1/src/Illuminate/View/Concerns/ManagesFragments.php [4] https://github.com/laravel/framework/tree/v12.54.1/src/Illuminate/View/View.php#L87 --- src/ServiceProvider.php | 15 +++++++++++++++ src/View/Factory.php | 19 +++++++++++++++++++ src/View/View.php | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+) create mode 100644 src/View/Factory.php create mode 100644 src/View/View.php diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index 284072e..be87087 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -11,6 +11,7 @@ namespace TwigBridge; +use Illuminate\View\Factory as LaravelFactory; use Illuminate\View\ViewServiceProvider; use InvalidArgumentException; use Twig\Environment; @@ -22,6 +23,7 @@ use Twig\Loader\ArrayLoader; use Twig\Loader\ChainLoader; use Twig\RuntimeLoader\ContainerRuntimeLoader; +use TwigBridge\View\Factory; /** * Bootstrap Laravel TwigBridge. @@ -284,6 +286,19 @@ function () { $this->app['config']->get('twigbridge.twig.globals', []) ); }); + + $this->app->extend('view', function (LaravelFactory $view, $app) { + $factory = new Factory($view->getEngineResolver(), $view->getFinder(), $view->getDispatcher()); + + // We will also set the container instance on this view environment since the + // view composers may be classes registered in the container, which allows + // for great testable, flexible composers for the application developer. + $factory->setContainer($app); + + $factory->share('app', $app); + + return $factory; + }); } /** diff --git a/src/View/Factory.php b/src/View/Factory.php new file mode 100644 index 0000000..5b19be2 --- /dev/null +++ b/src/View/Factory.php @@ -0,0 +1,19 @@ +getEngineFromPath($path); + if ($engine instanceof Twig) { + return new View($this, $engine, $view, $path, $data); + } + + return parent::viewInstance($view, $path, $data); + } +} diff --git a/src/View/View.php b/src/View/View.php new file mode 100644 index 0000000..304e61e --- /dev/null +++ b/src/View/View.php @@ -0,0 +1,33 @@ +factory->incrementRender(); + $this->factory->callComposer($this); + + /** @var \TwigBridge\Engine\Compiler */ + $compiler = $this->engine->getCompiler(); + $contents = $compiler->load($this->path)->renderBlock($fragment, $this->gatherData()); + + $this->factory->decrementRender(); + $this->factory->flushStateIfDoneRendering(); + + return $contents; + } catch (Throwable $e) { + $this->factory->flushState(); + + throw $e; + } + } +}