1
0
mirror of https://github.com/maximebf/php-debugbar.git synced 2025-01-16 21:08:34 +01:00

Enable AssetProvider to support inline assets (#338)

Add new inline_css, inline_js, and inline_head keys on the
AssetProvider::getAssets() function.  This allows us to support
collectors that require static assets that are not actually saved to a
file.

Then, update all the asset functions in JavascriptRenderer to support
these new keys.

An initial use case for this is supporting the HtmlDumper in Symfony’s
VarDumper.  HtmlDumper only provides the styles and scripts in inline
HTML form.  The static assets can be customized based on some
configuration properties available on the HtmlDumper class.  One can
actually view the CSS/JS as a long PHP string/heredoc embedded in the
HtmlDumper.php source code.  They are only accessible via the
getDumpHeader function, which returns the CSS/JS in a combined HTML
string.
This commit is contained in:
James Johnston 2017-07-15 02:08:32 -07:00 committed by Barry vd. Heuvel
parent 4773b2f89b
commit e6f0b5a48d
5 changed files with 215 additions and 61 deletions

View File

@ -71,16 +71,31 @@ in `JavascriptRenderer::addControl($name, $options)` (see Rendering chapter).
This will have the result of adding a new indicator to the debug bar.
When implementing the Renderable interface, you may use widgets which are not provided
When implementing the `Renderable` interface, you may use widgets which are not provided
with the default install. You can add new assets by implementing the `DebugBar\DataCollector\AssetProvider` interface.
to implement it, you must define the `getAssets()` method. It must return an array with the
To implement it, you must define the `getAssets()` method. It must return an array with the
following keys:
- base\_path: base path of assets (optional, if omitted or null, will use the base path of the JavascriptRenderer)
- base\_url: base url of assets (optional, same as base\_path)
- css: an array of css filenames
- js: an array of javascript filenames
- `base_path`: base path of assets (optional, if omitted or null, will use the base path of the `JavascriptRenderer`)
- `base_url`: base url of assets (optional, same as `base_path`)
- `css`: an array of css filenames
- `js`: an array of javascript filenames
- `inline_css`: an array map of content ID to inline CSS content (not including `<style>` tag)
- `inline_js`: an array map of content ID to inline JS content (not including `<script>` tag)
- `inline_head`: an array map of content ID to arbitrary inline HTML content (typically
`<style>`/`<script>` tags); it will be embedded within the `<head>` element
All keys are optional.
Ideally, you should store static assets in filenames that are returned via the normal `css`/`js`
keys. However, the inline asset elements are useful when integrating with 3rd-party
libraries that require static assets that are only available in an inline format.
The inline content arrays require special string array keys to identify the content: the debug bar
will use them to deduplicate. This is particularly useful if multiple instances of the same asset
provider are used. Inline assets from all collectors are merged together into the same array,
so these content IDs effectively deduplicate the inline assets.
Example:
@ -104,7 +119,18 @@ Example:
{
return array(
'css' => 'widgets/sqlqueries/widget.css',
'js' => 'widgets/sqlqueries/widget.js'
'js' => 'widgets/sqlqueries/widget.js',
// Ordinarily, inline assets like these should be avoided whenever possible:
'inline_css' => array(
'db_widget_css' => 'div.myelement { color: #000; }',
),
'inline_js' => array(
'db_widget_js' => 'alert("Db widget asset loaded.");'
),
'inline_head' => array(
'db_widget_head' => '<meta content="Arbitrary HTML content">'
)
);
}
}

View File

@ -1,6 +1,6 @@
# Rendering
Rendering is performed using the `DebugBar\JavascriptRenderer̀ class. It contains
Rendering is performed using the `DebugBar\JavascriptRenderer` class. It contains
all the useful functions to included the needed assets and generate a debug bar.
$renderer = $debugbar->getJavascriptRenderer();
@ -9,15 +9,16 @@ all the useful functions to included the needed assets and generate a debug bar.
The debug bar relies on some css and javascript files which needs to be included
into your webpage. They are located in the *src/DebugBar/Resources* folder.
This can be done in four ways:
Additionally, asset providers may provide inline assets that have to be embedded
directly in the HTML. This can be done in four ways:
- Using `JavascriptRenderer::renderHead()` which will returns a string with
the needed script and link tags
- Using [Assetic](https://github.com/kriswallsmith/assetic) and
`JavascriptRenderer::getAsseticCollection()`
- Dumping the assets yourself using `JavascriptRenderer::dumpCssAssets()` and
`JavascriptRenderer::dumpJsAssets()`
- Retrieving the list filenames of assets using `JavascriptRenderer::getAssets()`
- Dumping the assets yourself using `JavascriptRenderer::dumpCssAssets()`,
`JavascriptRenderer::dumpJsAssets()`, and `JavascriptRenderer::dumpHeadAssets()`.
- Retrieving filenames and inline content of assets using `JavascriptRenderer::getAssets()`
and doing something with it
I would recommend using the second method as Assetic is a very powerful asset
@ -40,7 +41,7 @@ Using `renderHead()`:
Using Assetic:
list($cssCollection, $jsCollection) = $renderer->getAsseticCollection();
list($cssCollection, $jsCollection, $inlineHeadCollection) = $renderer->getAsseticCollection();
Dumping the assets:
@ -49,7 +50,7 @@ Dumping the assets:
Retrieving the assets:
list($cssFiles, $jsFiles) = $renderer->getAssets();
list($cssFiles, $jsFiles, $inlineCss, $inlineJs, $inlineHead) = $renderer->getAssets();
Note that you can only use the debug bar assets and manage the dependencies by yourself
using `$renderer->setIncludeVendors(false)`. Instead of false, *css* or *js* may be used
@ -100,7 +101,7 @@ the first argument of ̀render()`.
### Controlling object initialization
You can further control the initialization of the javascript object using `setInitialization()`.
It takes a bitwise value made out of the constants ̀INITIALIZE_CONSTRUCTOR` and `INITIALIZE_CONTROLS`.
It takes a bitwise value made out of the constants `INITIALIZE_CONSTRUCTOR` and `INITIALIZE_CONTROLS`.
The first one controls whether to initialize the variable (ie. `var debugbar = new DebugBar()`). The
second one whether to initialize all the controls (ie. adding tab and indicators as well as data mapping).

View File

@ -21,6 +21,21 @@ interface AssetProvider
* - base_url
* - css: an array of filenames
* - js: an array of filenames
* - inline_css: an array map of content ID to inline CSS content (not including <style> tag)
* - inline_js: an array map of content ID to inline JS content (not including <script> tag)
* - inline_head: an array map of content ID to arbitrary inline HTML content (typically
* <style>/<script> tags); it must be embedded within the <head> element
*
* All keys are optional.
*
* Ideally, you should store static assets in filenames that are returned via the normal css/js
* keys. However, the inline asset elements are useful when integrating with 3rd-party
* libraries that require static assets that are only available in an inline format.
*
* The inline content arrays require special string array keys: the caller of this function
* will use them to deduplicate content. This is particularly useful if multiple instances of
* the same asset provider are used. Inline assets from all collectors are merged together into
* the same array, so these content IDs effectively deduplicate the inline assets.
*
* @return array
*/

View File

@ -584,12 +584,13 @@ class JavascriptRenderer
}
/**
* Add assets to render in the head
* Add assets stored in files to render in the head
*
* @param array $cssFiles An array of filenames
* @param array $jsFiles An array of filenames
* @param string $basePath Base path of those files
* @param string $baseUrl Base url of those files
* @return $this
*/
public function addAssets($cssFiles, $jsFiles, $basePath = null, $baseUrl = null)
{
@ -602,10 +603,37 @@ class JavascriptRenderer
return $this;
}
/**
* Add inline assets to render inline in the head. Ideally, you should store static assets in
* files that you add with the addAssets function. However, adding inline assets is useful when
* integrating with 3rd-party libraries that require static assets that are only available in an
* inline format.
*
* The inline content arrays require special string array keys: they are used to deduplicate
* content. This is particularly useful if multiple instances of the same asset end up being
* added. Inline assets from all collectors are merged together into the same array, so these
* content IDs effectively deduplicate the inline assets.
*
* @param array $inlineCss An array map of content ID to inline CSS content (not including <style> tag)
* @param array $inlineJs An array map of content ID to inline JS content (not including <script> tag)
* @param array $inlineHead An array map of content ID to arbitrary inline HTML content (typically
* <style>/<script> tags); it must be embedded within the <head> element
* @return $this
*/
public function addInlineAssets($inlineCss, $inlineJs, $inlineHead)
{
$this->additionalAssets[] = array(
'inline_css' => (array) $inlineCss,
'inline_js' => (array) $inlineJs,
'inline_head' => (array) $inlineHead
);
return $this;
}
/**
* Returns the list of asset files
*
* @param string $type Only return css or js files
* @param string $type 'css', 'js', 'inline_css', 'inline_js', 'inline_head', or null for all
* @param string $relativeTo The type of path to which filenames must be relative (path, url or null)
* @return array
*/
@ -613,6 +641,9 @@ class JavascriptRenderer
{
$cssFiles = $this->cssFiles;
$jsFiles = $this->jsFiles;
$inlineCss = array();
$inlineJs = array();
$inlineHead = array();
if ($this->includeVendors !== false) {
if ($this->includeVendors === true || in_array('css', $this->includeVendors)) {
@ -643,11 +674,29 @@ class JavascriptRenderer
$root = $this->getRelativeRoot($relativeTo,
$this->makeUriRelativeTo($basePath, $this->basePath),
$this->makeUriRelativeTo($baseUrl, $this->baseUrl));
$cssFiles = array_merge($cssFiles, $this->makeUriRelativeTo((array) $assets['css'], $root));
$jsFiles = array_merge($jsFiles, $this->makeUriRelativeTo((array) $assets['js'], $root));
if (isset($assets['css'])) {
$cssFiles = array_merge($cssFiles, $this->makeUriRelativeTo((array) $assets['css'], $root));
}
if (isset($assets['js'])) {
$jsFiles = array_merge($jsFiles, $this->makeUriRelativeTo((array) $assets['js'], $root));
}
if (isset($assets['inline_css'])) {
$inlineCss = array_merge($inlineCss, (array) $assets['inline_css']);
}
if (isset($assets['inline_js'])) {
$inlineJs = array_merge($inlineJs, (array) $assets['inline_js']);
}
if (isset($assets['inline_head'])) {
$inlineHead = array_merge($inlineHead, (array) $assets['inline_head']);
}
}
return $this->filterAssetArray(array($cssFiles, $jsFiles), $type);
// Deduplicate files
$cssFiles = array_unique($cssFiles);
$jsFiles = array_unique($jsFiles);
return $this->filterAssetArray(array($cssFiles, $jsFiles, $inlineCss, $inlineJs, $inlineHead), $type);
}
/**
@ -697,53 +746,64 @@ class JavascriptRenderer
}
/**
* Filters a tuple of (css, js) assets according to $type
* Filters a tuple of (css, js, inline_css, inline_js, inline_head) assets according to $type
*
* @param array $array
* @param string $type 'css', 'js' or null for both
* @param string $type 'css', 'js', 'inline_css', 'inline_js', 'inline_head', or null for all
* @return array
*/
protected function filterAssetArray($array, $type = null)
{
$type = strtolower($type);
if ($type === 'css') {
return $array[0];
}
if ($type === 'js') {
return $array[1];
}
return $array;
$types = array('css', 'js', 'inline_css', 'inline_js', 'inline_head');
$typeIndex = array_search(strtolower($type), $types);
return $typeIndex !== false ? $array[$typeIndex] : $array;
}
/**
* Returns a tuple where the both items are Assetic AssetCollection,
* the first one being css files and the second js files
* Returns an array where all items are Assetic AssetCollection:
* - The first one contains the CSS files
* - The second one contains the JS files
* - The third one contains arbitrary inline HTML (typically composed of <script>/<style>
* elements); it must be embedded within the <head> element
*
* @param string $type Only return css or js collection
* @param string $type Optionally return only 'css', 'js', or 'inline_head' collection
* @return array or \Assetic\Asset\AssetCollection
*/
public function getAsseticCollection($type = null)
{
list($cssFiles, $jsFiles) = $this->getAssets();
return $this->filterAssetArray(array(
$this->createAsseticCollection($cssFiles),
$this->createAsseticCollection($jsFiles)
), $type);
$types = array('css', 'js', 'inline_head');
$typeIndex = array_search(strtolower($type), $types);
list($cssFiles, $jsFiles, $inlineCss, $inlineJs, $inlineHead) = $this->getAssets();
$collections = array(
$this->createAsseticCollection($cssFiles, $inlineCss),
$this->createAsseticCollection($jsFiles, $inlineJs),
$this->createAsseticCollection(null, $inlineHead)
);
return $typeIndex !== false ? $collections[$typeIndex] : $collections;
}
/**
* Create an Assetic AssetCollection with the given files.
* Create an Assetic AssetCollection with the given content.
* Filenames will be converted to absolute path using
* the base path.
*
* @param array $files
* @param array|null $files Array of asset filenames.
* @param array|null $content Array of inline asset content.
* @return \Assetic\Asset\AssetCollection
*/
protected function createAsseticCollection($files)
protected function createAsseticCollection($files = null, $content = null)
{
$assets = array();
foreach ($files as $file) {
$assets[] = new \Assetic\Asset\FileAsset($file);
if ($files) {
foreach ($files as $file) {
$assets[] = new \Assetic\Asset\FileAsset($file);
}
}
if ($content) {
foreach ($content as $item) {
$assets[] = new \Assetic\Asset\StringAsset($item);
}
}
return new \Assetic\Asset\AssetCollection($assets);
}
@ -755,7 +815,7 @@ class JavascriptRenderer
*/
public function dumpCssAssets($targetFilename = null)
{
$this->dumpAssets($this->getAssets('css'), $targetFilename);
$this->dumpAssets($this->getAssets('css'), $this->getAssets('inline_css'), $targetFilename);
}
/**
@ -765,29 +825,48 @@ class JavascriptRenderer
*/
public function dumpJsAssets($targetFilename = null)
{
$this->dumpAssets($this->getAssets('js'), $targetFilename, $this->useRequireJs);
$this->dumpAssets($this->getAssets('js'), $this->getAssets('inline_js'), $targetFilename, $this->useRequireJs);
}
/**
* Write all inline HTML header assets to standard output or in a file (only returns assets not
* already returned by dumpCssAssets or dumpJsAssets)
*
* @param string $targetFilename
*/
public function dumpHeadAssets($targetFilename = null)
{
$this->dumpAssets(null, $this->getAssets('inline_head'), $targetFilename);
}
/**
* Write assets to standard output or in a file
*
* @param array $files
* @param array|null $files Filenames containing assets
* @param array|null $content Inline content to dump
* @param string $targetFilename
* @param bool $useRequireJs
*/
protected function dumpAssets($files, $targetFilename = null, $useRequireJs = false)
protected function dumpAssets($files = null, $content = null, $targetFilename = null, $useRequireJs = false)
{
$content = '';
foreach ($files as $file) {
$content .= file_get_contents($file) . "\n";
$dumpedContent = '';
if ($files) {
foreach ($files as $file) {
$dumpedContent .= file_get_contents($file) . "\n";
}
}
if ($content) {
foreach ($content as $item) {
$dumpedContent .= $item . "\n";
}
}
if ($useRequireJs) {
$content = "define('debugbar', ['jquery'], function($){\r\n" . $content . "\r\n return PhpDebugBar; \r\n});";
$dumpedContent = "define('debugbar', ['jquery'], function($){\r\n" . $dumpedContent . "\r\n return PhpDebugBar; \r\n});";
}
if ($targetFilename !== null) {
file_put_contents($targetFilename, $content);
file_put_contents($targetFilename, $dumpedContent);
} else {
echo $content;
echo $dumpedContent;
}
}
@ -800,17 +879,29 @@ class JavascriptRenderer
*/
public function renderHead()
{
list($cssFiles, $jsFiles) = $this->getAssets(null, self::RELATIVE_URL);
list($cssFiles, $jsFiles, $inlineCss, $inlineJs, $inlineHead) = $this->getAssets(null, self::RELATIVE_URL);
$html = '';
foreach ($cssFiles as $file) {
$html .= sprintf('<link rel="stylesheet" type="text/css" href="%s">' . "\n", $file);
}
foreach ($inlineCss as $content) {
$html .= sprintf('<style type="text/css">%s</style>' . "\n", $content);
}
foreach ($jsFiles as $file) {
$html .= sprintf('<script type="text/javascript" src="%s"></script>' . "\n", $file);
}
foreach ($inlineJs as $content) {
$html .= sprintf('<script type="text/javascript">%s</script>' . "\n", $content);
}
foreach ($inlineHead as $content) {
$html .= $content . "\n";
}
if ($this->enableJqueryNoConflict && !$this->useRequireJs) {
$html .= '<script type="text/javascript">jQuery.noConflict(true);</script>' . "\n";
}

View File

@ -64,14 +64,25 @@ class JavascriptRendererTest extends DebugBarTestCase
public function testAddAssets()
{
$this->r->addAssets('foo.css', 'foo.js', '/bar', '/foobar');
// Use a loop to test deduplication of assets
for ($i = 0; $i < 2; ++$i) {
$this->r->addAssets('foo.css', 'foo.js', '/bar', '/foobar');
$this->r->addInlineAssets(array('Css' => 'CssTest'), array('Js' => 'JsTest'), array('Head' => 'HeaderTest'));
}
list($css, $js) = $this->r->getAssets();
// Make sure all the right assets are returned by getAssets
list($css, $js, $inline_css, $inline_js, $inline_head) = $this->r->getAssets();
$this->assertContains('/bar/foo.css', $css);
$this->assertContains('/bar/foo.js', $js);
$this->assertEquals(array('Css' => 'CssTest'), $inline_css);
$this->assertEquals(array('Js' => 'JsTest'), $inline_js);
$this->assertEquals(array('Head' => 'HeaderTest'), $inline_head);
// Make sure asset files are deduplicated
$this->assertCount(count(array_unique($css)), $css);
$this->assertCount(count(array_unique($js)), $js);
$html = $this->r->renderHead();
//$this->assertTag(array('tag' => 'script', 'attributes' => array('src' => '/foobar/foo.js')), $html);
$this->assertContains('<script type="text/javascript" src="/foobar/foo.js"></script>', $html);
}
@ -90,13 +101,23 @@ class JavascriptRendererTest extends DebugBarTestCase
public function testRenderHead()
{
$html = $this->r->renderHead();
$this->assertContains('<script type="text/javascript" src="/burl/debugbar.js"></script>', $html);
$this->assertTrue(strpos($html, "jQuery.noConflict(true);") > -1);
$this->r->addInlineAssets(array('Css' => 'CssTest'), array('Js' => 'JsTest'), array('Head' => 'HeaderTest'));
$html = $this->r->renderHead();
// Check for file links
$this->assertContains('<link rel="stylesheet" type="text/css" href="/burl/debugbar.css">', $html);
$this->assertContains('<script type="text/javascript" src="/burl/debugbar.js"></script>', $html);
// Check for inline assets
$this->assertContains('<style type="text/css">CssTest</style>', $html);
$this->assertContains('<script type="text/javascript">JsTest</script>', $html);
$this->assertContains('HeaderTest', $html);
// Check jQuery noConflict
$this->assertContains('jQuery.noConflict(true);', $html);
// Check for absence of jQuery noConflict
$this->r->setEnableJqueryNoConflict(false);
$html = $this->r->renderHead();
$this->assertFalse(strpos($html, "jQuery.noConflict(true);"));
$this->assertNotContains('noConflict', $html);
}
public function testRenderFullInitialization()