diff --git a/README.md b/README.md index 1aa5c0c..09975eb 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,34 @@ This PHP 7.1 library is a [PSR-15](https://www.php-fig.org/psr/psr-15/) middleware dedicated to manage static assets like CSS, JS, or image files. +## Usage + +```php +addAssetsDirectory('/path/to/my/first/web-assets-directory'); +$assetsMiddleware->addAssetsDirectory('/path/to/another/web-assets-directory'); + +// optionally you can limit the acceptable media types +$assetsMiddleware->setAllowMediaTypes([ + 'image/*', + 'text/css', + 'application/javascript' +]); + +// returns the computed path to the assets directory +$assetsMiddleware->getAssetUri('/path/to/another/web-assets-directory/an-image.jpg'); + +// processed a PSR-7 server request as a PSR-15 middleware +$assetsMiddleware->process($aPsr7ServerRequest, $aPsr15RequestHandler); // <-- returns a PSR-7 response +``` + ## Installation This library is available through [Packagist](https://packagist.org/packages/codeinc/assets-middleware) and can be installed using [Composer](https://getcomposer.org/): diff --git a/composer.json b/composer.json index 85301cb..cd33a41 100644 --- a/composer.json +++ b/composer.json @@ -1,12 +1,13 @@ { "name": "codeinc/assets-middleware", - "version": "1.2.3", + "version": "2.0.0", "description": "A PSR-15 middleware to server static assets (CSS, JS, images, etc.)", "homepage": "https://github.com/CodeIncHQ/AssetsMiddleware", "type": "library", "license": "MIT", "require": { "php": ">=7.1", + "ext-gettext": "*", "psr/http-message": "^1.0", "psr/http-server-middleware": "^1.0", "psr/http-server-handler": "^1.0", diff --git a/src/AssetsMiddleware.php b/src/AssetsMiddleware.php index 58fb5f2..875cfc9 100644 --- a/src/AssetsMiddleware.php +++ b/src/AssetsMiddleware.php @@ -3,14 +3,14 @@ // +---------------------------------------------------------------------+ // | CODE INC. SOURCE CODE | // +---------------------------------------------------------------------+ -// | Copyright (c) 2017 - Code Inc. SAS - All Rights Reserved. | +// | Copyright (c) 2018 - Code Inc. SAS - All Rights Reserved. | // | Visit https://www.codeinc.fr for more information about licensing. | // +---------------------------------------------------------------------+ // | NOTICE: All information contained herein is, and remains the | // | property of Code Inc. SAS. The intellectual and technical concepts | // | contained herein are proprietary to Code Inc. SAS are protected by | // | trade secret or copyright law. Dissemination of this information or | -// | reproduction of this material is strictly forbidden unless prior | +// | reproduction of this material is strictly forbidden unless prior | // | written permission is obtained from Code Inc. SAS. | // +---------------------------------------------------------------------+ // @@ -21,10 +21,16 @@ // declare(strict_types = 1); namespace CodeInc\AssetsMiddleware; -use CodeInc\AssetsMiddleware\Assets\AssetCompressedResponse; -use CodeInc\AssetsMiddleware\Assets\AssetNotModifiedResponse; -use CodeInc\AssetsMiddleware\Assets\AssetResponse; -use CodeInc\AssetsMiddleware\Test\AssetsMiddlewareTest; +use CodeInc\AssetsMiddleware\Exceptions\InvalidAssetMediaTypeException; +use CodeInc\AssetsMiddleware\Exceptions\InvalidAssetPathException; +use CodeInc\AssetsMiddleware\Exceptions\EmptyDirectoryKeyException; +use CodeInc\AssetsMiddleware\Exceptions\NotADirectoryException; +use CodeInc\AssetsMiddleware\Exceptions\ResponseErrorException; +use CodeInc\AssetsMiddleware\Responses\AssetResponse; +use CodeInc\AssetsMiddleware\Responses\AssetResponseInterface; +use CodeInc\AssetsMiddleware\Responses\MinifiedAssetResponse; +use CodeInc\AssetsMiddleware\Responses\NotModifiedAssetResponse; +use CodeInc\MediaTypes\MediaTypes; use Micheh\Cache\CacheUtil; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -39,166 +45,208 @@ * @author Joan Fabrégat * @license MIT * @link https://github.com/CodeIncHQ/AssetsMiddleware - * @see AssetsMiddlewareTest */ class AssetsMiddleware implements MiddlewareInterface { /** - * @var string + * @var array */ - private $assetsLocalPath; + private $assetsDirectories = []; /** + * Base assets URI path. + * * @var string */ - private $assetsUriPath; + private $assetsUriPrefix; /** + * Allows the assets to the cached in the web browser. + * * @var bool */ - private $allowAssetsCache; + private $cacheAssets; /** + * Allows the assets to be minimized. + * * @var bool */ - private $allowAssetsCompression; + private $minimizeAssets; /** - * AssetsMiddleware constructor. + * Limits the allowed assets media types. * - * @param string $assetsLocalPath - * @param string $assetsUriPath - * @param bool $allowAssetsCache Allows assets cache through HTTP headers - * @param bool $allowAssetsCompression Compresses CSS, JS and SVG files - * @throws AssetsMiddlewareException + * @var null|string[] */ - public function __construct(string $assetsLocalPath, string $assetsUriPath, - bool $allowAssetsCache = true, bool $allowAssetsCompression = false) - { - if (!is_dir($assetsLocalPath) || ($assetsLocalPath = realpath($assetsLocalPath)) === null) { - throw new AssetsMiddlewareException( - sprintf("%s is not a directory and can not be used as assets source", $assetsLocalPath), - $this - ); - } - $this->assetsLocalPath = $assetsLocalPath; - $this->assetsUriPath = $assetsUriPath; - $this->allowAssetsCache = $allowAssetsCache; - $this->allowAssetsCompression = $allowAssetsCompression; - } + private $allowedMediaTypes; /** - * @inheritdoc - * @param ServerRequestInterface $request - * @param RequestHandlerInterface $handler - * @return ResponseInterface - * @throws \CodeInc\MediaTypes\Exceptions\MediaTypesException - * @throws \CodeInc\Psr7Responses\ResponseException + * AssetsMiddleware constructor. + * + * @param string $assetsUriPrefix Base assets URI path + * @param bool $cacheAssets Allows the assets to the cached in the web browser + * @param bool $minimizeAssets Minimizes the assets before sending them (@see AssetCompressedResponse) */ - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler):ResponseInterface + public function __construct(string $assetsUriPrefix, bool $cacheAssets = true, + bool $minimizeAssets = false) { - // if the response points toward a valid asset - if (($assetName = $this->getAssetName($request)) !== null) { - $assetPath = $this->getAssetPath($assetName); - if (file_exists($assetPath)) { - - // builds the response - if (!$this->allowAssetsCompression) { - $response = new AssetResponse($assetPath, $assetName); - } - else { - $response = new AssetCompressedResponse($assetPath, $assetName); - } - - // enables the cache - if ($this->allowAssetsCache) { - $assetMTime = filemtime($assetPath); - $cache = new CacheUtil(); - $response = $cache->withCache($response, true, 3600); - $response = $cache->withETag($response, hash('sha1', (string)$assetMTime)); - $response = $cache->withLastModified($response, $assetMTime); - if ($cache->isNotModified($request, $response)) { - return new AssetNotModifiedResponse($assetName); - } - } - - return $response; - } - } - - // returns the handler response - return $handler->handle($request); + $this->assetsUriPrefix = $assetsUriPrefix; + $this->cacheAssets = $cacheAssets; + $this->minimizeAssets = $minimizeAssets; } /** - * @return string + * Adds an assets directory + * + * @param string $directoryPath + * @param string|null $directoryKey */ - public function getAssetsLocalPath():string + public function addAssetsDirectory(string $directoryPath, string $directoryKey = null):void { - return $this->assetsLocalPath; + if (!is_dir($directoryPath) || ($directoryPath = realpath($directoryPath)) === false) { + throw new NotADirectoryException($directoryPath); + } + if ($directoryKey !== null && empty($directoryKey)) { + throw new EmptyDirectoryKeyException($directoryPath); + } + $this->assetsDirectories[$directoryKey ?? md5($directoryPath)] = $directoryPath; } /** - * @return string + * @inheritdoc + * @return iterable */ - public function getAssetsUriPath():string - { - return $this->assetsUriPath; - } + protected function getAssetsDirectories():iterable + { + return $this->assetsDirectories; + } /** - * Enables the assets cache (enabled by default). + * Sets the allowed media types for the assets. The comparison supports shell patterns with operators + * like *, ?, etc. + * + * @param iterable $allowMediaTypes */ - public function enableAssetsCache():void + public function setAllowMediaTypes(iterable $allowMediaTypes):void { - $this->allowAssetsCache = false; + $this->allowedMediaTypes = ($allowMediaTypes instanceof \Traversable) + ? iterator_to_array($allowMediaTypes) + : $allowMediaTypes; } /** - * Disables the assets cache (enabled by default). + * @inheritdoc + * @param ServerRequestInterface $request + * @param RequestHandlerInterface $handler + * @return AssetResponseInterface */ - public function disableAssetsCache():void + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler):ResponseInterface { - $this->allowAssetsCache = false; + // if the requests points toward an assets directory + if (preg_match('#^'.preg_quote($this->assetsUriPrefix, '#').'([^/]+)/(.+)$#i', + $request->getUri()->getPath(), $matches)) { + + // searching for the corresponding assets directory + foreach ($this->getAssetsDirectories() as $directoryKey => $directoryPath) { + + // if a match is found + if ($matches[1] == $directoryKey) { + if (($realDirectoryPath = realpath($directoryPath)) === false) { + throw new NotADirectoryException($directoryPath); + } + + // validating the assets location + $assetPath = realpath($directoryPath.DIRECTORY_SEPARATOR.$matches[2]); + if ($assetPath && substr($assetPath, 0, strlen($realDirectoryPath)) == $realDirectoryPath) + { + return $this->buildAssetResponse($assetPath, $request); + } + } + } + } + + return $handler->handle($request); } /** - * Returns an asset's name from a request or null if the request does'nt points toward an asset. + * Builds and returns the asset's PSR-7 response. * + * @param string $assetPath * @param ServerRequestInterface $request - * @return null|string + * @return AssetResponseInterface */ - public function getAssetName(ServerRequestInterface $request):?string + private function buildAssetResponse(string $assetPath, ServerRequestInterface $request):AssetResponseInterface { - if (preg_match('#^'.preg_quote($this->assetsUriPath, '#').'([\\w\\-_./]+)$#ui', - $request->getUri()->getPath(), $matches)) { - return $matches[1]; + try { + // reading the assets media type + $assetMediaType = MediaTypes::getFilenameMediaType($assetPath); + + // checking the asset's media type + if (!$this->isMediaTypeAllowed($assetMediaType)) { + throw new InvalidAssetMediaTypeException($assetPath, $assetMediaType); + } + + // building the response + $response = $this->minimizeAssets + ? new MinifiedAssetResponse($assetPath, $assetMediaType) : + new AssetResponse($assetPath, $assetMediaType); + + // enabling cache + if ($this->cacheAssets) { + $assetMTime = filemtime($assetPath); + $cache = new CacheUtil(); + $response = $cache->withCache($response, true, 3600); + $response = $cache->withETag($response, hash('sha1', (string)$assetMTime)); + $response = $cache->withLastModified($response, $assetMTime); + if ($cache->isNotModified($request, $response)) { + $response = new NotModifiedAssetResponse($assetPath); + } + return $response; + } + return $response; + } + catch (\Throwable $exception) { + throw new ResponseErrorException($assetPath, 0, $exception); } - return null; } /** - * Returns an asset's path. + * Verifies if the assets media type is supported. * - * @param string $assetName - * @return string + * @param string $assetMediaType + * @return bool */ - public function getAssetPath(string $assetName):string + protected function isMediaTypeAllowed(string $assetMediaType):bool { - if (substr($assetName, 0, strlen(DIRECTORY_SEPARATOR)) == DIRECTORY_SEPARATOR) { - $assetName = substr($assetName, strlen(DIRECTORY_SEPARATOR)); + if (is_array($this->allowedMediaTypes) && !empty($this->allowedMediaTypes)) { + foreach ($this->allowedMediaTypes as $mediaType) { + if (strcasecmp($assetMediaType, $mediaType) === 0 || fnmatch($mediaType, $assetMediaType)) { + return true; + } + } + return false; } - return $this->assetsLocalPath.DIRECTORY_SEPARATOR.$assetName; + return true; } /** - * Returns an assets URI path. + * Returns the public URI for a given asset. The asset must be within a registered assets directory. * - * @param string $asset + * @param string $assetPath * @return string */ - public function getAssetUriPath(string $asset):string + public function getAssetUri(string $assetPath):?string { - return $this->assetsUriPath.$asset; + if (($realAssetPath = realpath($assetPath)) === false) { + throw new InvalidAssetPathException($assetPath); + } + foreach ($this->getAssetsDirectories() as $directoryKey => $directoryPath) { + if (substr($assetPath, 0, strlen($directoryPath)) == $directoryPath) { + return $this->assetsUriPrefix.urlencode($directoryKey) + .str_replace('\\', '/', substr($assetPath, strlen($directoryPath))); + } + } + return null; } } \ No newline at end of file diff --git a/src/Exceptions/AssetsMiddlewareException.php b/src/Exceptions/AssetsMiddlewareException.php new file mode 100644 index 0000000..9bc759e --- /dev/null +++ b/src/Exceptions/AssetsMiddlewareException.php @@ -0,0 +1,33 @@ + +// Date: 28/09/2018 +// Project: AssetsMiddleware +// +declare(strict_types=1); +namespace CodeInc\AssetsMiddleware\Exceptions; + +/** + * Interface AssetsMiddlewareException + * + * @package CodeInc\AssetsMiddleware\Exceptions + * @author Joan Fabrégat + */ +interface AssetsMiddlewareException +{ + +} \ No newline at end of file diff --git a/src/Exceptions/EmptyDirectoryKeyException.php b/src/Exceptions/EmptyDirectoryKeyException.php new file mode 100644 index 0000000..22a5e32 --- /dev/null +++ b/src/Exceptions/EmptyDirectoryKeyException.php @@ -0,0 +1,63 @@ + +// Date: 28/09/2018 +// Project: AssetsMiddleware +// +declare(strict_types=1); +namespace CodeInc\AssetsMiddleware\Exceptions; +use Throwable; + + +/** + * Class EmptyDirectoryKeyException + * + * @package CodeInc\AssetsMiddleware\Exceptions + * @author Joan Fabrégat + */ +class EmptyDirectoryKeyException extends \LogicException implements AssetsMiddlewareException +{ + /** + * @var string + */ + private $directoryPath; + + /** + * EmptyDirectoryKeyException constructor. + * + * @param string $directoryPath + * @param int $code + * @param Throwable|null $previous + */ + public function __construct(string $directoryPath, int $code = 0, Throwable $previous = null) + { + $this->directoryPath = $directoryPath; + parent::__construct( + sprintf("The key of the directory '%s' can not empty.", $directoryPath), + $code, + $previous + ); + } + + /** + * @return string + */ + public function getDirectoryPath():string + { + return $this->directoryPath; + } +} \ No newline at end of file diff --git a/src/Exceptions/InvalidAssetMediaTypeException.php b/src/Exceptions/InvalidAssetMediaTypeException.php new file mode 100644 index 0000000..e4843cb --- /dev/null +++ b/src/Exceptions/InvalidAssetMediaTypeException.php @@ -0,0 +1,78 @@ + +// Date: 04/10/2018 +// Project: AssetsMiddleware +// +declare(strict_types=1); +namespace CodeInc\AssetsMiddleware\Exceptions; +use Throwable; + + +/** + * Class InvalidAssetMediaType + * + * @package CodeInc\AssetsMiddleware\Exceptions + * @author Joan Fabrégat + */ +class InvalidAssetMediaTypeException extends \RuntimeException implements AssetsMiddlewareException +{ + /** + * @var string + */ + private $assetPath; + + /** + * @var string + */ + private $mediaType; + + /** + * InvalidAssetMediaTypeException constructor. + * + * @param string $assetPath + * @param string $mediaType + * @param int $code + * @param Throwable|null $previous + */ + public function __construct(string $assetPath, string $mediaType, int $code = 0, Throwable $previous = null) + { + $this->assetPath = $assetPath; + $this->mediaType = $mediaType; + parent::__construct( + sprintf("The media type '%s' of the asset '%s' is not allowed.", $mediaType, $assetPath), + $code, + $previous + ); + } + + /** + * @return string + */ + public function getAssetPath():string + { + return $this->assetPath; + } + + /** + * @return string + */ + public function getMediaType():string + { + return $this->mediaType; + } +} \ No newline at end of file diff --git a/src/AssetsMiddlewareException.php b/src/Exceptions/InvalidAssetPathException.php similarity index 55% rename from src/AssetsMiddlewareException.php rename to src/Exceptions/InvalidAssetPathException.php index 600f40f..a32e743 100644 --- a/src/AssetsMiddlewareException.php +++ b/src/Exceptions/InvalidAssetPathException.php @@ -3,61 +3,61 @@ // +---------------------------------------------------------------------+ // | CODE INC. SOURCE CODE | // +---------------------------------------------------------------------+ -// | Copyright (c) 2017 - Code Inc. SAS - All Rights Reserved. | +// | Copyright (c) 2018 - Code Inc. SAS - All Rights Reserved. | // | Visit https://www.codeinc.fr for more information about licensing. | // +---------------------------------------------------------------------+ // | NOTICE: All information contained herein is, and remains the | // | property of Code Inc. SAS. The intellectual and technical concepts | // | contained herein are proprietary to Code Inc. SAS are protected by | // | trade secret or copyright law. Dissemination of this information or | -// | reproduction of this material is strictly forbidden unless prior | +// | reproduction of this material is strictly forbidden unless prior | // | written permission is obtained from Code Inc. SAS. | // +---------------------------------------------------------------------+ // // Author: Joan Fabrégat -// Date: 03/05/2018 -// Time: 16:15 +// Date: 28/09/2018 // Project: AssetsMiddleware // declare(strict_types=1); -namespace CodeInc\AssetsMiddleware; -use RuntimeException; +namespace CodeInc\AssetsMiddleware\Exceptions; use Throwable; /** - * Class AssetsMiddlewareException + * Class InvalidAssetPathException * - * @package CodeInc\AssetsMiddleware + * @package CodeInc\AssetsMiddleware\Exceptions * @author Joan Fabrégat */ -class AssetsMiddlewareException extends RuntimeException +class InvalidAssetPathException extends \RuntimeException implements AssetsMiddlewareException { /** - * @var AssetsMiddleware + * @var string */ - private $assetsMiddleware; + private $assetPath; /** - * AssetsMiddlewareException constructor. + * InvalidAssetPathException constructor. * - * @param string $message - * @param AssetsMiddleware $assetsMiddleware + * @param string $assetPath * @param int $code * @param Throwable|null $previous */ - public function __construct(string $message, AssetsMiddleware $assetsMiddleware, - int $code = 0, Throwable $previous = null) + public function __construct(string $assetPath, int $code = 0, Throwable $previous = null) { - $this->assetsMiddleware = $assetsMiddleware; - parent::__construct($message, $code, $previous); + $this->assetPath = $assetPath; + parent::__construct( + sprintf("The asset path '%s' is not valid.", $assetPath), + $code, + $previous + ); } /** - * @return AssetsMiddleware + * @return string */ - public function getAssetsMiddleware():AssetsMiddleware + public function getAssetPath():string { - return $this->assetsMiddleware; + return $this->assetPath; } } \ No newline at end of file diff --git a/src/Exceptions/NotADirectoryException.php b/src/Exceptions/NotADirectoryException.php new file mode 100644 index 0000000..c7302de --- /dev/null +++ b/src/Exceptions/NotADirectoryException.php @@ -0,0 +1,63 @@ + +// Date: 28/09/2018 +// Project: AssetsMiddleware +// +declare(strict_types=1); +namespace CodeInc\AssetsMiddleware\Exceptions; +use Throwable; + + +/** + * Class NotADirectoryException + * + * @package CodeInc\AssetsMiddleware\Exceptions + * @author Joan Fabrégat + */ +class NotADirectoryException extends \LogicException implements AssetsMiddlewareException +{ + /** + * @var string + */ + private $path; + + /** + * NotADirectoryException constructor. + * + * @param string $path + * @param int $code + * @param Throwable|null $previous + */ + public function __construct(string $path, int $code = 0, Throwable $previous = null) + { + $this->path = $path; + parent::__construct( + sprintf("The assets path '%s' is not a directory or does not exist.", $path), + $code, + $previous + ); + } + + /** + * @return string + */ + public function getPath():string + { + return $this->path; + } +} \ No newline at end of file diff --git a/src/Exceptions/ResponseErrorException.php b/src/Exceptions/ResponseErrorException.php new file mode 100644 index 0000000..d30ee3e --- /dev/null +++ b/src/Exceptions/ResponseErrorException.php @@ -0,0 +1,63 @@ + +// Date: 28/09/2018 +// Project: AssetsMiddleware +// +declare(strict_types=1); +namespace CodeInc\AssetsMiddleware\Exceptions; +use Throwable; + + +/** + * Class ResponseErrorException + * + * @package CodeInc\AssetsMiddleware\Exceptions + * @author Joan Fabrégat + */ +class ResponseErrorException extends \RuntimeException implements AssetsMiddlewareException +{ + /** + * @var string + */ + private $assetPath; + + /** + * ResponseErrorException constructor. + * + * @param string $assetPath + * @param int $code + * @param Throwable|null $previous + */ + public function __construct(string $assetPath, int $code = 0, Throwable $previous = null) + { + $this->assetPath = $assetPath; + parent::__construct( + sprintf("Error while building the PSR-7 response for the asset '%s'.", $assetPath), + $code, + $previous + ); + } + + /** + * @return string + */ + public function getAssetPath():string + { + return $this->assetPath; + } +} \ No newline at end of file diff --git a/src/Assets/AssetResponse.php b/src/Responses/AssetResponse.php similarity index 62% rename from src/Assets/AssetResponse.php rename to src/Responses/AssetResponse.php index f910a6a..4bedaae 100644 --- a/src/Assets/AssetResponse.php +++ b/src/Responses/AssetResponse.php @@ -20,14 +20,14 @@ // Project: AssetsMiddleware // declare(strict_types=1); -namespace CodeInc\AssetsMiddleware\Assets; +namespace CodeInc\AssetsMiddleware\Responses; use CodeInc\Psr7Responses\FileResponse; /** * Class AssetResponse * - * @package CodeInc\AssetsMiddleware\Assets + * @package CodeInc\AssetsMiddleware\Responses * @author Joan Fabrégat */ class AssetResponse extends FileResponse implements AssetResponseInterface @@ -35,37 +35,28 @@ class AssetResponse extends FileResponse implements AssetResponseInterface /** * @var string */ - private $assetName; + private $assetPath; /** * AssetResponse constructor. * - * @param string $filePath - * @param string $assetName - * @param null|string $fileName - * @param null|string $mimeType - * @param bool $asAttachment - * @param int $status - * @param array $headers - * @param string $version - * @param null|string $reason + * @param string $assetPath + * @param string $mediaType * @throws \CodeInc\MediaTypes\Exceptions\MediaTypesException - * @throws \CodeInc\Psr7Responses\ResponseException */ - public function __construct(string $filePath, string $assetName, ?string $fileName = null, - ?string $mimeType = null, bool $asAttachment = false, int $status = 200, array $headers = [], - string $version = '1.1', ?string $reason = null) + public function __construct(string $assetPath, string $mediaType) { - $this->assetName = $assetName; - parent::__construct($filePath, $fileName, $mimeType, $asAttachment, $status, $headers, $version, $reason); + $this->assetPath = $assetPath; + parent::__construct($assetPath, basename($assetPath), $mediaType, false); } + /** * @inheritdoc * @return string */ - public function getAssetName():string + public function getAssetPath():string { - return $this->assetName; + return $this->assetPath; } } \ No newline at end of file diff --git a/src/Assets/AssetResponseInterface.php b/src/Responses/AssetResponseInterface.php similarity index 77% rename from src/Assets/AssetResponseInterface.php rename to src/Responses/AssetResponseInterface.php index 668f066..71f8347 100644 --- a/src/Assets/AssetResponseInterface.php +++ b/src/Responses/AssetResponseInterface.php @@ -3,39 +3,38 @@ // +---------------------------------------------------------------------+ // | CODE INC. SOURCE CODE | // +---------------------------------------------------------------------+ -// | Copyright (c) 2017 - Code Inc. SAS - All Rights Reserved. | +// | Copyright (c) 2018 - Code Inc. SAS - All Rights Reserved. | // | Visit https://www.codeinc.fr for more information about licensing. | // +---------------------------------------------------------------------+ // | NOTICE: All information contained herein is, and remains the | // | property of Code Inc. SAS. The intellectual and technical concepts | // | contained herein are proprietary to Code Inc. SAS are protected by | // | trade secret or copyright law. Dissemination of this information or | -// | reproduction of this material is strictly forbidden unless prior | +// | reproduction of this material is strictly forbidden unless prior | // | written permission is obtained from Code Inc. SAS. | // +---------------------------------------------------------------------+ // // Author: Joan Fabrégat -// Date: 03/05/2018 -// Time: 17:15 +// Date: 14/09/2018 // Project: AssetsMiddleware // declare(strict_types=1); -namespace CodeInc\AssetsMiddleware\Assets; +namespace CodeInc\AssetsMiddleware\Responses; use Psr\Http\Message\ResponseInterface; /** * Interface AssetResponseInterface * - * @package CodeInc\AssetsMiddleware\Assets + * @package CodeInc\AssetsMiddleware\Responses * @author Joan Fabrégat */ interface AssetResponseInterface extends ResponseInterface { /** - * Returns the name of the asset. + * Returns the asset's path. * * @return string */ - public function getAssetName():string; + public function getAssetPath():string; } \ No newline at end of file diff --git a/src/Assets/AssetCompressedResponse.php b/src/Responses/MinifiedAssetResponse.php similarity index 63% rename from src/Assets/AssetCompressedResponse.php rename to src/Responses/MinifiedAssetResponse.php index 054045c..62386d7 100644 --- a/src/Assets/AssetCompressedResponse.php +++ b/src/Responses/MinifiedAssetResponse.php @@ -20,9 +20,9 @@ // Project: AssetsMiddleware // declare(strict_types=1); -namespace CodeInc\AssetsMiddleware\Assets; +namespace CodeInc\AssetsMiddleware\Responses; use CodeInc\MediaTypes\MediaTypes; -use CodeInc\Psr7Responses\StreamResponse; +use CodeInc\Psr7Responses\FileResponse; use enshrined\svgSanitize\Sanitizer; use function GuzzleHttp\Psr7\stream_for; use MatthiasMullie\Minify; @@ -31,66 +31,44 @@ /** - * Class AssetCompressedResponse + * Class MinifiedAssetResponse * - * @package CodeInc\AssetsMiddleware\Assets + * @package CodeInc\AssetsMiddleware\Responses * @author Joan Fabrégat */ -class AssetCompressedResponse extends StreamResponse implements AssetResponseInterface +class MinifiedAssetResponse extends FileResponse implements AssetResponseInterface { /** * @var string */ - private $assetName; + private $assetPath; /** - * AssetCompressedResponse constructor. + * @var string + */ + private $mediaType; + + /** + * MinifiedAssetResponse constructor. * - * @param string $filePath - * @param string $assetName - * @param null|string $fileName - * @param null|string $mimeType - * @param bool $asAttachment - * @param int $status - * @param array $headers - * @param string $version - * @param null|string $reason + * @param string $assetPath + * @param string $mediaType * @throws \CodeInc\MediaTypes\Exceptions\MediaTypesException */ - public function __construct(string $filePath, string $assetName, ?string $fileName = null, ?string $mimeType = null, - bool $asAttachment = false, int $status = 200, array $headers = [], - string $version = '1.1', ?string $reason = null) + public function __construct(string $assetPath, string $mediaType) { - $this->assetName = $assetName; - - if (!$fileName) { - $fileName = basename($assetName); - } - if (!$mimeType) { - $mimeType = MediaTypes::getFilenameMediaType($fileName); - } - - parent::__construct( - $this->buildStream($mimeType, $filePath), - $mimeType, - null, - $fileName, - $asAttachment, - $status, - $headers, - $version, - $reason - ); + $this->assetPath = $assetPath; + $this->mediaType = $mediaType; + parent::__construct($this->buildStream($assetPath), basename($assetPath), $mediaType, false); } /** - * @param string $mimeType * @param string $filePath * @return StreamInterface */ - private function buildStream(string $mimeType, string $filePath):StreamInterface + private function buildStream(string $filePath):StreamInterface { - switch ($mimeType) { + switch ($this->mediaType) { case 'text/css': $css = new Minify\CSS($filePath); $css->setImportExtensions([]); @@ -123,11 +101,12 @@ private function buildStream(string $mimeType, string $filePath):StreamInterface } /** - * @inheritdoc + * Returns the asset's path. + * * @return string */ - public function getAssetName():string + public function getAssetPath():string { - return $this->assetName; + return $this->assetPath; } } \ No newline at end of file diff --git a/src/Assets/AssetNotModifiedResponse.php b/src/Responses/NotModifiedAssetResponse.php similarity index 67% rename from src/Assets/AssetNotModifiedResponse.php rename to src/Responses/NotModifiedAssetResponse.php index f9b5d6c..7f495c7 100644 --- a/src/Assets/AssetNotModifiedResponse.php +++ b/src/Responses/NotModifiedAssetResponse.php @@ -20,46 +20,40 @@ // Project: AssetsMiddleware // declare(strict_types=1); -namespace CodeInc\AssetsMiddleware\Assets; +namespace CodeInc\AssetsMiddleware\Responses; use GuzzleHttp\Psr7\Response; /** * Class AssetNotModifiedResponse * - * @package CodeInc\AssetsMiddleware\Assets + * @package CodeInc\AssetsMiddleware\Responses * @author Joan Fabrégat */ -class AssetNotModifiedResponse extends Response implements AssetResponseInterface +class NotModifiedAssetResponse extends Response implements AssetResponseInterface { /** * @var string */ - private $assetName; + private $assetPath; /** * AssetNotModifiedResponse constructor. * - * @param string $assetName - * @param int $status - * @param array $headers - * @param null $body - * @param string $version - * @param null|string $reason + * @param string $assetPath */ - public function __construct(string $assetName, int $status = 304, array $headers = [], $body = null, - string $version = '1.1', ?string $reason = null) + public function __construct(string $assetPath) { - $this->assetName = $assetName; - parent::__construct($status, $headers, $body, $version, $reason); + $this->assetPath = $assetPath; + parent::__construct(304); } /** * @inheritdoc * @return string */ - public function getAssetName():string + public function getAssetPath():string { - return $this->assetName; + return $this->assetPath; } } \ No newline at end of file diff --git a/tests/AssetsMiddlewareTest.php b/tests/AssetsMiddlewareTest.php index a283dd8..b4bdaed 100644 --- a/tests/AssetsMiddlewareTest.php +++ b/tests/AssetsMiddlewareTest.php @@ -21,8 +21,8 @@ // declare(strict_types=1); namespace CodeInc\AssetsMiddleware\Test; -use CodeInc\AssetsMiddleware\Assets\AssetNotModifiedResponse; -use CodeInc\AssetsMiddleware\Assets\AssetResponseInterface; +use CodeInc\AssetsMiddleware\Responses\AssetResponseInterface; +use CodeInc\AssetsMiddleware\Responses\NotModifiedAssetResponse; use CodeInc\AssetsMiddleware\AssetsMiddleware; use CodeInc\MiddlewareTestKit\FakeRequestHandler; use CodeInc\MiddlewareTestKit\FakeServerRequest; @@ -46,16 +46,12 @@ final class AssetsMiddlewareTest extends TestCase ]; /** - * @throws \CodeInc\AssetsMiddleware\AssetsMiddlewareException * @throws \CodeInc\MediaTypes\Exceptions\MediaTypesException - * @throws \CodeInc\Psr7Responses\ResponseException */ public function testAssets():void { - $middleware = new AssetsMiddleware( - __DIR__ . '/Assets', - '/assets/v2/' - ); + $middleware = new AssetsMiddleware(__DIR__ . '/Assets', true); + $middleware->addAssetsDirectory('/assets/v2/'); foreach (self::ASSETS as $path => $type) { self::assertFileExists($path); @@ -69,7 +65,6 @@ public function testAssets():void self::assertInstanceOf(ResponseInterface::class, $response); self::assertInstanceOf(AssetResponseInterface::class, $response); - self::assertEquals($response->getAssetName(), basename($path)); self::assertEquals($type, $response->getHeaderLine('Content-Type')); self::assertNotEmpty($response->getHeaderLine('Cache-Control')); self::assertNotEmpty($response->getHeaderLine('ETag')); @@ -79,17 +74,11 @@ public function testAssets():void } /** - * @throws \CodeInc\AssetsMiddleware\AssetsMiddlewareException - * @throws \CodeInc\MediaTypes\Exceptions\MediaTypesException - * @throws \CodeInc\Psr7Responses\ResponseException */ public function testUncachedAssets():void { - $middleware = new AssetsMiddleware( - __DIR__ . '/Assets', - '/assets/v2/' - ); - $middleware->disableAssetsCache(); + $middleware = new AssetsMiddleware(__DIR__ . '/Assets', false); + $middleware->registerAssetsDirectory('/assets/v2/'); foreach (self::ASSETS as $path => $type) { self::assertFileExists($path); @@ -110,16 +99,11 @@ public function testUncachedAssets():void } /** - * @throws \CodeInc\AssetsMiddleware\AssetsMiddlewareException - * @throws \CodeInc\MediaTypes\Exceptions\MediaTypesException - * @throws \CodeInc\Psr7Responses\ResponseException */ public function testNotFoundAsset():void { - $middleware = new AssetsMiddleware( - __DIR__ . '/Assets', - '/assets/v2/' - ); + $middleware = new AssetsMiddleware(__DIR__ . '/Assets'); + $middleware->registerAssetsDirectory('/assets/v2/'); $response = $middleware->process( FakeServerRequest::getSecureServerRequestWithPath('/assets/v2/a-not-found-asset.bin'), new FakeRequestHandler() @@ -129,16 +113,11 @@ public function testNotFoundAsset():void } /** - * @throws \CodeInc\AssetsMiddleware\AssetsMiddlewareException - * @throws \CodeInc\MediaTypes\Exceptions\MediaTypesException - * @throws \CodeInc\Psr7Responses\ResponseException */ public function testNonAssetRequest():void { - $middleware = new AssetsMiddleware( - __DIR__ . '/Assets', - '/assets/v2/' - ); + $middleware = new AssetsMiddleware(__DIR__ . '/Assets'); + $middleware->registerAssetsDirectory('/assets/v2/'); $response = $middleware->process( FakeServerRequest::getSecureServerRequestWithPath('/a-page.html'), new FakeRequestHandler() @@ -148,16 +127,11 @@ public function testNonAssetRequest():void } /** - * @throws \CodeInc\AssetsMiddleware\AssetsMiddlewareException - * @throws \CodeInc\MediaTypes\Exceptions\MediaTypesException - * @throws \CodeInc\Psr7Responses\ResponseException */ public function testDateCacheAsset():void { - $middleware = new AssetsMiddleware( - __DIR__ . '/Assets', - '/assets/' - ); + $middleware = new AssetsMiddleware(__DIR__ . '/Assets'); + $middleware->registerAssetsDirectory('/assets/'); $request = FakeServerRequest::getSecureServerRequestWithPath('/assets/image.svg') ->withHeader('If-Modified-Since', date('D, d M Y H:i:s \G\M\T')); @@ -166,21 +140,16 @@ public function testDateCacheAsset():void $response = $middleware->process($request, new FakeRequestHandler()); self::assertInstanceOf(ResponseInterface::class, $response); - self::assertInstanceOf(AssetNotModifiedResponse::class, $response); - self::assertEquals($response->getAssetName(), 'image.svg'); + self::assertInstanceOf(NotModifiedAssetResponse::class, $response); + self::assertEquals(basename($response->getAssetPath()), 'image.svg'); } /** - * @throws \CodeInc\AssetsMiddleware\AssetsMiddlewareException - * @throws \CodeInc\MediaTypes\Exceptions\MediaTypesException - * @throws \CodeInc\Psr7Responses\ResponseException */ public function testEtagCacheAsset():void { - $middleware = new AssetsMiddleware( - __DIR__ . '/Assets', - '/assets/' - ); + $middleware = new AssetsMiddleware(__DIR__ . '/Assets'); + $middleware->registerAssetsDirectory('/assets/'); $request = FakeServerRequest::getSecureServerRequestWithPath('/assets/image.svg') ->withHeader('If-None-Match', '"9fda03907099301a7e94f69f6502b3f3805bf1c3"'); @@ -189,7 +158,7 @@ public function testEtagCacheAsset():void $response = $middleware->process($request, new FakeRequestHandler()); self::assertInstanceOf(ResponseInterface::class, $response); - self::assertInstanceOf(AssetNotModifiedResponse::class, $response); - self::assertEquals($response->getAssetName(), 'image.svg'); + self::assertInstanceOf(NotModifiedAssetResponse::class, $response); + self::assertEquals(basename($response->getAssetPath()), 'image.svg'); } } \ No newline at end of file