Skip to content

Commit b8ac279

Browse files
committed
Add Filter
1 parent 66532db commit b8ac279

File tree

5 files changed

+208
-7
lines changed

5 files changed

+208
-7
lines changed

src/Bundle.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ final public function add(Asset $asset)
112112
/**
113113
* Merges Assets from another Bundle.
114114
*
115-
* @param static $bundle
115+
* @param self $bundle
116116
*
117117
* @return $this
118118
*/

src/Filters/AssetsFilter.php

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php namespace Tatter\Assets\Filters;
2+
3+
use CodeIgniter\Filters\FilterInterface;
4+
use CodeIgniter\HTTP\RedirectResponse;
5+
use CodeIgniter\HTTP\RequestInterface;
6+
use CodeIgniter\HTTP\ResponseInterface;
7+
use Tatter\Assets\RouteBundle;
8+
use InvalidArgumentException;
9+
use RuntimeException;
10+
11+
/**
12+
* Assets Filter
13+
*
14+
* Injects Asset tags for the current route into
15+
* the response body HTML.
16+
*/
17+
class AssetsFilter implements FilterInterface
18+
{
19+
/**
20+
* @codeCoverageIgnore
21+
*/
22+
public function before(RequestInterface $request, $arguments = null)
23+
{
24+
}
25+
26+
/**
27+
* Renders the menus and injects their content.
28+
*
29+
* @param RequestInterface $request
30+
* @param ResponseInterface $response
31+
* @param array|null $arguments
32+
*
33+
* @return ResponseInterface|null
34+
*/
35+
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null): ?ResponseInterface
36+
{
37+
// Ignore irrelevent responses
38+
if ($response instanceof RedirectResponse || empty($response->getBody()))
39+
{
40+
return null;
41+
}
42+
43+
// Check CLI separately for coverage
44+
if (is_cli() && ENVIRONMENT !== 'testing')
45+
{
46+
return null; // @codeCoverageIgnore
47+
}
48+
49+
// Only run on HTML content
50+
if (strpos($response->getHeaderLine('Content-Type'), 'html') === false)
51+
{
52+
return null;
53+
}
54+
55+
$bundle = RouteBundle::createFromRoute(ltrim($request->getPath(), '/ '));
56+
$headTags = $bundle->head();
57+
$bodyTags = $bundle->body();
58+
59+
// Short circuit?
60+
if ($headTags === '' && $bodyTags === '')
61+
{
62+
return null;
63+
}
64+
65+
$body = $response->getBody();
66+
67+
// Add any head content right before the closing head tag
68+
if ($headTags)
69+
{
70+
$body = str_replace('</head>', $headTags . PHP_EOL . '</head>', $body);
71+
}
72+
// Add any body content right before the closing body tag
73+
if ($bodyTags)
74+
{
75+
$body = str_replace('</body>', $bodyTags . PHP_EOL . '</body>', $body);
76+
}
77+
78+
// Use the new body and return the updated Response
79+
return $response->setBody($body);
80+
}
81+
}

src/RouteBundle.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ public static function createFromRoute(string $uri): self
2626

2727
if ($config->useCache)
2828
{
29-
// Get the hash for these items
30-
$key = md5(serialize($items));
29+
// Use the hash of these items for the cache key
30+
$key = 'assets-' . md5(serialize($items));
3131

3232
// If there's a cached version then return it
3333
if ($bundle = cache($key))
@@ -67,6 +67,11 @@ public static function createFromRoute(string $uri): self
6767
}
6868
}
6969

70+
if (isset($key))
71+
{
72+
cache()->save($key, $bundle);
73+
}
74+
7075
return $bundle;
7176
}
7277

@@ -83,7 +88,7 @@ public function __serialize(): array
8388
/**
8489
* Prepares the bundle for caching.
8590
*
86-
* @param Asset[]
91+
* @param Asset[] $data
8792
*/
8893
public function __unserialize(array $data): void
8994
{

tests/FilterTest.php

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
<?php namespace Tests\Support;
2+
3+
use CodeIgniter\HTTP\ResponseInterface;
4+
use CodeIgniter\Test\FilterTestTrait;
5+
use Tatter\Assets\Filters\AssetsFilter;
6+
use Tests\Support\AssetsTestCase;
7+
use Tests\Support\Bundles\FruitSalad;
8+
use Tests\Support\Bundles\LunchBreak;
9+
10+
final class FilterTest extends AssetsTestCase
11+
{
12+
use FilterTestTrait;
13+
14+
/**
15+
* @var string
16+
*/
17+
private $body = <<<EOD
18+
<html>
19+
<head>
20+
<title>Test</title>
21+
</head>
22+
<body>
23+
<h1>Hello</h1>
24+
</body>
25+
</html>
26+
EOD;
27+
28+
protected function setUp(): void
29+
{
30+
parent::setUp();
31+
32+
$this->config->routes = [
33+
'*' => [
34+
'https://pagecdn.io/lib/cleave/1.6.0/cleave.min.js',
35+
FruitSalad::class,
36+
],
37+
'admin/*' => [
38+
LunchBreak::class,
39+
'directory/machines.js',
40+
],
41+
];
42+
43+
$this->response->setBody($this->body);
44+
$this->response->setHeader('Content-Type', 'text/html');
45+
}
46+
47+
public function testFilter()
48+
{
49+
$expected = <<<EOD
50+
<html>
51+
<head>
52+
<title>Test</title>
53+
<link href="http://example.com/assets/apple.css" rel="stylesheet" type="text/css" />
54+
</head>
55+
<body>
56+
<h1>Hello</h1>
57+
<script src="https://pagecdn.io/lib/cleave/1.6.0/cleave.min.js" type="text/javascript"></script>
58+
<script src="http://example.com/assets/banana.js" type="text/javascript"></script>
59+
</body>
60+
</html>
61+
EOD;
62+
63+
$this->request->setPath('foobar');
64+
65+
$caller = $this->getFilterCaller(AssetsFilter::class, 'after');
66+
$result = $caller();
67+
68+
$this->assertInstanceOf(ResponseInterface::class, $result);
69+
$this->assertSame($expected, $result->getBody());
70+
}
71+
72+
public function testEmptyTags()
73+
{
74+
$this->config->routes = [];
75+
76+
$caller = $this->getFilterCaller(AssetsFilter::class, 'after');
77+
78+
$this->assertNull($caller());
79+
}
80+
81+
public function testEmptyBody()
82+
{
83+
$this->response->setBody('');
84+
$caller = $this->getFilterCaller(AssetsFilter::class, 'after');
85+
86+
$this->assertNull($caller());
87+
}
88+
89+
public function testRedirect()
90+
{
91+
$this->response = redirect('');
92+
$caller = $this->getFilterCaller(AssetsFilter::class, 'after');
93+
94+
$this->assertNull($caller());
95+
}
96+
97+
public function testWrongContentType()
98+
{
99+
$this->response->setHeader('Content-Type', 'application/json');
100+
$caller = $this->getFilterCaller(AssetsFilter::class, 'after');
101+
102+
$this->assertNull($caller());
103+
}
104+
}

tests/RouteBundleTest.php

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public function testCreateFromRoute()
4545

4646
public function testCreateFromRouteUsesCache()
4747
{
48-
$key = md5(serialize([
48+
$key = 'assets-' . md5(serialize([
4949
'https://pagecdn.io/lib/cleave/1.6.0/cleave.min.js',
5050
FruitSalad::class,
5151
]));
@@ -54,11 +54,21 @@ public function testCreateFromRouteUsesCache()
5454
$this->assertEmpty(cache()->getCacheInfo());
5555

5656
// Place a fake bundle in the cache
57-
cache()->save($key, $bundle = new RouteBundle());
57+
cache()->save($key, new RouteBundle());
5858

5959
$result = RouteBundle::createFromRoute('foo');
6060

61-
$this->assertSame($bundle, $result);
61+
$this->assertEquals(new RouteBundle(), $result);
62+
}
63+
64+
public function testCreateFromRouteSavesToCache()
65+
{
66+
$this->config->useCache = true;
67+
$this->assertEmpty(cache()->getCacheInfo());
68+
69+
$result = RouteBundle::createFromRoute('foo');
70+
71+
$this->assertNotEmpty(cache()->getCacheInfo());
6272
}
6373

6474
public function testCreateFromRouteEmpty()
@@ -73,6 +83,7 @@ public function testCreateFromRouteEmpty()
7383

7484
public function testCreateFromRouteThrowsNotString()
7585
{
86+
// @phpstan-ignore-next-line
7687
$this->config->routes['invalid'] = [true];
7788

7889
$this->expectException('InvalidArgumentException');

0 commit comments

Comments
 (0)