Skip to content

Instantly share code, notes, and snippets.

@sdboyer
Last active December 19, 2015 03:48
Show Gist options
  • Select an option

  • Save sdboyer/5892709 to your computer and use it in GitHub Desktop.

Select an option

Save sdboyer/5892709 to your computer and use it in GitHub Desktop.
<?php
/**
* @file
* Contains Drupal\Core\Asset\AssetCollector.
*/
namespace Drupal\Core\Asset;
/**
* A class that helps to create and collect assets.
*
* This class should be set with appropriate defaults, injected with an AssetBag
* for collection, then injected into an asset-producing segment of code in
* order to ease the creation and collection of asset information.
*/
class AssetCollector {
/**
* The bag used to store any assets that are added.
*
* @var \Drupal\Core\Asset\AssetBagInterface
*/
protected $bag;
/**
* Flag indicating whether or not the object is locked.
*
* Locking prevents modifying the underlying defaults or the current bag.
*
* @var bool
*/
protected $locked = FALSE;
/**
* The key with which the lock was set.
*
* This exact value (===) must be provided in order to unlock the instance.
*
* There are no type restrictions.
*
* @var mixed
*/
protected $lockKey;
protected $defaultAssetDefaults = array(
'css' => array(
'group' => CSS_AGGREGATE_DEFAULT,
'weight' => 0,
'every_page' => FALSE,
'media' => 'all',
'preprocess' => TRUE,
'browsers' => array(
'IE' => TRUE,
'!IE' => TRUE,
),
),
'js' => array(
'group' => JS_DEFAULT,
'every_page' => FALSE,
'weight' => 0,
'scope' => 'header',
'cache' => TRUE,
'preprocess' => TRUE,
'attributes' => array(),
'version' => NULL,
'browsers' => array(),
),
);
protected $assetDefaults = array();
protected $methodMap = array(
'css' => array(
'file' => 'createCssFileAsset',
'external' => 'createCssExternalAsset',
'string' => 'createCssStringAsset',
),
'js' => array(
'file' => 'createJsFileAsset',
'external' => 'createJsExternalAsset',
'string' => 'createJsStringAsset',
),
);
public function __construct() {
$this->restoreDefaults();
}
/**
* Adds an asset to the injected AM
*/
public function add($name, AssetInterface $asset) {
}
/**
* Creates an asset and returns it.
*/
public function create($asset_type, $source_type, $data, $options = array()) {
}
public function setBag(AssetBagInterface $bag) {
if ($this->isLocked()) {
throw new \Exception('The collector instance is locked. A new bag cannot assigned on a locked collector.');
}
$this->bag = $bag;
}
public function clearBag() {
if ($this->isLocked()) {
throw new \Exception('The collector instance is locked. Bags cannot be cleared on a locked collector.');
}
$this->bag = NULL;
}
public function createCssFileAsset() {
}
public function createCssStringAsset() {
}
public function createCssExternalAsset() {
}
public function createJsFileAsset() {
}
public function createJsStringAsset() {
}
public function createJsExternalAsset() {
}
public function createJsSetting() {
}
public function lock($key) {
if ($this->isLocked()) {
throw new \Exception('Collector is already locked.', E_WARNING);
}
$this->locked = TRUE;
$this->lockKey = $key;
return TRUE;
}
public function unlock($key) {
if (!$this->isLocked()) {
throw new \Exception('Collector is not locked', E_WARNING);
}
if ($this->lockKey !== $key) {
throw new \Exception('Attempted to unlock Collector with incorrect key.', E_WARNING);
}
$this->locked = FALSE;
$this->lockKey = NULL;
return TRUE;
}
public function isLocked() {
return $this->locked;
}
public function setDefaults($type, array $defaults) {
if ($this->isLocked()) {
throw new \Exception('The collector instance is locked. Asset defaults cannot be modified on a locked collector.');
}
$this->assetDefaults[$type] = array_merge($this->assetDefaults[$type], $defaults);
}
public function getDefaults($type = NULL) {
if (!isset($type)) {
return $this->assetDefaults;
}
if (!isset($this->assetDefaults[$type])) {
throw new \InvalidArgumentException(sprintf('The type provided, "%s", is not known.', $type));
}
return $this->assetDefaults[$type];
}
public function restoreDefaults() {
if ($this->isLocked()) {
throw new \Exception('The collector instance is locked. Asset defaults cannot be modified on a locked collector.');
}
$this->assetDefaults = $this->defaultAssetDefaults;
}
}
<?php
/**
* @file
* Contains Drupal\Tests\Core\Asset\AssetCollectorTest.
*/
namespace Drupal\Tests\Core\Asset;
if (!defined('CSS_AGGREGATE_THEME')) {
define('CSS_AGGREGATE_THEME', 100);
}
if (!defined('CSS_AGGREGATE_DEFAULT')) {
define('CSS_AGGREGATE_DEFAULT', 0);
}
if (!defined('JS_DEFAULT')) {
define('JS_DEFAULT', 0);
}
use Drupal\Core\Asset\AssetBag;
use Drupal\Core\Asset\AssetCollector;
use Drupal\Tests\UnitTestCase;
/**
* Tests for the asset collector.
*
* TODO DOCS, DOCS, DOCS DOCS DOCS
*
* @group Asset
*/
class AssetCollectorTest extends UnitTestCase {
/**
* @var \Drupal\Core\Asset\AssetCollector
*/
protected $collector;
public static function getInfo() {
return array(
'name' => 'Asset Collector tests',
'description' => 'Tests that the AssetCollector system works correctly.',
'group' => 'Asset',
);
}
public function setUp() {
parent::setUp();
$this->collector = new AssetCollector();
}
/**
* Tests that the collector creates the right types of assets.
*/
public function testAssetCreation() {
// First, ensure that each type of asset can be created correctly, both
// through the generic and specific methods.
$css_file1 = $this->collector->create('css', 'file', 'foo');
$css_file2 = $this->collector->createCssFileAsset('foo');
$this->assertInstanceOf('\Drupal\Core\Asset\CssFileAsset', $css_file1, 'Collector correctly created a CssFileAsset instance through the generic method.');
$this->assertInstanceOf('\Drupal\Core\Asset\CssFileAsset', $css_file2, 'Collector correctly created a CssFileAsset instance through the specific method.');
$css_external1 = $this->collector->create('css', 'external', 'foo');
$css_external2 = $this->collector->createCssExternalAsset('foo');
$this->assertInstanceOf('\Drupal\Core\Asset\CssExternalAsset', $css_external1, 'Collector correctly created a CssExternalAsset instance through the generic method.');
$this->assertInstanceOf('\Drupal\Core\Asset\CssExternalAsset', $css_external2, 'Collector correctly created a CssExternalAsset instance through the specific method.');
$css_string1 = $this->collector->create('css', 'string', 'foo');
$css_string2 = $this->collector->createCssStringAsset('foo');
$this->assertInstanceOf('\Drupal\Core\Asset\CssStringAsset', $css_string1, 'Collector correctly created a CssStringAsset instance through the generic method.');
$this->assertInstanceOf('\Drupal\Core\Asset\CssStringAsset', $css_string2, 'Collector correctly created a CssStringAsset instance through the specific method.');
$js_file1 = $this->collector->create('js', 'file', 'foo');
$js_file2 = $this->collector->createJsFileAsset('foo');
$this->assertInstanceOf('\Drupal\Core\Asset\JsFileAsset', $js_file1, 'Collector correctly created a JsFileAsset instance through the generic method.');
$this->assertInstanceOf('\Drupal\Core\Asset\JsFileAsset', $js_file2, 'Collector correctly created a JsFileAsset instance through the specific method.');
$js_external1 = $this->collector->create('js', 'external', 'foo');
$js_external2 = $this->collector->createJsExternalAsset('foo');
$this->assertInstanceOf('\Drupal\Core\Asset\JsExternalAsset', $js_external1, 'Collector correctly created a JsExternalAsset instance through the generic method.');
$this->assertInstanceOf('\Drupal\Core\Asset\JsExternalAsset', $js_external2, 'Collector correctly created a JsExternalAsset instance through the specific method.');
$js_string1 = $this->collector->create('js', 'string', 'foo');
$js_string2 = $this->collector->createJsStringAsset('foo');
$this->assertInstanceOf('\Drupal\Core\Asset\JsStringAsset', $js_string1, 'Collector correctly created a JsStringAsset instance through the generic method.');
$this->assertInstanceOf('\Drupal\Core\Asset\JsStringAsset', $js_string2, 'Collector correctly created a JsStringAsset instance through the specific method.');
// Now ensure that an exception is thrown
}
/**
* Tests that the collector injects provided metadata to created assets.
*/
public function testMetadataInjection() {
// Test a single value first
$asset = $this->collector->createCssFileAsset('foo', array('group' => CSS_AGGREGATE_THEME));
$this->assertEquals(CSS_AGGREGATE_THEME, $asset['group'], 'Collector injected user-passed parameters into the created asset.');
// TODO is it worth testing multiple params? what about weird ones, like weight?
}
public function testStorage() {
// First, try to add an asset when no bag is present in the collector.
$asset = $this->collector->create('css', 'string', 'foo');
try {
$this->collector->add('foo', $asset);
$this->fail('Collector failed to throw an appropriate exception when an asset was added without a bag being present.');
}
catch (\Exception $e) {
$this->assertTrue(TRUE, 'Collector threw expected exception when attempting to add an asset without an underlying bag present.');
}
// Now add a bag to the collector, add the asset, and ensure we can get it
// back out of the bag.
$bag = new AssetBag();
$this->collector->setBag($bag);
$this->collector->add('foo', $asset);
$this->assertContains($asset, $bag->getCss(), 'Asset added to collector is available to the code that injected the bag.');
// Ensure that implicit adding also works: when a bag is present in a
// collector, it should automatically add created assets to it.
$asset2 = $this->collector->create('css', 'file', 'bar');
$this->assertContains($asset2, $bag->getCss(), 'Asset created via generic method was implicitly added to bag.');
$asset3 = $this->collector->createCssFileAsset('baz');
$this->assertContains($asset3, $bag->getCss(), 'Asset created via specific method was implicitly added to bag.');
// Now remove the bag, and make sure the adding exception is thrown again.
$this->collector->clearBag();
try {
$this->collector->add('foo', $asset);
$this->fail('Collector failed to throw an appropriate exception when an asset was added after the containing bag should have been cleared.');
}
catch (\Exception $e) {
$this->assertTrue(TRUE, 'Collector threw expected exception when attempting to add an asset after the containing bag was removed.');
}
}
/**
* Tests that locking works, and the correct methods are disabled when locked.
*/
public function testLocking() {
$this->assertTrue($this->collector->lock($this), 'Collector locked succesfully.');
$this->assertTrue($this->collector->isLocked(), 'Collector accurately reports that it is locked via isLocked() method.');
try {
$this->collector->setDefaults('css', array('foo' => 'bar'));
$this->fail('No exception thrown when an attempt was made to change protected values while the collector was locked.');
}
catch (\Exception $e) {
$this->assertTrue(TRUE, 'Collector threw appropriate exception on lock violation.');
}
try {
$this->collector->restoreDefaults();
$this->fail('No exception thrown when an attempt was made to change protected values while the collector was locked.');
}
catch (\Exception $e) {
$this->assertTrue(TRUE, 'Collector threw appropriate exception on lock violation.');
}
try {
$this->collector->clearBag();
$this->fail('No exception thrown when an attempt was made to clear the current bag while the collector was locked.');
}
catch (\Exception $e) {
$this->assertTrue(TRUE, 'Collector threw appropriate exception on lock violation.');
}
try {
$this->collector->setBag(new AssetBag());
$this->fail('No exception thrown when an attempt was made to clear the current bag while the collector was locked.');
}
catch (\Exception $e) {
$this->assertTrue(TRUE, 'Collector threw appropriate exception on lock violation.');
}
try {
$this->collector->unlock('foo');
$this->fail('Collector failed to throw an appropriate exception when an attempt to unlock was made without the correct key.');
}
catch (\Exception $e) {
$this->assertTrue(TRUE, 'Collector threw appropriate exception on unauthorized attempt to unlock.');
}
$this->assertTrue($this->collector->unlock($this), 'Collector unlocked successfully when appropriate key was provided.');
$this->assertFalse($this->collector->isLocked(), 'Collector correctly reported unlocked state via isLocked() method after unlocking.');
}
public function testDefaults() {
$builtin_defaults = array(
'css' => array(
'group' => CSS_AGGREGATE_DEFAULT,
'weight' => 0,
'every_page' => FALSE,
'media' => 'all',
'preprocess' => TRUE,
'browsers' => array(
'IE' => TRUE,
'!IE' => TRUE,
),
),
'js' => array(
'group' => JS_DEFAULT,
'every_page' => FALSE,
'weight' => 0,
'scope' => 'header',
'cache' => TRUE,
'preprocess' => TRUE,
'attributes' => array(),
'version' => NULL,
'browsers' => array(),
),
);
// First, test that manipulating the assets works as expected.
$this->assertEquals($builtin_defaults['css'], $this->collector->getDefaults('css'), 'Expected set of built-in defaults reside in the collector.');
$changed_defaults = array('every_page' => TRUE, 'group' => CSS_AGGREGATE_THEME);
$this->collector->setDefaults('css', $changed_defaults);
$this->assertEquals($changed_defaults + $builtin_defaults['css'], $this->collector->getDefaults('css'), 'Expected combination of built-in and injected defaults reside in the collector.');
$this->collector->restoreDefaults();
$this->assertEquals($builtin_defaults, $this->collector->getDefaults(), 'Built-in defaults were correctly restored.');
// Test that an exception is thrown when an invalid type is requested.
try {
$this->collector->getDefaults('foo');
$this->fail('No exception thrown when an invalid key was requested.');
}
catch (\InvalidArgumentException $e) {}
// Test that defaults are correctly applied when passing through both
// the generic and specific factory methods.
$css1 = $this->collector->create('css', 'file', 'foo');
$this->assertTrue($css1['every_page'], 'Correct default propagated for "every_page" property.');
$this->assertEquals(CSS_AGGREGATE_THEME, $css1['group'], 'Correct default propagated for "group" property.');
$css2 = $this->collector->createCssFileAsset('foo');
$this->assertTrue($css2['every_page'], 'Correct default propagated for "every_page" property.');
$this->assertEquals(CSS_AGGREGATE_THEME, $css2['group'], 'Correct default propagated for "group" property.');
// TODO bother testing js? it seems logically redundant
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment