Skip to content

Commit 50d0c2a

Browse files
timribarryvdh
authored andcommitted
Feature/twig profiler (php-debugbar#362)
* Use Twig_Extension_Profiler as data-source * Add documentation for TwigProfileCollector * Add DebugBar\Bridge\Twig\TimeableTwigExtensionProfiler to measure rendering * Fix & enable xdebug-link-generation in ExceptionsCollector * Enable xdebug-link-generation to TwigProfileCollector (An instance of the Twig_Environment or the Twig_Loader has to be passed to the collector, to be able to resolve the templatename to the filename) Add "ajax"-attribute to xdebug-links (IDEA / PhpStorm-Links should be opened by ajax)
1 parent 42b6df1 commit 50d0c2a

File tree

8 files changed

+404
-8
lines changed

8 files changed

+404
-8
lines changed

docs/bridge_collectors.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,26 @@ You need to wrap your `Twig_Environment` object into a `DebugBar\Bridge\Twig\Tra
9999

100100
You can provide a `DebugBar\DataCollector\TimeDataCollector` as the second argument of
101101
`TraceableTwigEnvironment` so render operation can be measured.
102+
103+
## Twig (Profiler)
104+
105+
An alternative to `DebugBar\Bridge\Twig\TwigCollector` for newer versions of twig.
106+
This collector uses the class `Twig_Extension_Profiler` to collect info about rendered
107+
templates, blocks and macros.
108+
You need to inject the root-`Twig_Profiler_Profile` into the collector:
109+
110+
$loader = new Twig_Loader_Filesystem('.');
111+
$env = new Twig_Environment($loader);
112+
$profile = new Twig_Profiler_Profile();
113+
$env->addExtension(new Twig_Extension_Profiler($profile));
114+
$debugbar->addCollector(new DebugBar\Bridge\TwigProfileCollector($profile));
115+
116+
You can optionally use `DebugBar\Bridge\Twig\TimeableTwigExtensionProfiler` in place of
117+
`Twig_Extension_Profiler` so render operation can be measured.
118+
119+
$loader = new Twig_Loader_Filesystem('.');
120+
$env = new Twig_Environment($loader);
121+
$profile = new Twig_Profiler_Profile();
122+
$env->addExtension(new TimeableTwigExtensionProfiler($profile, $debugbar['time']));
123+
$debugbar->addCollector(new DebugBar\Bridge\TwigProfileCollector($profile));
124+
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
/*
3+
* This file is part of the DebugBar package.
4+
*
5+
* (c) 2017 Tim Riemenschneider
6+
*
7+
* For the full copyright and license information, please view the LICENSE
8+
* file that was distributed with this source code.
9+
*/
10+
11+
namespace DebugBar\Bridge\Twig;
12+
13+
use DebugBar\DataCollector\TimeDataCollector;
14+
use Twig_Profiler_Profile;
15+
16+
/**
17+
* Class TimeableTwigExtensionProfiler
18+
*
19+
* Extends Twig_Extension_Profiler to add rendering times to the TimeDataCollector
20+
*
21+
* @package DebugBar\Bridge\Twig
22+
*/
23+
class TimeableTwigExtensionProfiler extends \Twig_Extension_Profiler
24+
{
25+
/**
26+
* @var \DebugBar\DataCollector\TimeDataCollector
27+
*/
28+
private $timeDataCollector;
29+
30+
/**
31+
* @param \DebugBar\DataCollector\TimeDataCollector $timeDataCollector
32+
*/
33+
public function setTimeDataCollector(TimeDataCollector $timeDataCollector)
34+
{
35+
$this->timeDataCollector = $timeDataCollector;
36+
}
37+
38+
public function __construct(\Twig_Profiler_Profile $profile, TimeDataCollector $timeDataCollector = null)
39+
{
40+
parent::__construct($profile);
41+
42+
$this->timeDataCollector = $timeDataCollector;
43+
}
44+
45+
public function enter(Twig_Profiler_Profile $profile)
46+
{
47+
if ($this->timeDataCollector && $profile->isTemplate()) {
48+
$this->timeDataCollector->startMeasure($profile->getName(), 'template ' . $profile->getName());
49+
}
50+
parent::enter($profile);
51+
}
52+
53+
public function leave(Twig_Profiler_Profile $profile)
54+
{
55+
parent::leave($profile);
56+
if ($this->timeDataCollector && $profile->isTemplate()) {
57+
$this->timeDataCollector->stopMeasure($profile->getName());
58+
}
59+
}
60+
}
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
<?php
2+
/*
3+
* This file is part of the DebugBar package.
4+
*
5+
* (c) 2017 Tim Riemenschneider
6+
*
7+
* For the full copyright and license information, please view the LICENSE
8+
* file that was distributed with this source code.
9+
*/
10+
11+
namespace DebugBar\Bridge;
12+
13+
use DebugBar\DataCollector\AssetProvider;
14+
use DebugBar\DataCollector\DataCollector;
15+
use DebugBar\DataCollector\Renderable;
16+
17+
/**
18+
* Collects data about rendered templates
19+
*
20+
* http://twig.sensiolabs.org/
21+
*
22+
* A Twig_Extension_Profiler should be added to your Twig_Environment
23+
* The root-Twig_Profiler_Profile-object should then be injected into this collector
24+
*
25+
* you can optionally provide the Twig_Environment or the Twig_Loader to also create
26+
* debug-links.
27+
*
28+
* @see \Twig_Extension_Profiler, \Twig_Profiler_Profile
29+
*
30+
* <code>
31+
* $env = new Twig_Environment($loader); // Or from a PSR11-container
32+
* $profile = new Twig_Profiler_Profile();
33+
* $env->addExtension(new Twig_Extension_Profile($profile));
34+
* $debugbar->addCollector(new TwigProfileCollector($profile, $env));
35+
* // or: $debugbar->addCollector(new TwigProfileCollector($profile, $loader));
36+
* </code>
37+
*/
38+
class TwigProfileCollector extends DataCollector implements Renderable, AssetProvider
39+
{
40+
/**
41+
* @var \Twig_Profiler_Profile
42+
*/
43+
private $profile;
44+
/**
45+
* @var \Twig_LoaderInterface
46+
*/
47+
private $loader;
48+
/** @var int */
49+
private $templateCount;
50+
/** @var int */
51+
private $blockCount;
52+
/** @var int */
53+
private $macroCount;
54+
/**
55+
* @var array[] {
56+
* @var string $name
57+
* @var int $render_time
58+
* @var string $render_time_str
59+
* @var string $memory_str
60+
* @var string $xdebug_link
61+
* }
62+
*/
63+
private $templates;
64+
65+
/**
66+
* TwigProfileCollector constructor.
67+
*
68+
* @param \Twig_Profiler_Profile $profile
69+
* @param \Twig_LoaderInterface|\Twig_Environment $loaderOrEnv
70+
*/
71+
public function __construct(\Twig_Profiler_Profile $profile, $loaderOrEnv = null)
72+
{
73+
$this->profile = $profile;
74+
if ($loaderOrEnv instanceof \Twig_Environment) {
75+
$loaderOrEnv = $loaderOrEnv->getLoader();
76+
}
77+
$this->loader = $loaderOrEnv;
78+
}
79+
80+
/**
81+
* Returns a hash where keys are control names and their values
82+
* an array of options as defined in {@see DebugBar\JavascriptRenderer::addControl()}
83+
*
84+
* @return array
85+
*/
86+
public function getWidgets()
87+
{
88+
return array(
89+
'twig' => array(
90+
'icon' => 'leaf',
91+
'widget' => 'PhpDebugBar.Widgets.TemplatesWidget',
92+
'map' => 'twig',
93+
'default' => json_encode(array('templates' => array())),
94+
),
95+
'twig:badge' => array(
96+
'map' => 'twig.badge',
97+
'default' => 0,
98+
),
99+
);
100+
}
101+
102+
/**
103+
* @return array
104+
*/
105+
public function getAssets()
106+
{
107+
return array(
108+
'css' => 'widgets/templates/widget.css',
109+
'js' => 'widgets/templates/widget.js',
110+
);
111+
}
112+
113+
/**
114+
* Called by the DebugBar when data needs to be collected
115+
*
116+
* @return array Collected data
117+
*/
118+
public function collect()
119+
{
120+
$this->templateCount = $this->blockCount = $this->macroCount = 0;
121+
$this->templates = array();
122+
$this->computeData($this->profile);
123+
124+
return array(
125+
'nb_templates' => $this->templateCount,
126+
'nb_blocks' => $this->blockCount,
127+
'nb_macros' => $this->macroCount,
128+
'templates' => $this->templates,
129+
'accumulated_render_time' => $this->profile->getDuration(),
130+
'accumulated_render_time_str' => $this->getDataFormatter()->formatDuration($this->profile->getDuration()),
131+
'memory_usage_str' => $this->getDataFormatter()->formatBytes($this->profile->getMemoryUsage()),
132+
'callgraph' => $this->getHtmlCallGraph(),
133+
'badge' => implode(
134+
'/',
135+
array(
136+
$this->templateCount,
137+
$this->blockCount,
138+
$this->macroCount,
139+
)
140+
),
141+
);
142+
}
143+
144+
/**
145+
* Returns the unique name of the collector
146+
*
147+
* @return string
148+
*/
149+
public function getName()
150+
{
151+
return 'twig';
152+
}
153+
154+
public function getHtmlCallGraph()
155+
{
156+
$dumper = new \Twig_Profiler_Dumper_Html();
157+
158+
return $dumper->dump($this->profile);
159+
}
160+
161+
/**
162+
* Get an Xdebug Link to a file
163+
*
164+
* @return array {
165+
* @var string url
166+
* @var bool ajax
167+
* }
168+
*/
169+
public function getXdebugLink($template, $line = 1)
170+
{
171+
if (is_null($this->loader)) {
172+
return null;
173+
}
174+
$file = $this->loader->getSourceContext($template)->getPath();
175+
176+
return parent::getXdebugLink($file, $line);
177+
}
178+
179+
private function computeData(\Twig_Profiler_Profile $profile)
180+
{
181+
$this->templateCount += ($profile->isTemplate() ? 1 : 0);
182+
$this->blockCount += ($profile->isBlock() ? 1 : 0);
183+
$this->macroCount += ($profile->isMacro() ? 1 : 0);
184+
if ($profile->isTemplate()) {
185+
$this->templates[] = array(
186+
'name' => $profile->getName(),
187+
'render_time' => $profile->getDuration(),
188+
'render_time_str' => $this->getDataFormatter()->formatDuration($profile->getDuration()),
189+
'memory_str' => $this->getDataFormatter()->formatBytes($profile->getMemoryUsage()),
190+
'xdebug_link' => $this->getXdebugLink($profile->getTemplate()),
191+
);
192+
}
193+
foreach ($profile as $p) {
194+
$this->computeData($p);
195+
}
196+
}
197+
}

src/DebugBar/DataCollector/DataCollector.php

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ abstract class DataCollector implements DataCollectorInterface
2424

2525
protected $dataFormater;
2626
protected $varDumper;
27+
protected $xdebugLinkTemplate = '';
28+
protected $xdebugShouldUseAjax = false;
29+
protected $xdebugReplacements = array();
2730

2831
/**
2932
* Sets the default data formater instance used by all collectors subclassing this class
@@ -74,12 +77,23 @@ public function getDataFormatter()
7477
/**
7578
* Get an Xdebug Link to a file
7679
*
77-
* @return string|null
80+
* @param string $file
81+
* @param int $line
82+
*
83+
* @return array {
84+
* @var string $url
85+
* @var bool $ajax should be used to open the url instead of a normal links
86+
* }
7887
*/
7988
public function getXdebugLink($file, $line = 1)
8089
{
81-
if (ini_get('xdebug.file_link_format') || extension_loaded('xdebug')) {
82-
return e(str_replace(array('%f', '%l'), array($file, $line), ini_get('xdebug.file_link_format')));
90+
if (count($this->xdebugReplacements)) {
91+
$file = strtr($file, $this->xdebugReplacements);
92+
}
93+
94+
$url = strtr($this->getXdebugLinkTemplate(), ['%f' => $file, '%l' => $line]);
95+
if ($url) {
96+
return ['url' => $url, 'ajax' => $this->getXdebugShouldUseAjax()];
8397
}
8498
}
8599

@@ -155,4 +169,66 @@ public function formatBytes($size, $precision = 2)
155169
{
156170
return $this->getDataFormatter()->formatBytes($size, $precision);
157171
}
172+
173+
/**
174+
* @return string
175+
*/
176+
public function getXdebugLinkTemplate()
177+
{
178+
if (empty($this->xdebugLinkTemplate) && !empty(ini_get('xdebug.file_link_format'))) {
179+
$this->xdebugLinkTemplate = ini_get('xdebug.file_link_format');
180+
}
181+
182+
return $this->xdebugLinkTemplate;
183+
}
184+
185+
/**
186+
* @param string $xdebugLinkTemplate
187+
* @param bool $shouldUseAjax
188+
*/
189+
public function setXdebugLinkTemplate($xdebugLinkTemplate, $shouldUseAjax = false)
190+
{
191+
if ($xdebugLinkTemplate === 'idea') {
192+
$this->xdebugLinkTemplate = 'http://localhost:63342/api/file/?file=%f&line=%l';
193+
$this->xdebugShouldUseAjax = true;
194+
} else {
195+
$this->xdebugLinkTemplate = $xdebugLinkTemplate;
196+
$this->xdebugShouldUseAjax = $shouldUseAjax;
197+
}
198+
}
199+
200+
/**
201+
* @return bool
202+
*/
203+
public function getXdebugShouldUseAjax()
204+
{
205+
return $this->xdebugShouldUseAjax;
206+
}
207+
208+
/**
209+
* returns an array of filename-replacements
210+
*
211+
* this is useful f.e. when using vagrant or remote servers,
212+
* where the path of the file is different between server and
213+
* development environment
214+
*
215+
* @return array key-value-pairs of replacements, key = path on server, value = replacement
216+
*/
217+
public function getXdebugReplacements()
218+
{
219+
return $this->xdebugReplacements;
220+
}
221+
222+
/**
223+
* @param array $xdebugReplacements
224+
*/
225+
public function setXdebugReplacements($xdebugReplacements)
226+
{
227+
$this->xdebugReplacements = $xdebugReplacements;
228+
}
229+
230+
public function setXdebugReplacement($serverPath, $replacement)
231+
{
232+
$this->xdebugReplacements[$serverPath] = $replacement;
233+
}
158234
}

0 commit comments

Comments
 (0)