Initial commit: apps.supersam24.ru
This commit is contained in:
commit
e955e1da36
6566 files changed
+1271095
No files matched your search
@@ -0,0 +1,4 @@
|
||||
/vendor/
|
||||
*.cache
|
||||
composer.lock
|
||||
phpunit.xml
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
$header = <<<EOF
|
||||
AJGL Breakpoint Twig Extension Component
|
||||
|
||||
Copyright (C) Antonio J. García Lagar <aj@garcialagar.es>
|
||||
|
||||
For the full copyright and license information, please view the LICENSE
|
||||
file that was distributed with this source code.
|
||||
EOF;
|
||||
|
||||
return \PhpCsFixer\Config::create()
|
||||
->setRiskyAllowed(true)
|
||||
->setRules(
|
||||
[
|
||||
'@Symfony' => true,
|
||||
'@Symfony:risky' => true,
|
||||
'array_syntax' => array('syntax' => 'short'),
|
||||
'fully_qualified_strict_types' => true,
|
||||
'header_comment' => array('header' => $header),
|
||||
'native_function_invocation' => false,
|
||||
'ordered_imports' => [
|
||||
'imports_order' => ['class', 'const', 'function'],
|
||||
],
|
||||
'phpdoc_order' => true,
|
||||
'psr4' => true,
|
||||
'strict_comparison' => true,
|
||||
'strict_param' => true,
|
||||
]
|
||||
)
|
||||
->setFinder(
|
||||
\PhpCsFixer\Finder::create()
|
||||
->in(__DIR__.'/src')
|
||||
->in(__DIR__.'/tests')
|
||||
)
|
||||
;
|
||||
@@ -0,0 +1,7 @@
|
||||
# .scrutinizer.yml
|
||||
checks:
|
||||
php: true
|
||||
|
||||
tools:
|
||||
external_code_coverage:
|
||||
timeout: 900
|
||||
@@ -0,0 +1,68 @@
|
||||
language: php
|
||||
sudo: false
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.composer/cache/files
|
||||
- $HOME/symfony-bridge/.phpunit
|
||||
|
||||
env:
|
||||
global:
|
||||
- PHPUNIT_FLAGS="-v"
|
||||
- SYMFONY_PHPUNIT_DIR="$HOME/symfony-bridge/.phpunit"
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
include:
|
||||
# Minimum supported dependencies with the latest and oldest PHP version
|
||||
- php: 7.3
|
||||
env: COMPOSER_FLAGS="--prefer-stable --prefer-lowest" SYMFONY_DEPRECATIONS_HELPER="weak_vendors"
|
||||
- php: 5.6
|
||||
env: COMPOSER_FLAGS="--prefer-stable --prefer-lowest" SYMFONY_DEPRECATIONS_HELPER="weak_vendors"
|
||||
|
||||
# Test the latest stable release
|
||||
- php: 5.6
|
||||
- php: 7.0
|
||||
- php: 7.1
|
||||
- php: 7.2
|
||||
- php: 7.3
|
||||
env: COVERAGE=true PHPUNIT_FLAGS="-v --coverage-text" LINT=true
|
||||
|
||||
# Test LTS versions. This makes sure we do not use Symfony packages with version greater
|
||||
# than 2 or 3 respectively. Read more at https://github.com/symfony/lts
|
||||
- php: 7.3
|
||||
env: DEPENDENCIES="symfony/lts:^2"
|
||||
- php: 7.3
|
||||
env: DEPENDENCIES="symfony/lts:^3"
|
||||
|
||||
# Latest commit to master
|
||||
- php: 7.3
|
||||
env: STABILITY="dev"
|
||||
- php: 7.4
|
||||
env: STABILITY="dev"
|
||||
- php: nightly
|
||||
env: STABILITY="dev"
|
||||
|
||||
allow_failures:
|
||||
# Dev-master is allowed to fail.
|
||||
- env: STABILITY="dev"
|
||||
|
||||
before_install:
|
||||
- if [[ $COVERAGE != true ]]; then phpenv config-rm xdebug.ini || true; fi
|
||||
- if ! [ -z "$STABILITY" ]; then composer config minimum-stability ${STABILITY}; fi;
|
||||
- if ! [ -v "$DEPENDENCIES" ]; then composer require --no-update ${DEPENDENCIES}; fi;
|
||||
- if [[ $LINT = true ]]; then wget https://cs.symfony.com/download/php-cs-fixer-v2.phar; fi
|
||||
|
||||
install:
|
||||
- composer update ${COMPOSER_FLAGS} --prefer-dist --no-interaction
|
||||
- ./vendor/bin/simple-phpunit install
|
||||
|
||||
script:
|
||||
- composer validate --strict --no-check-lock
|
||||
- if [[ $LINT = true ]]; then php php-cs-fixer-v2.phar fix --dry-run --diff --no-ansi; fi
|
||||
# simple-phpunit is the PHPUnit wrapper provided by the PHPUnit Bridge component and
|
||||
# it helps with testing legacy code and deprecations (composer require symfony/phpunit-bridge)
|
||||
- ./vendor/bin/simple-phpunit $PHPUNIT_FLAGS
|
||||
|
||||
after_script:
|
||||
- wget https://scrutinizer-ci.com/ocular.phar
|
||||
- php ocular.phar code-coverage:upload --format=php-clover coverage.clover
|
||||
@@ -0,0 +1,60 @@
|
||||
# [CHANGELOG](http://keepachangelog.com/)
|
||||
All notable changes to this project will be documented in this file.
|
||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [Unreleased][unreleased]
|
||||
|
||||
|
||||
## [0.3.4] - 2019-04-10
|
||||
|
||||
### Fixed
|
||||
- Population of `$arguments` variable
|
||||
|
||||
|
||||
## [0.3.3] - 2019-03-18
|
||||
|
||||
### Fixed
|
||||
- Population of `$arguments` variable
|
||||
- Usage of deprecated classes
|
||||
|
||||
|
||||
## [0.3.2] - 2018-12-10
|
||||
|
||||
### Added
|
||||
- Add support for Symfony 4
|
||||
|
||||
## [0.3.1] - 2017-11-20
|
||||
|
||||
### Changed
|
||||
- Require PHP 5.6
|
||||
|
||||
### Fixed
|
||||
- Update requirements to allow twig 2.x
|
||||
|
||||
|
||||
## [0.3.0] - 2016-03-31
|
||||
|
||||
### Added
|
||||
- Add `$environment` and `$context` variables
|
||||
- Allow to inspect function arguments
|
||||
|
||||
|
||||
## [0.2.0] - 2016-03-10
|
||||
|
||||
### Added
|
||||
- Add Symfony Bundle
|
||||
|
||||
|
||||
## 0.1.0 - 2016-03-09
|
||||
|
||||
### Added
|
||||
- Add `breakpoint` function
|
||||
|
||||
|
||||
[unreleased]: https://github.com/ajgarlag/AjglBreakpointTwigExtension/compare/0.3.4...master
|
||||
[0.3.4]: https://github.com/ajgarlag/AjglBreakpointTwigExtension/compare/0.3.3...0.3.4
|
||||
[0.3.3]: https://github.com/ajgarlag/AjglBreakpointTwigExtension/compare/0.3.2...0.3.3
|
||||
[0.3.2]: https://github.com/ajgarlag/AjglBreakpointTwigExtension/compare/0.3.1...0.3.2
|
||||
[0.3.1]: https://github.com/ajgarlag/AjglBreakpointTwigExtension/compare/0.3.0...0.3.1
|
||||
[0.3.0]: https://github.com/ajgarlag/AjglBreakpointTwigExtension/compare/0.2.0...0.3.0
|
||||
[0.2.0]: https://github.com/ajgarlag/AjglBreakpointTwigExtension/compare/0.1.0...0.2.0
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
Copyright (c) 2013-2016 Antonio J. García Lagar <aj@garcialagar.es>
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the
|
||||
Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions of
|
||||
the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
|
||||
KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
||||
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
|
||||
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
AjglBreakpointTwigExtension
|
||||
===========================
|
||||
|
||||
The AjglBreakpointTwigExtension component allows you set breakpoints in twig templates.
|
||||
|
||||
[](https://travis-ci.org/ajgarlag/AjglBreakpointTwigExtension)
|
||||
[](https://packagist.org/packages/ajgl/breakpoint-twig-extension)
|
||||
[](https://packagist.org/packages/ajgl/breakpoint-twig-extension)
|
||||
[](https://packagist.org/packages/ajgl/breakpoint-twig-extension)
|
||||
[](https://packagist.org/packages/ajgl/breakpoint-twig-extension)
|
||||
[](https://packagist.org/packages/ajgl/breakpoint-twig-extension)
|
||||
[](https://packagist.org/packages/ajgl/breakpoint-twig-extension)
|
||||
[](https://scrutinizer-ci.com/g/ajgarlag/AjglBreakpointTwigExtension/?branch=master)
|
||||
[](https://scrutinizer-ci.com/g/ajgarlag/AjglBreakpointTwigExtension/?branch=master)
|
||||
[](https://insight.sensiolabs.com/projects/e0f1276d-6ded-4a20-9b3f-1a7c77a92015)
|
||||
|
||||
This component requires the [Xdebug] PHP extension to be installed.
|
||||
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
To install the latest stable version of this component, open a console and execute the following command:
|
||||
```
|
||||
$ composer require ajgl/breakpoint-twig-extension
|
||||
```
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
The first step is to register the extension into the twig environment
|
||||
```php
|
||||
/* @var $twig Twig_Environment */
|
||||
$twig->addExtension(new Ajgl\Twig\Extension\BreakpointExtension());
|
||||
```
|
||||
|
||||
Once registered, you can call the new `breakpoint` function:
|
||||
```twig
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>title</title>
|
||||
</head>
|
||||
<body>
|
||||
{{ breakpoint() }}
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
Once stopped, your debugger will allow you to inspect the `$environment` and `$context` variables.
|
||||
|
||||
### Function arguments
|
||||
|
||||
Any argument passed to the twig function will be added to the `$arguments` array, so you can inspect it easily.
|
||||
|
||||
```twig
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>title</title>
|
||||
</head>
|
||||
<body>
|
||||
{{ breakpoint(app.user, app.session) }}
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
Symfony Bundle
|
||||
--------------
|
||||
|
||||
If you want to use this extension in your Symfony application, you can enable the
|
||||
Symfony Bundle included in this package.
|
||||
|
||||
The bundle will register the twig extension automatically. So, once enabled, you
|
||||
can insert the `breakpoint` twig function in your templates.
|
||||
|
||||
### Symfony 2/3
|
||||
|
||||
```php
|
||||
// app/AppKernel.php
|
||||
if (in_array($this->getEnvironment(), array('dev', 'test'), true)) {
|
||||
$bundles[] = new Ajgl\Twig\Extension\SymfonyBundle\AjglBreakpointTwigExtensionBundle();
|
||||
}
|
||||
```
|
||||
|
||||
### Symfony 4
|
||||
|
||||
```php
|
||||
// config/bundles.php
|
||||
//...
|
||||
return [
|
||||
//...
|
||||
Ajgl\Twig\Extension\SymfonyBundle\AjglBreakpointTwigExtensionBundle::class => ['dev' => true]
|
||||
];
|
||||
```
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
This component is under the MIT license. See the complete license in the [LICENSE] file.
|
||||
|
||||
|
||||
Reporting an issue or a feature request
|
||||
---------------------------------------
|
||||
|
||||
Issues and feature requests are tracked in the [Github issue tracker].
|
||||
|
||||
|
||||
Author Information
|
||||
------------------
|
||||
|
||||
Developed with ♥ by [Antonio J. García Lagar].
|
||||
|
||||
If you find this component useful, please add a ★ in the [GitHub repository page] and/or the [Packagist package page].
|
||||
|
||||
[Xdebug]: https://xdebug.org/
|
||||
[LICENSE]: LICENSE
|
||||
[Github issue tracker]: https://github.com/ajgarlag/AjglBreakpointTwigExtension/issues
|
||||
[Antonio J. García Lagar]: http://aj.garcialagar.es
|
||||
[GitHub repository page]: https://github.com/ajgarlag/AjglBreakpointTwigExtension
|
||||
[Packagist package page]: https://packagist.org/packages/ajgl/breakpoint-twig-extension
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "ajgl/breakpoint-twig-extension",
|
||||
"description": "Twig extension to set breakpoints",
|
||||
"keywords": ["twig", "xdebug", "breakpoint"],
|
||||
"homepage": "https://github.com/ajgarlag/AjglBreakpointTwigExtension",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Antonio J. García Lagar",
|
||||
"email": "aj@garcialagar.es",
|
||||
"homepage": "http://aj.garcialagar.es",
|
||||
"role": "developer"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": { "Ajgl\\Twig\\Extension\\": "src/" }
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": { "Ajgl\\Twig\\Extension\\Tests\\": "tests/" }
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.6",
|
||||
"twig/twig": "^1.14|^2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/framework-bundle": "^2.7|^3.2|^4.1",
|
||||
"symfony/twig-bundle": "^2.7|^3.2|^4.1",
|
||||
"symfony/phpunit-bridge": "^3.4|^4.1"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-xdebug": "The Xdebug extension is required for the breakpoint to work",
|
||||
"symfony/framework-bundle": "The framework bundle to integrate the extension into Symfony",
|
||||
"symfony/twig-bundle": "The twig bundle to integrate the extension into Symfony"
|
||||
},
|
||||
"scripts": {
|
||||
"phpunit": "vendor/bin/simple-phpunit --colors=always",
|
||||
"test": [
|
||||
"@phpunit"
|
||||
]
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "0.3.x-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.0/phpunit.xsd"
|
||||
backupGlobals="false"
|
||||
colors="true"
|
||||
bootstrap="vendor/autoload.php"
|
||||
>
|
||||
|
||||
<testsuites>
|
||||
<testsuite name="AjglBreakpointTwigExtension Test Suite">
|
||||
<directory>./tests</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<filter>
|
||||
<whitelist>
|
||||
<directory>./src</directory>
|
||||
</whitelist>
|
||||
</filter>
|
||||
|
||||
</phpunit>
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* AJGL Breakpoint Twig Extension Component
|
||||
*
|
||||
* Copyright (C) Antonio J. García Lagar <aj@garcialagar.es>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Ajgl\Twig\Extension;
|
||||
|
||||
use Twig\Environment;
|
||||
use Twig\Extension\AbstractExtension;
|
||||
use Twig\TwigFunction;
|
||||
|
||||
/**
|
||||
* @author Antonio J. García Lagar <aj@garcialagar.es>
|
||||
*/
|
||||
class BreakpointExtension extends AbstractExtension
|
||||
{
|
||||
public function getName()
|
||||
{
|
||||
return 'breakpoint';
|
||||
}
|
||||
|
||||
public function getFunctions()
|
||||
{
|
||||
return [
|
||||
new TwigFunction('breakpoint', [$this, 'setBreakpoint'], ['needs_environment' => true, 'needs_context' => true]),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* If Xdebug is detected, makes the debugger break.
|
||||
*
|
||||
* @param Environment $environment the environment instance
|
||||
* @param mixed $context variables from the Twig template
|
||||
*/
|
||||
public function setBreakpoint(Environment $environment, $context)
|
||||
{
|
||||
if (function_exists('xdebug_break')) {
|
||||
$arguments = func_get_args();
|
||||
$arguments = array_slice($arguments, 2);
|
||||
xdebug_break();
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* AJGL Breakpoint Twig Extension Component
|
||||
*
|
||||
* Copyright (C) Antonio J. García Lagar <aj@garcialagar.es>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Ajgl\Twig\Extension\SymfonyBundle;
|
||||
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
|
||||
/**
|
||||
* @author Antonio J. García Lagar <aj@garcialagar.es>
|
||||
*/
|
||||
class AjglBreakpointTwigExtensionBundle extends Bundle
|
||||
{
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* AJGL Breakpoint Twig Extension Component
|
||||
*
|
||||
* Copyright (C) Antonio J. García Lagar <aj@garcialagar.es>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Ajgl\Twig\Extension\SymfonyBundle\DependencyInjection;
|
||||
|
||||
use Symfony\Component\Config\FileLocator;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Loader;
|
||||
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
|
||||
|
||||
/**
|
||||
* @author Antonio J. García Lagar <aj@garcialagar.es>
|
||||
*/
|
||||
class AjglBreakpointTwigExtensionExtension extends Extension
|
||||
{
|
||||
public function load(array $config, ContainerBuilder $container)
|
||||
{
|
||||
$loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
|
||||
$loader->load('twig.xml');
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<container xmlns="http://symfony.com/schema/dic/services"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
|
||||
|
||||
<services>
|
||||
<service id="ajgl_twig_extension.breakpoint" class="Ajgl\Twig\Extension\BreakpointExtension" public="false">
|
||||
<tag name="twig.extension"/>
|
||||
</service>
|
||||
</services>
|
||||
|
||||
</container>
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* AJGL Breakpoint Twig Extension Component
|
||||
*
|
||||
* Copyright (C) Antonio J. García Lagar <aj@garcialagar.es>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Ajgl\Twig\Extension\Tests;
|
||||
|
||||
use Ajgl\Twig\Extension\BreakpointExtension;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @author Antonio J. García Lagar <aj@garcialagar.es>
|
||||
*/
|
||||
class BreakpointExtensionTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var BreakpointExtension
|
||||
*/
|
||||
protected $extension;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
$this->extension = new BreakpointExtension();
|
||||
}
|
||||
|
||||
public function testGetName()
|
||||
{
|
||||
$this->assertSame('breakpoint', $this->extension->getName());
|
||||
}
|
||||
|
||||
public function testGetFunctions()
|
||||
{
|
||||
$functions = $this->extension->getFunctions();
|
||||
$this->assertCount(1, $functions);
|
||||
$function = reset($functions);
|
||||
$this->assertInstanceOf('Twig_SimpleFunction', $function);
|
||||
$callable = $function->getCallable();
|
||||
$this->assertTrue(is_array($callable));
|
||||
$this->assertCount(2, $callable);
|
||||
$this->assertSame($this->extension, $callable[0]);
|
||||
$this->assertSame('setBreakpoint', $callable[1]);
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* AJGL Breakpoint Twig Extension Component
|
||||
*
|
||||
* Copyright (C) Antonio J. García Lagar <aj@garcialagar.es>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Ajgl\Twig\Extension\Tests\SymfonyBundle\DependencyInjection;
|
||||
|
||||
use Ajgl\Twig\Extension\SymfonyBundle\DependencyInjection\AjglBreakpointTwigExtensionExtension;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
|
||||
/**
|
||||
* @author Antonio J. García Lagar <aj@garcialagar.es>
|
||||
*/
|
||||
class AjglBreakpointTwigExtensionExtensionTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var ContainerBuilder
|
||||
*/
|
||||
protected $container;
|
||||
|
||||
/**
|
||||
* @var AjglBreakpointTwigExtensionExtension
|
||||
*/
|
||||
protected $extension;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
$this->container = new ContainerBuilder();
|
||||
$this->extension = new AjglBreakpointTwigExtensionExtension();
|
||||
}
|
||||
|
||||
public function testTwigExtensionsDefinition()
|
||||
{
|
||||
$this->extension->load([], $this->container);
|
||||
$this->assertTrue($this->container->hasDefinition('ajgl_twig_extension.breakpoint'));
|
||||
$definition = $this->container->getDefinition('ajgl_twig_extension.breakpoint');
|
||||
$this->assertSame(
|
||||
'Ajgl\Twig\Extension\BreakpointExtension',
|
||||
$definition->getClass()
|
||||
);
|
||||
$this->assertNotNull($definition->getTag('twig.extension'));
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
language: php
|
||||
sudo: false
|
||||
|
||||
php:
|
||||
- 5.4
|
||||
- 5.5
|
||||
- 5.6
|
||||
- 7
|
||||
|
||||
env:
|
||||
global:
|
||||
# travis encrypt CODECLIMATE_REPO_TOKEN=<https://codeclimate.com/repos/53e0fcc8e30ba02d78013d4c/coverage_setup>
|
||||
- secure: "n4I9GwRCxi9UGjhDGe79SV6Nr/fRKpA8N+qqqbkveJv01Z4IRcAsRBRxKrSAX8zJUoFhlJK73VeRnBfdSBddsL3RqgDMbHilZFy6yC7Dj/vVyNe90RPWxc5J7w3sidT2g1OsgWueiUis8y3dGOQd2sewfcb2pZa29/eQusBV5i0="
|
||||
|
||||
install:
|
||||
- composer install
|
||||
|
||||
before_script:
|
||||
- mkdir -p build/logs
|
||||
|
||||
script:
|
||||
- vendor/bin/phpunit --coverage-clover build/logs/clover.xml
|
||||
|
||||
after_script:
|
||||
- php vendor/bin/coveralls -v
|
||||
- ./vendor/bin/test-reporter --stdout > codeclimate.json
|
||||
- "curl -X POST -d @codeclimate.json -H 'Content-Type: application/json' -H 'User-Agent: Code Climate (PHP Test Reporter v1.0.1-dev)' https://codeclimate.com/test_reports"
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
2.0.0
|
||||
-----
|
||||
|
||||
- **BC**: Require Twig v1.12, in order to replace deprecated Twig_Filter_Method with Twig_SimpleFilter
|
||||
- **Added**: Add support for ParsedownEnging
|
||||
|
||||
1.2.0
|
||||
-----
|
||||
|
||||
- **Added**: Add support for GitHub's markdown engine
|
||||
|
||||
1.1.0
|
||||
-----
|
||||
|
||||
- **Added**: Add support for PHP League CommonMark engine
|
||||
|
||||
1.0.0
|
||||
-----
|
||||
|
||||
- **BC**: Remove deprecated dflydev-markdown parser
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
Twig Markdown Extension
|
||||
=======================
|
||||
|
||||
[](http://travis-ci.org/aptoma/twig-markdown)
|
||||
[](https://coveralls.io/r/aptoma/twig-markdown)
|
||||
|
||||
Twig Markdown extension provides a new filter and a tag to allow parsing of
|
||||
content as Markdown in [Twig][1] templates.
|
||||
|
||||
This extension could be integrated with several Markdown parser as it provides an interface, which allows you to customize your Markdown parser.
|
||||
|
||||
### Supported parsers
|
||||
|
||||
* [michelf/php-markdown](https://github.com/michelf/php-markdown) (+ MarkdownExtra)
|
||||
* [league/commonmark](http://commonmark.thephpleague.com/)
|
||||
* [KnpLabs/php-github-api](https://github.com/KnpLabs/php-github-api)
|
||||
* [erusev/parsedown](https://github.com/erusev/parsedown)
|
||||
|
||||
## Features
|
||||
|
||||
* Filter support `{{ "# Heading Level 1"|markdown }}`
|
||||
* Tag support `{% markdown %}{% endmarkdown %}`
|
||||
|
||||
When used as a tag, the indentation level of the first line sets the default indentation level for the rest of the tag content.
|
||||
From this indentation level, all same indentation or outdented levels text will be transformed as regular text.
|
||||
|
||||
This feature allows you to write your Markdown content at any indentation level without caring of Markdown internal transformation:
|
||||
|
||||
```php
|
||||
<div>
|
||||
<h1 class="someClass">{{ title }}</h1>
|
||||
|
||||
{% markdown %}
|
||||
This is a list that is indented to match the context around the markdown tag:
|
||||
|
||||
* List item 1
|
||||
* List item 2
|
||||
* Sub List Item
|
||||
* Sub Sub List Item
|
||||
|
||||
The following block will be transformed as code, as it is indented more than the
|
||||
surrounding content:
|
||||
|
||||
$code = "good";
|
||||
|
||||
{% endmarkdown %}
|
||||
|
||||
</div>
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
Run the composer command to install the latest stable version:
|
||||
|
||||
```bash
|
||||
composer require aptoma/twig-markdown
|
||||
```
|
||||
|
||||
Or update your `composer.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"require": {
|
||||
"aptoma/twig-markdown": "~1.1"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can choose to provide your own Markdown engine, although we recommend
|
||||
using [michelf/php-markdown](https://github.com/michelf/php-markdown):
|
||||
|
||||
```bash
|
||||
composer require michelf/php-markdown ~1.3
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"require": {
|
||||
"michelf/php-markdown": "~1.3"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You may also use the [PHP League CommonMark engine](http://commonmark.thephpleague.com/):
|
||||
|
||||
```bash
|
||||
composer require league/commonmark ~0.5
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"require": {
|
||||
"league/commonmark": "~0.5"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Twig Extension
|
||||
|
||||
The Twig extension provides the `markdown` tag and filter support.
|
||||
|
||||
Assuming that you are using [composer](http://getcomposer.org) autoloading,
|
||||
add the extension to the Twig environment:
|
||||
|
||||
```php
|
||||
|
||||
use Aptoma\Twig\Extension\MarkdownExtension;
|
||||
use Aptoma\Twig\Extension\MarkdownEngine;
|
||||
|
||||
$engine = new MarkdownEngine\MichelfMarkdownEngine();
|
||||
|
||||
$twig->addExtension(new MarkdownExtension($engine));
|
||||
```
|
||||
|
||||
### Twig Token Parser
|
||||
|
||||
The Twig token parser provides the `markdown` tag only!
|
||||
|
||||
```php
|
||||
use Aptoma\Twig\Extension\MarkdownEngine;
|
||||
use Aptoma\Twig\TokenParser\MarkdownTokenParser;
|
||||
|
||||
$engine = new MarkdownEngine\MichelfMarkdownEngine();
|
||||
|
||||
$twig->addTokenParser(new MarkdownTokenParser($engine));
|
||||
```
|
||||
|
||||
### GitHub Markdown Engine
|
||||
|
||||
`MarkdownEngine\GitHubMarkdownEngine` provides an interface to GitHub's markdown engine using their public API via [`KnpLabs\php-github-api`][2]. To reduce API calls, rendered documents are hashed and cached in the filesystem. You can pass a GitHub repository and the path to be used for caching to the constructor:
|
||||
|
||||
```php
|
||||
use Aptoma\Twig\Extension\MarkdownEngine;
|
||||
|
||||
$engine = new MarkdownEngine\GitHubMarkdownEngine(
|
||||
'aptoma/twig-markdown', // The GitHub repository to use as a context
|
||||
true, // Whether to use GitHub's Flavored Markdown (GFM)
|
||||
'/tmp/markdown-cache', // Path on filesystem to store rendered documents
|
||||
);
|
||||
```
|
||||
|
||||
In order to authenticate the API client (for instance), it's possible to pass an own instance of `\GitHub\Client` instead of letting the engine create one itself:
|
||||
|
||||
```php
|
||||
$client = new \GitHub\Client;
|
||||
$client->authenticate('GITHUB_CLIENT_ID', 'GITHUB_CLIENT_SECRET', \Github\Client::AUTH_URL_CLIENT_ID);
|
||||
|
||||
$engine = new MarkdownEngine\GitHubMarkdownEngine('aptoma/twig-markdown', true, '/tmp/markdown-cache', $client);
|
||||
```
|
||||
|
||||
### Using a different Markdown parser engine
|
||||
|
||||
If you want to use a different Markdown parser, you need to create an adapter
|
||||
that implements `Aptoma\Twig\Extension\MarkdownEngineInterface.php`. Have
|
||||
a look at `Aptoma\Twig\Extension\MarkdownEngine\MichelfMarkdownEngine` for an
|
||||
example.
|
||||
|
||||
## Tests
|
||||
|
||||
The test suite uses PHPUnit:
|
||||
|
||||
$ phpunit
|
||||
|
||||
## License
|
||||
|
||||
Twig Markdown Extension is licensed under the MIT license.
|
||||
|
||||
[1]: http://twig.sensiolabs.org
|
||||
[2]: https://github.com/knplabs/php-github-api
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "aptoma/twig-markdown",
|
||||
"description": "Twig extension to work with Markdown content",
|
||||
"keywords": ["twig", "markdown"],
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Gunnar Lium",
|
||||
"email": "gunnar@aptoma.com"
|
||||
},
|
||||
{
|
||||
"name": "Joris Berthelot",
|
||||
"email": "joris@berthelot.tel"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"twig/twig": "~1.12"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "~4.0",
|
||||
"satooshi/php-coveralls": "~0.6",
|
||||
"codeclimate/php-test-reporter": "dev-master",
|
||||
"michelf/php-markdown": "~1",
|
||||
"league/commonmark": "~0.5",
|
||||
"knplabs/github-api": "~1.2",
|
||||
"erusev/parsedown": "^1.6"
|
||||
},
|
||||
"suggest": {
|
||||
"michelf/php-markdown": "Original Markdown engine with MarkdownExtra.",
|
||||
"knplabs/github-api": "Needed for using GitHub's Markdown engine provided through their API."
|
||||
},
|
||||
"autoload": {
|
||||
"psr-0": { "Aptoma": "src/" }
|
||||
}
|
||||
}
|
||||
+1772
File diff suppressed because it is too large.
Load diff
+18
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit backupGlobals="false"
|
||||
backupStaticAttributes="false"
|
||||
colors="true"
|
||||
convertErrorsToExceptions="true"
|
||||
convertNoticesToExceptions="true"
|
||||
convertWarningsToExceptions="true"
|
||||
processIsolation="false"
|
||||
stopOnFailure="false"
|
||||
syntaxCheck="false"
|
||||
bootstrap="vendor/autoload.php"
|
||||
>
|
||||
<testsuites>
|
||||
<testsuite name="Aptoma Markdown Test Suite">
|
||||
<directory>./tests/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
</phpunit>
|
||||
Vendored
+76
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
namespace Aptoma\Twig\Extension\MarkdownEngine;
|
||||
|
||||
use Aptoma\Twig\Extension\MarkdownEngineInterface;
|
||||
|
||||
/**
|
||||
* GithubMarkdownEngine.php
|
||||
*
|
||||
* Maps GitHub's Markdown engine API to Aptoma\Twig Markdown Extension using
|
||||
* KnpLabs\php-github-api.
|
||||
*
|
||||
* @author Lukas W <lukaswhl@gmail.com>
|
||||
*/
|
||||
class GitHubMarkdownEngine implements MarkdownEngineInterface
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $contextRepo The repository context. Pass a GitHub repo
|
||||
* such as 'aptoma/twig-markdown' to render e.g. issues #23 in the
|
||||
* context of the repo.
|
||||
* @param bool $gfm Whether to use GitHub's Flavored Markdown or the
|
||||
* standard markdown. Default is true.
|
||||
* @param string $cacheDir Location on disk where rendered documents should
|
||||
* be stored.
|
||||
* @param \Github\Client $client Client object to use. A new Github\Client()
|
||||
* object is constructed automatically if $client is null.
|
||||
*/
|
||||
public function __construct($contextRepo = null, $gfm = true, $cacheDir = '/tmp/github-markdown-cache', \GitHub\Client $client=null)
|
||||
{
|
||||
$this->repo = $contextRepo;
|
||||
$this->mode = $gfm ? 'gfm' : 'markdown';
|
||||
$this->cacheDir = rtrim($cacheDir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
|
||||
if (!is_dir($this->cacheDir)) {
|
||||
@mkdir($this->cacheDir, 0777, true);
|
||||
}
|
||||
|
||||
if ($client === null) {
|
||||
$client = new \Github\Client();
|
||||
}
|
||||
$this->api = $client->api('markdown');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($content)
|
||||
{
|
||||
$cacheFile = $this->getCachePath($content);
|
||||
if (file_exists($cacheFile)) {
|
||||
return file_get_contents($cacheFile);;
|
||||
}
|
||||
|
||||
$response = $this->api->render($content, $this->mode, $this->repo);
|
||||
file_put_contents($cacheFile, $response);
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'KnpLabs\php-github-api';
|
||||
}
|
||||
|
||||
private function getCachePath($content)
|
||||
{
|
||||
return $this->cacheDir . md5($content) . '_' . $this->mode. '_' . str_replace('/', '.', $this->repo);
|
||||
}
|
||||
|
||||
private $api;
|
||||
private $cacheDir;
|
||||
private $repo;
|
||||
private $mode;
|
||||
}
|
||||
Vendored
+32
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Aptoma\Twig\Extension\MarkdownEngine;
|
||||
|
||||
use Aptoma\Twig\Extension\MarkdownEngineInterface;
|
||||
use Michelf\MarkdownExtra;
|
||||
|
||||
/**
|
||||
* MichelfMarkdownEngine.php
|
||||
*
|
||||
* Maps Michelf\MarkdownExtra to Aptoma\Twig Markdown Extension
|
||||
*
|
||||
* @author Joris Berthelot <joris@berthelot.tel>
|
||||
*/
|
||||
class MichelfMarkdownEngine implements MarkdownEngineInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($content)
|
||||
{
|
||||
return MarkdownExtra::defaultTransform($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'Michelf\Markdown';
|
||||
}
|
||||
}
|
||||
Vendored
+49
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Aptoma\Twig\Extension\MarkdownEngine;
|
||||
|
||||
use Aptoma\Twig\Extension\MarkdownEngineInterface;
|
||||
use League\CommonMark\CommonMarkConverter;
|
||||
|
||||
/**
|
||||
* PHPLeagueCommonMarkEngine.php
|
||||
*
|
||||
* Maps League\CommonMark\CommonMarkConverter to Aptoma\Twig Markdown Extension
|
||||
*
|
||||
* @author Casey McLaughlin <caseyamcl@gmail.com>
|
||||
*/
|
||||
class PHPLeagueCommonMarkEngine implements MarkdownEngineInterface
|
||||
{
|
||||
/**
|
||||
* @var \League\CommonMark\CommonMarkConverter
|
||||
*/
|
||||
private $converter;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* Accepts CommonMarkConverter or creates one automatically
|
||||
*
|
||||
* @param \League\CommonMark\CommonMarkConverter $converter
|
||||
*/
|
||||
public function __construct(CommonMarkConverter $converter = null)
|
||||
{
|
||||
$this->converter = $converter ?: new CommonMarkConverter();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($content)
|
||||
{
|
||||
return $this->converter->convertToHtml($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'League\CommonMark';
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Aptoma\Twig\Extension\MarkdownEngine;
|
||||
|
||||
use Aptoma\Twig\Extension\MarkdownEngineInterface;
|
||||
use Parsedown;
|
||||
|
||||
/**
|
||||
* ParsedownEngine.php
|
||||
*
|
||||
* Maps erusev/parsedown to Aptoma\Twig Markdown Extension
|
||||
*
|
||||
* @author Sébastien Lourseau <https://github.com/SebLours>
|
||||
*/
|
||||
class ParsedownEngine implements MarkdownEngineInterface
|
||||
{
|
||||
/**
|
||||
* @var Parsedown
|
||||
*/
|
||||
protected $engine;
|
||||
|
||||
/**
|
||||
* @param string|null $instanceName
|
||||
*/
|
||||
public function __construct($instanceName = null)
|
||||
{
|
||||
$this->engine = Parsedown::instance($instanceName);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($content)
|
||||
{
|
||||
return $this->engine->parse($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'erusev/parsedown';
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace Aptoma\Twig\Extension;
|
||||
|
||||
/**
|
||||
* MarkdownEngineInterface.php
|
||||
*
|
||||
* Provide software interface to maps various Markdown engines
|
||||
*
|
||||
* @author Joris Berthelot <joris@berthelot.tel>
|
||||
*/
|
||||
interface MarkdownEngineInterface
|
||||
{
|
||||
/**
|
||||
* Transforms the given markdown data in HTML
|
||||
*
|
||||
* @param $content Markdown data
|
||||
* @return string
|
||||
*/
|
||||
public function transform($content);
|
||||
|
||||
/**
|
||||
* Return Markdown engine vendor ID
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName();
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace Aptoma\Twig\Extension;
|
||||
|
||||
use Aptoma\Twig\TokenParser\MarkdownTokenParser;
|
||||
|
||||
/**
|
||||
* MarkdownExtension provides support for Markdown.
|
||||
*
|
||||
* @author Gunnar Lium <gunnar@aptoma.com>
|
||||
* @author Joris Berthelot <joris@berthelot.tel>
|
||||
*/
|
||||
class MarkdownExtension extends \Twig_Extension
|
||||
{
|
||||
|
||||
/**
|
||||
* @var MarkdownEngineInterface $markdownEngine
|
||||
*/
|
||||
private $markdownEngine;
|
||||
|
||||
/**
|
||||
* @param MarkdownEngineInterface $markdownEngine The Markdown parser engine
|
||||
*/
|
||||
public function __construct(MarkdownEngineInterface $markdownEngine)
|
||||
{
|
||||
$this->markdownEngine = $markdownEngine;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFilters()
|
||||
{
|
||||
return array(
|
||||
new \Twig_SimpleFilter(
|
||||
'markdown',
|
||||
array($this, 'parseMarkdown'),
|
||||
array('is_safe' => array('html'))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform Markdown content to HTML
|
||||
*
|
||||
* @param $content The Markdown content to be transformed
|
||||
* @return string The result of the Markdown engine transformation
|
||||
*/
|
||||
public function parseMarkdown($content)
|
||||
{
|
||||
return $this->markdownEngine->transform($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTokenParsers()
|
||||
{
|
||||
return array(new MarkdownTokenParser($this->markdownEngine));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'markdown';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace Aptoma\Twig\Node;
|
||||
|
||||
/**
|
||||
* Represents a markdown node.
|
||||
*
|
||||
* It parses content as Markdown.
|
||||
*
|
||||
* @author Gunnar Lium <gunnar@aptoma.com>
|
||||
* @author Joris Berthelot <joris@berthelot.tel>
|
||||
*/
|
||||
class MarkdownNode extends \Twig_Node
|
||||
{
|
||||
public function __construct(\Twig_NodeInterface $body, $lineno, $tag = 'markdown')
|
||||
{
|
||||
parent::__construct(array('body' => $body), array(), $lineno, $tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles the node to PHP.
|
||||
*
|
||||
* @param \Twig_Compiler A Twig_Compiler instance
|
||||
*/
|
||||
public function compile(\Twig_Compiler $compiler)
|
||||
{
|
||||
$compiler
|
||||
->addDebugInfo($this)
|
||||
->write('ob_start();' . PHP_EOL)
|
||||
->subcompile($this->getNode('body'))
|
||||
->write('$content = ob_get_clean();' . PHP_EOL)
|
||||
->write('preg_match("/^\s*/", $content, $matches);' . PHP_EOL)
|
||||
->write('$lines = explode("\n", $content);' . PHP_EOL)
|
||||
->write('$content = preg_replace(\'/^\' . $matches[0]. \'/\', "", $lines);' . PHP_EOL)
|
||||
->write('$content = join("\n", $content);' . PHP_EOL)
|
||||
->write('echo $this->env->getTokenParsers()->getTokenParser(\'markdown\')->getEngine()->transform($content);' . PHP_EOL);
|
||||
}
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace Aptoma\Twig\TokenParser;
|
||||
|
||||
use Aptoma\Twig\Node\MarkdownNode;
|
||||
use Aptoma\Twig\Extension\MarkdownEngineInterface;
|
||||
|
||||
/**
|
||||
* @author Gunnar Lium <gunnar@aptoma.com>
|
||||
* @author Joris Berthelot <joris@berthelot.tel>
|
||||
*/
|
||||
class MarkdownTokenParser extends \Twig_TokenParser
|
||||
{
|
||||
/**
|
||||
* @var The Markdown engine
|
||||
*/
|
||||
protected $markdownEngine;
|
||||
|
||||
/**
|
||||
* @param MarkdownEngineInterface $markdownEngine The Markdown parser engine
|
||||
*/
|
||||
public function __construct(MarkdownEngineInterface $markdownEngine)
|
||||
{
|
||||
$this->markdownEngine = $markdownEngine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Markdown parser engine getter
|
||||
*
|
||||
* @return MarkdownEngineInterface
|
||||
*/
|
||||
public function getEngine()
|
||||
{
|
||||
return $this->markdownEngine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a token and returns a node.
|
||||
*
|
||||
* @param \Twig_Token $token A \Twig_Token instance
|
||||
*
|
||||
* @throws \Twig_Error_Syntax
|
||||
* @return \Twig_NodeInterface A \Twig_NodeInterface instance
|
||||
*/
|
||||
public function parse(\Twig_Token $token)
|
||||
{
|
||||
$lineno = $token->getLine();
|
||||
|
||||
$this->parser->getStream()->expect(\Twig_Token::BLOCK_END_TYPE);
|
||||
$body = $this->parser->subparse(array($this, 'decideMarkdownEnd'), true);
|
||||
$this->parser->getStream()->expect(\Twig_Token::BLOCK_END_TYPE);
|
||||
|
||||
return new MarkdownNode($body, $lineno, $this->getTag());
|
||||
}
|
||||
|
||||
/**
|
||||
* Decide if current token marks end of Markdown block.
|
||||
*
|
||||
* @param \Twig_Token $token
|
||||
* @return bool
|
||||
*/
|
||||
public function decideMarkdownEnd(\Twig_Token $token)
|
||||
{
|
||||
return $token->test('endmarkdown');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the tag name associated with this token parser.
|
||||
*
|
||||
* @return string The tag name
|
||||
*/
|
||||
public function getTag()
|
||||
{
|
||||
return 'markdown';
|
||||
}
|
||||
}
|
||||
Vendored
+31
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace Aptoma\Twig\Extension\MarkdownEngine;
|
||||
|
||||
use Aptoma\Twig\Extension\MarkdownExtensionTest;
|
||||
|
||||
require_once(__DIR__ . '/../MarkdownExtensionTest.php');
|
||||
|
||||
/**
|
||||
* Class GitHubMarkdownEngineTest
|
||||
*
|
||||
* @author Lukas W <lukaswhl@gmail.com>
|
||||
*/
|
||||
class GitHubMarkdownEngineTest extends MarkdownExtensionTest
|
||||
{
|
||||
public function getParseMarkdownTests()
|
||||
{
|
||||
return array(
|
||||
array('{{ "# Main Title"|markdown }}', '<h1>Main Title</h1>'),
|
||||
array('{{ content|markdown }}', '<h1>Main Title</h1>', array('content' => '# Main Title')),
|
||||
// Check if GFM is working
|
||||
array('{{ "@aptoma"|markdown }}',
|
||||
'<p><a href="https://github.com/aptoma" class="user-mention">@aptoma</a></p>'),
|
||||
);
|
||||
}
|
||||
|
||||
protected function getEngine()
|
||||
{
|
||||
return new GitHubMarkdownEngine();
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Aptoma\Twig\Extension\MarkdownEngine;
|
||||
|
||||
use Aptoma\Twig\Extension\MarkdownExtensionTest;
|
||||
|
||||
// Require parent class if not autoloaded
|
||||
if (!class_exists('\Aptoma\Twig\Extension\MarkdownExtensionTest')) {
|
||||
require_once(__DIR__ . '/../MarkdownExtensionTest.php');
|
||||
}
|
||||
|
||||
/**
|
||||
* Class PHPLeagueCommonMarkEngineTest
|
||||
*
|
||||
* @author Casey McLaughlin <caseyamcl@gmail.com>
|
||||
*/
|
||||
class PHPLeagueCommonMarkEngineTest extends MarkdownExtensionTest
|
||||
{
|
||||
protected function getEngine()
|
||||
{
|
||||
return new PHPLeagueCommonMarkEngine();
|
||||
}
|
||||
}
|
||||
Vendored
+32
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Aptoma\Twig\Extension\MarkdownEngine;
|
||||
|
||||
use Aptoma\Twig\Extension\MarkdownExtensionTest;
|
||||
|
||||
// Require parent class if not autoloaded
|
||||
if (!class_exists('\Aptoma\Twig\Extension\MarkdownExtensionTest')) {
|
||||
require_once(__DIR__ . '/../MarkdownExtensionTest.php');
|
||||
}
|
||||
|
||||
/**
|
||||
* Class ParsedownEngineTest
|
||||
*
|
||||
* @author Sébastien Lourseau <https://github.com/SebLours>
|
||||
*/
|
||||
class ParsedownEngineTest extends MarkdownExtensionTest
|
||||
{
|
||||
public function getParseMarkdownTests()
|
||||
{
|
||||
return array(
|
||||
array('{{ "# Main Title"|markdown }}', '<h1>Main Title</h1>'),
|
||||
array('{{ content|markdown }}', '<h1>Main Title</h1>', array('content' => '# Main Title')),
|
||||
array('{% markdown %}{{ content }}{% endmarkdown %}', '<h1>Main Title</h1>', array('content' => '# Main Title'))
|
||||
);
|
||||
}
|
||||
|
||||
protected function getEngine()
|
||||
{
|
||||
return new ParsedownEngine();
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Aptoma\Twig\Extension;
|
||||
|
||||
use Aptoma\Twig\Extension\MarkdownEngine\MichelfMarkdownEngine;
|
||||
|
||||
/**
|
||||
* @author Gunnar Liun <gunnar@aptoma.com>
|
||||
*/
|
||||
class MarkdownExtensionTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider getParseMarkdownTests
|
||||
*/
|
||||
public function testParseMarkdown($template, $expected, $context = array())
|
||||
{
|
||||
$this->assertEquals($expected, $this->getTemplate($template)->render($context));
|
||||
}
|
||||
|
||||
public function getParseMarkdownTests()
|
||||
{
|
||||
return array(
|
||||
array('{{ "# Main Title"|markdown }}', '<h1>Main Title</h1>' . PHP_EOL),
|
||||
array('{{ content|markdown }}', '<h1>Main Title</h1>' . PHP_EOL, array('content' => '# Main Title'))
|
||||
);
|
||||
}
|
||||
|
||||
protected function getEngine()
|
||||
{
|
||||
return new MichelfMarkdownEngine();
|
||||
}
|
||||
|
||||
protected function getTemplate($template)
|
||||
{
|
||||
$loader = new \Twig_Loader_Array(array('index' => $template));
|
||||
$twig = new \Twig_Environment($loader, array('debug' => true, 'cache' => false));
|
||||
$twig->addExtension(new MarkdownExtension($this->getEngine()));
|
||||
|
||||
return $twig->loadTemplate('index');
|
||||
}
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
namespace Aptoma\Twig\TokenParser;
|
||||
|
||||
use Aptoma\Twig\Extension\MarkdownEngine\MichelfMarkdownEngine;
|
||||
use Aptoma\Twig\Node\MarkdownNode;
|
||||
|
||||
/**
|
||||
* @author Gunnar Lium <gunnar@aptoma.com>
|
||||
*/
|
||||
class MarkdownTokenParserTest extends \Twig_Test_NodeTestCase
|
||||
{
|
||||
public function testConstructor()
|
||||
{
|
||||
$body = new \Twig_Node(array(new \Twig_Node_Text("#Title\n\nparagraph\n", 1)));
|
||||
$node = new MarkdownNode($body, 1);
|
||||
|
||||
$this->assertEquals($body, $node->getNode('body'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the generated code actually do what we expect
|
||||
*
|
||||
* The contents of this test is the same that we write in the compile method.
|
||||
* This requires manual synchronization, which we should probably not rely on.
|
||||
*/
|
||||
public function testMarkdownPrepareBehavior()
|
||||
{
|
||||
$body = " #Title\n\n paragraph\n\n code";
|
||||
$bodyPrepared = "#Title\n\nparagraph\n\n code";
|
||||
|
||||
ob_start();
|
||||
echo $body;
|
||||
$content = ob_get_clean();
|
||||
preg_match("/^\s*/", $content, $matches);
|
||||
$lines = explode("\n", $content);
|
||||
$content = preg_replace('/^' . $matches[0]. '/', "", $lines);
|
||||
$content = join("\n", $content);
|
||||
|
||||
// Assert prepared content looks right
|
||||
$this->assertEquals($bodyPrepared, $content);
|
||||
|
||||
// Assert Markdown output
|
||||
$expectedOutput = "<h1>Title</h1>\n\n<p>paragraph</p>\n\n<pre><code>code\n</code></pre>\n";
|
||||
$this->assertEquals($expectedOutput, $this->getEngine()->transform($content));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the generated code looks as expected
|
||||
*
|
||||
* @dataProvider getTests
|
||||
*/
|
||||
public function testCompile($node, $source, $environment = null)
|
||||
{
|
||||
parent::testCompile($node, $source, $environment);
|
||||
}
|
||||
|
||||
protected function getEngine()
|
||||
{
|
||||
return new MichelfMarkdownEngine();
|
||||
}
|
||||
|
||||
public function getTests()
|
||||
{
|
||||
$tests = array();
|
||||
|
||||
$body = new \Twig_Node(array(new \Twig_Node_Text("#Title\n\nparagraph\n", 1)));
|
||||
$node = new MarkdownNode($body, 1);
|
||||
|
||||
$tests['simple text'] = array($node, <<<EOF
|
||||
// line 1
|
||||
ob_start();
|
||||
echo "#Title
|
||||
|
||||
paragraph
|
||||
";
|
||||
\$content = ob_get_clean();
|
||||
preg_match("/^\s*/", \$content, \$matches);
|
||||
\$lines = explode("\\n", \$content);
|
||||
\$content = preg_replace('/^' . \$matches[0]. '/', "", \$lines);
|
||||
\$content = join("\\n", \$content);
|
||||
echo \$this->env->getTokenParsers()->getTokenParser('markdown')->getEngine()->transform(\$content);
|
||||
EOF
|
||||
);
|
||||
|
||||
$body = new \Twig_Node(array(new \Twig_Node_Text(" #Title\n\n paragraph\n\n code\n", 1)));
|
||||
$node = new MarkdownNode($body, 1);
|
||||
|
||||
$tests['text with leading indent'] = array($node, <<<EOF
|
||||
// line 1
|
||||
ob_start();
|
||||
echo " #Title
|
||||
|
||||
paragraph
|
||||
|
||||
code
|
||||
";
|
||||
\$content = ob_get_clean();
|
||||
preg_match("/^\s*/", \$content, \$matches);
|
||||
\$lines = explode("\\n", \$content);
|
||||
\$content = preg_replace('/^' . \$matches[0]. '/', "", \$lines);
|
||||
\$content = join("\\n", \$content);
|
||||
echo \$this->env->getTokenParsers()->getTokenParser('markdown')->getEngine()->transform(\$content);
|
||||
EOF
|
||||
);
|
||||
|
||||
return $tests;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
phpunit.xml
|
||||
composer.lock
|
||||
composer.phar
|
||||
/vendor/
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
language: php
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- php: 5.6
|
||||
dist: xenial
|
||||
env: 'COMPOSER_FLAGS="--prefer-stable --prefer-lowest" TWIG_VERSION="^1.0"'
|
||||
- php: 5.6
|
||||
dist: xenial
|
||||
env: TWIG_VERSION="^1.0"
|
||||
- php: 7.0
|
||||
dist: xenial
|
||||
env: TWIG_VERSION="^1.0"
|
||||
- php: 7.1
|
||||
dist: bionic
|
||||
env: TWIG_VERSION="^1.0"
|
||||
- php: 7.2
|
||||
dist: bionic
|
||||
env: TWIG_VERSION="^1.0"
|
||||
- php: 7.3
|
||||
dist: bionic
|
||||
env: TWIG_VERSION="^1.0"
|
||||
- php: 7.4
|
||||
dist: bionic
|
||||
env: TWIG_VERSION="^1.0"
|
||||
- php: 7.0
|
||||
dist: xenial
|
||||
env: TWIG_VERSION="^2.0"
|
||||
- php: 7.1
|
||||
dist: bionic
|
||||
env: TWIG_VERSION="^2.0"
|
||||
- php: 7.2
|
||||
dist: bionic
|
||||
env: TWIG_VERSION="^2.0"
|
||||
- php: 7.3
|
||||
dist: bionic
|
||||
env: TWIG_VERSION="^2.0"
|
||||
- php: 7.4
|
||||
dist: bionic
|
||||
env: TWIG_VERSION="^2.0"
|
||||
|
||||
install: composer require twig/twig:${TWIG_VERSION}
|
||||
|
||||
script: composer test
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2013 Alexander <iam.asm89@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
+238
@@ -0,0 +1,238 @@
|
||||
Twig cache extension
|
||||
====================
|
||||
|
||||
The missing cache extension for Twig. The extension allows for caching rendered parts of
|
||||
templates using several cache strategies.
|
||||
|
||||
[](http://travis-ci.org/asm89/twig-cache-extension)
|
||||
|
||||
## Installation
|
||||
|
||||
The extension is installable via composer:
|
||||
|
||||
```json
|
||||
{
|
||||
"require": {
|
||||
"asm89/twig-cache-extension": "~1.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Quick start
|
||||
|
||||
### Setup
|
||||
|
||||
A minimal setup for adding the extension with the `LifeTimeCacheStrategy` and
|
||||
doctrine array cache is as following:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use Doctrine\Common\Cache\ArrayCache;
|
||||
use Asm89\Twig\CacheExtension\CacheProvider\DoctrineCacheAdapter;
|
||||
use Asm89\Twig\CacheExtension\CacheStrategy\LifetimeCacheStrategy;
|
||||
use Asm89\Twig\CacheExtension\Extension as CacheExtension;
|
||||
|
||||
$cacheProvider = new DoctrineCacheAdapter(new ArrayCache());
|
||||
$cacheStrategy = new LifetimeCacheStrategy($cacheProvider);
|
||||
$cacheExtension = new CacheExtension($cacheStrategy);
|
||||
|
||||
$twig->addExtension($cacheExtension);
|
||||
```
|
||||
|
||||
### Want to use a PSR-6 cache pool?
|
||||
|
||||
Instead of using the default `DoctrineCacheAdapter` the extension also has
|
||||
a `PSR-6` compatible adapter. You need to instantiate one of the cache pool
|
||||
implementations as can be found on: http://php-cache.readthedocs.io/en/latest/
|
||||
|
||||
Example: Making use of the `ApcuCachePool` via the `PsrCacheAdapter`:
|
||||
|
||||
```bash
|
||||
composer require cache/apcu-adapter
|
||||
```
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use Asm89\Twig\CacheExtension\CacheProvider\PsrCacheAdapter;
|
||||
use Asm89\Twig\CacheExtension\CacheStrategy\LifetimeCacheStrategy;
|
||||
use Asm89\Twig\CacheExtension\Extension as CacheExtension;
|
||||
use Cache\Adapter\Apcu\ApcuCachePool;
|
||||
|
||||
$cacheProvider = new PsrCacheAdapter(new ApcuCachePool());
|
||||
$cacheStrategy = new LifetimeCacheStrategy($cacheProvider);
|
||||
$cacheExtension = new CacheExtension($cacheStrategy);
|
||||
|
||||
$twig->addExtension($cacheExtension);
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
To cache a part of a template in Twig surround the code with a `cache` block.
|
||||
The cache block takes two parameters, first an "annotation" part, second the
|
||||
"value" the cache strategy can work with. Example:
|
||||
|
||||
```jinja
|
||||
{% cache 'v1/summary' 900 %}
|
||||
{# heavy lifting template stuff here, include/render other partials etc #}
|
||||
{% endcache %}
|
||||
```
|
||||
|
||||
Cache blocks can be nested:
|
||||
|
||||
```jinja
|
||||
{% cache 'v1' 900 %}
|
||||
{% for item in items %}
|
||||
{% cache 'v1' item %}
|
||||
{# ... #}
|
||||
{% endcache %}
|
||||
{% endfor %}
|
||||
{% endcache %}
|
||||
```
|
||||
|
||||
The annotation can also be an expression:
|
||||
|
||||
```jinja
|
||||
{% set version = 42 %}
|
||||
{% cache 'hello_v' ~ version 900 %}
|
||||
Hello {{ name }}!
|
||||
{% endcache %}
|
||||
```
|
||||
|
||||
## Cache strategies
|
||||
|
||||
The extension ships with a few cache strategies out of the box. Setup and usage
|
||||
of all of them is described below.
|
||||
|
||||
### Lifetime
|
||||
|
||||
See the ["Quick start"](#quick-start) for usage information of the `LifetimeCacheStrategy`.
|
||||
|
||||
### Generational
|
||||
|
||||
Strategy for generational caching.
|
||||
|
||||
In theory the strategy only saves fragments to the cache with infinite
|
||||
lifetime. The key of the strategy lies in the fact that the keys for blocks
|
||||
will change as the value for which the key is generated changes.
|
||||
|
||||
For example: entities containing a last update time, would include a timestamp
|
||||
in the key. For an interesting blog post about this type of caching see:
|
||||
http://37signals.com/svn/posts/3113-how-key-based-cache-expiration-works
|
||||
|
||||
### Blackhole
|
||||
|
||||
Strategy for development mode.
|
||||
|
||||
In development mode it often not very useful to cache fragments. The blackhole
|
||||
strategy provides an easy way to not cache anything it all. It always generates
|
||||
a new key and does not fetch or save any fragments.
|
||||
|
||||
#### Setup
|
||||
|
||||
In order to use the strategy you need to setup a `KeyGenerator` class that is
|
||||
able to generate a cache key for a given value.
|
||||
|
||||
The following naive example always assumes the value is an object with the methods
|
||||
`getId()` and `getUpdatedAt()` method. The key then composed from the class
|
||||
name, the id and the updated time of the object:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use Asm89\Twig\CacheExtension\CacheStrategy\KeyGeneratorInterface;
|
||||
|
||||
class MyKeyGenerator implements KeyGeneratorInterface
|
||||
{
|
||||
public function generateKey($value)
|
||||
{
|
||||
return get_class($value) . '_' . $value->getId() . '_' . $value->getUpdatedAt();
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Next the `GenerationalCacheStrategy` needs to be setup with the keygenerator.
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use Asm89\Twig\CacheExtension\CacheStrategy\GenerationalCacheStrategy;
|
||||
use Asm89\Twig\CacheExtension\Extension as CacheExtension;
|
||||
|
||||
$keyGenerator = new MyKeyGenerator();
|
||||
$cacheProvider = /* see Quick start */;
|
||||
$cacheStrategy = new GenerationalCacheStrategy($cacheProvider, $keyGenerator, 0 /* = infinite lifetime */);
|
||||
$cacheExtension = new CacheExtension($cacheStrategy);
|
||||
|
||||
$twig->addExtension($cacheExtension);
|
||||
```
|
||||
|
||||
#### Usage
|
||||
|
||||
The strategy expects an object as value for determining the cache key of the
|
||||
block:
|
||||
|
||||
```jinja
|
||||
{% cache 'v1/summary' item %}
|
||||
{# heavy lifting template stuff here, include/render other partials etc #}
|
||||
{% endcache %}
|
||||
```
|
||||
|
||||
### Using multiple strategies
|
||||
|
||||
Different cache strategies are useful for different usecases. It is possible to
|
||||
mix multiple strategies in an application with the
|
||||
`IndexedChainingCacheStrategy`. The strategy takes an array of `'name' =>
|
||||
$strategy` and delegates the caching to the appropriate strategy.
|
||||
|
||||
#### Setup
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use Asm89\Twig\CacheExtension\CacheStrategy\IndexedChainingCacheStrategy;
|
||||
use Asm89\Twig\CacheExtension\Extension as CacheExtension;
|
||||
|
||||
$cacheStrategy = new IndexedChainingCacheStrategy(array(
|
||||
'time' => $lifetimeCacheStrategy,
|
||||
'gen' => $generationalCacheStrategy,
|
||||
));
|
||||
$cacheExtension = new CacheExtension($cacheStrategy);
|
||||
|
||||
$twig->addExtension($cacheExtension);
|
||||
```
|
||||
|
||||
#### Usage
|
||||
|
||||
The strategy expects an array with as key the name of the strategy which it
|
||||
needs to delegate to and as value the appropriate value for the delegated
|
||||
strategy.
|
||||
|
||||
```jinja
|
||||
{# delegate to lifetime strategy #}
|
||||
{% cache 'v1/summary' {time: 300} %}
|
||||
{# heavy lifting template stuff here, include/render other partials etc #}
|
||||
{% endcache %}
|
||||
|
||||
{# delegate to generational strategy #}
|
||||
{% cache 'v1/summary' {gen: item} %}
|
||||
{# heavy lifting template stuff here, include/render other partials etc #}
|
||||
{% endcache %}
|
||||
```
|
||||
|
||||
## Implementing a cache strategy
|
||||
|
||||
Creating separate caches for different access levels, languages or other
|
||||
usecases can be done by implementing a custom cache strategy. In order to do so
|
||||
implement the `CacheProviderInterface`. It is recommended to use composition
|
||||
and wrap a custom strategy around an existing one.
|
||||
|
||||
## Authors
|
||||
|
||||
Alexander <iam.asm89@gmail.com>
|
||||
|
||||
## License
|
||||
|
||||
twig-cache-extension is licensed under the MIT License - see the LICENSE file for details
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "asm89/twig-cache-extension",
|
||||
"description": "Cache fragments of templates directly within Twig.",
|
||||
"keywords": ["twig", "cache", "extension"],
|
||||
"homepage": "https://github.com/asm89/twig-cache-extension",
|
||||
"type": "library",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Alexander",
|
||||
"email": "iam.asm89@gmail.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.3.2",
|
||||
"twig/twig": "^1.0|^2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^5.0 || ^4.8.10",
|
||||
"doctrine/cache": "~1.0"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "phpunit"
|
||||
},
|
||||
"suggest": {
|
||||
"psr/cache-implementation": "To make use of PSR-6 cache implementation via PsrCacheAdapter."
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"": "lib/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"": "test/"
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.4-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of twig-cache-extension.
|
||||
*
|
||||
* (c) Alexander <iam.asm89@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Asm89\Twig\CacheExtension\CacheProvider;
|
||||
|
||||
use Asm89\Twig\CacheExtension\CacheProviderInterface;
|
||||
use Doctrine\Common\Cache\Cache;
|
||||
|
||||
/**
|
||||
* Adapter class to use the cache classes provider by Doctrine.
|
||||
*
|
||||
* @author Alexander <iam.asm89@gmail.com>
|
||||
*/
|
||||
class DoctrineCacheAdapter implements CacheProviderInterface
|
||||
{
|
||||
private $cache;
|
||||
|
||||
/**
|
||||
* @param Cache $cache
|
||||
*/
|
||||
public function __construct(Cache $cache)
|
||||
{
|
||||
$this->cache = $cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function fetch($key)
|
||||
{
|
||||
return $this->cache->fetch($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function save($key, $value, $lifetime = 0)
|
||||
{
|
||||
return $this->cache->save($key, $value, $lifetime);
|
||||
}
|
||||
}
|
||||
Vendored
+68
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of twig-cache-extension.
|
||||
*
|
||||
* (c) Alexander <iam.asm89@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Asm89\Twig\CacheExtension\CacheProvider;
|
||||
|
||||
use Asm89\Twig\CacheExtension\CacheProviderInterface;
|
||||
use Psr\Cache\CacheItemPoolInterface;
|
||||
|
||||
/**
|
||||
* Adapter class to make extension interoperable with every PSR-6 adapter.
|
||||
*
|
||||
* @see http://php-cache.readthedocs.io/
|
||||
*
|
||||
* @author Rvanlaak <rvanlaak@gmail.com>
|
||||
*/
|
||||
class PsrCacheAdapter implements CacheProviderInterface
|
||||
{
|
||||
/**
|
||||
* @var CacheItemPoolInterface
|
||||
*/
|
||||
private $cache;
|
||||
|
||||
/**
|
||||
* @param CacheItemPoolInterface $cache
|
||||
*/
|
||||
public function __construct(CacheItemPoolInterface $cache)
|
||||
{
|
||||
$this->cache = $cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @return mixed|false
|
||||
*/
|
||||
public function fetch($key)
|
||||
{
|
||||
// PSR-6 implementation returns null, CacheProviderInterface expects false
|
||||
$item = $this->cache->getItem($key);
|
||||
if ($item->isHit()) {
|
||||
return $item->get();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param string $value
|
||||
* @param int|\DateInterval $lifetime
|
||||
* @return bool
|
||||
*/
|
||||
public function save($key, $value, $lifetime = 0)
|
||||
{
|
||||
$item = $this->cache->getItem($key);
|
||||
$item->set($value);
|
||||
$item->expiresAfter($lifetime);
|
||||
|
||||
return $this->cache->save($item);
|
||||
}
|
||||
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of twig-cache-extension.
|
||||
*
|
||||
* (c) Alexander <iam.asm89@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Asm89\Twig\CacheExtension;
|
||||
|
||||
/**
|
||||
* Cache provider interface.
|
||||
*
|
||||
* @author Alexander <iam.asm89@gmail.com>
|
||||
*/
|
||||
interface CacheProviderInterface
|
||||
{
|
||||
/**
|
||||
* @param string $key
|
||||
*
|
||||
* @return mixed False, if there was no value to be fetched. Null or a string otherwise.
|
||||
*/
|
||||
public function fetch($key);
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param string $value
|
||||
* @param integer $lifetime
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function save($key, $value, $lifetime = 0);
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of twig-cache-extension.
|
||||
*
|
||||
* (c) Alexander <iam.asm89@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Asm89\Twig\CacheExtension\CacheStrategy;
|
||||
|
||||
use Asm89\Twig\CacheExtension\CacheStrategyInterface;
|
||||
|
||||
/**
|
||||
* CacheStrategy which doesn't cache at all
|
||||
*
|
||||
* This strategy can be used in development mode, e.g. editing twig templates,
|
||||
* to prevent previously cached versions from being rendered.
|
||||
*
|
||||
* @see https://github.com/asm89/twig-cache-extension/pull/29
|
||||
*
|
||||
* @author Hagen Hübel <hhuebel@itinance.com>
|
||||
*
|
||||
* @package Asm89\Twig\CacheExtension\CacheStrategy
|
||||
*/
|
||||
class BlackholeCacheStrategy implements CacheStrategyInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function fetchBlock($key)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function generateKey($annotation, $value)
|
||||
{
|
||||
return microtime(true) . mt_rand();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function saveBlock($key, $block)
|
||||
{
|
||||
// fire and forget
|
||||
}
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of twig-cache-extension.
|
||||
*
|
||||
* (c) Alexander <iam.asm89@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Asm89\Twig\CacheExtension\CacheStrategy;
|
||||
|
||||
use Asm89\Twig\CacheExtension\CacheProviderInterface;
|
||||
use Asm89\Twig\CacheExtension\CacheStrategyInterface;
|
||||
use Asm89\Twig\CacheExtension\Exception\InvalidCacheKeyException;
|
||||
|
||||
/**
|
||||
* Strategy for generational caching.
|
||||
*
|
||||
* In theory the strategy only saves fragments to the cache with infinite
|
||||
* lifetime. The key of the strategy lies in the fact that the keys for blocks
|
||||
* will change as the value for which the key is generated changes.
|
||||
*
|
||||
* For example: entities containing a last update time, would include a
|
||||
* timestamp in the key.
|
||||
*
|
||||
* @see http://37signals.com/svn/posts/3113-how-key-based-cache-expiration-works
|
||||
*
|
||||
* @author Alexander <iam.asm89@gmail.com>
|
||||
*/
|
||||
class GenerationalCacheStrategy implements CacheStrategyInterface
|
||||
{
|
||||
private $keyGenerator;
|
||||
private $cache;
|
||||
private $lifetime;
|
||||
|
||||
/**
|
||||
* @param CacheProviderInterface $cache
|
||||
* @param KeyGeneratorInterface $keyGenerator
|
||||
* @param integer $lifetime
|
||||
*/
|
||||
public function __construct(CacheProviderInterface $cache, KeyGeneratorInterface $keyGenerator, $lifetime = 0)
|
||||
{
|
||||
$this->keyGenerator = $keyGenerator;
|
||||
$this->cache = $cache;
|
||||
$this->lifetime = $lifetime;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function fetchBlock($key)
|
||||
{
|
||||
return $this->cache->fetch($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function generateKey($annotation, $value)
|
||||
{
|
||||
$key = $this->keyGenerator->generateKey($value);
|
||||
|
||||
if (null === $key) {
|
||||
throw new InvalidCacheKeyException();
|
||||
}
|
||||
|
||||
return $annotation . '__GCS__' . $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function saveBlock($key, $block)
|
||||
{
|
||||
return $this->cache->save($key, $block, $this->lifetime);
|
||||
}
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of twig-cache-extension.
|
||||
*
|
||||
* (c) Alexander <iam.asm89@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Asm89\Twig\CacheExtension\CacheStrategy;
|
||||
|
||||
use Asm89\Twig\CacheExtension\CacheStrategyInterface;
|
||||
use Asm89\Twig\CacheExtension\Exception\NonExistingStrategyException;
|
||||
use Asm89\Twig\CacheExtension\Exception\NonExistingStrategyKeyException;
|
||||
|
||||
/**
|
||||
* Combines several configured cache strategies.
|
||||
*
|
||||
* Useful for combining for example generational cache strategy with a lifetime
|
||||
* cache strategy, but also useful when combining several generational cache
|
||||
* strategies which differ on cache lifetime (infinite, 1hr, 5m).
|
||||
*
|
||||
* @author Alexander <iam.asm89@gmail.com>
|
||||
*/
|
||||
class IndexedChainingCacheStrategy implements CacheStrategyInterface
|
||||
{
|
||||
/**
|
||||
* @var CacheStrategyInterface[]
|
||||
*/
|
||||
private $strategies;
|
||||
|
||||
/**
|
||||
* @param array $strategies
|
||||
*/
|
||||
public function __construct(array $strategies)
|
||||
{
|
||||
$this->strategies = $strategies;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function fetchBlock($key)
|
||||
{
|
||||
return $this->strategies[$key['strategyKey']]->fetchBlock($key['key']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function generateKey($annotation, $value)
|
||||
{
|
||||
if (!is_array($value) || null === $strategyKey = key($value)) {
|
||||
throw new NonExistingStrategyKeyException();
|
||||
}
|
||||
|
||||
if (!isset($this->strategies[$strategyKey])) {
|
||||
throw new NonExistingStrategyException($strategyKey);
|
||||
}
|
||||
|
||||
return array(
|
||||
'strategyKey' => $strategyKey,
|
||||
'key' => $this->strategies[$strategyKey]->generateKey($annotation, current($value)),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function saveBlock($key, $block)
|
||||
{
|
||||
return $this->strategies[$key['strategyKey']]->saveBlock($key['key'], $block);
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of twig-cache-extension.
|
||||
*
|
||||
* (c) Alexander <iam.asm89@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Asm89\Twig\CacheExtension\CacheStrategy;
|
||||
|
||||
/**
|
||||
* Generates a key for a given value.
|
||||
*
|
||||
* @author Alexander <iam.asm89@gmail.com>
|
||||
*/
|
||||
interface KeyGeneratorInterface
|
||||
{
|
||||
/**
|
||||
* Generate a cache key for a given value.
|
||||
*
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function generateKey($value);
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of twig-cache-extension.
|
||||
*
|
||||
* (c) Alexander <iam.asm89@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Asm89\Twig\CacheExtension\CacheStrategy;
|
||||
|
||||
use Asm89\Twig\CacheExtension\CacheProviderInterface;
|
||||
use Asm89\Twig\CacheExtension\CacheStrategyInterface;
|
||||
use Asm89\Twig\CacheExtension\Exception\InvalidCacheLifetimeException;
|
||||
|
||||
/**
|
||||
* Strategy for caching with a pre-defined lifetime.
|
||||
*
|
||||
* The value passed to the strategy is the lifetime of the cache block in
|
||||
* seconds.
|
||||
*
|
||||
* @author Alexander <iam.asm89@gmail.com>
|
||||
*/
|
||||
class LifetimeCacheStrategy implements CacheStrategyInterface
|
||||
{
|
||||
private $cache;
|
||||
|
||||
/**
|
||||
* @param CacheProviderInterface $cache
|
||||
*/
|
||||
public function __construct(CacheProviderInterface $cache)
|
||||
{
|
||||
$this->cache = $cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function fetchBlock($key)
|
||||
{
|
||||
return $this->cache->fetch($key['key']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function generateKey($annotation, $value)
|
||||
{
|
||||
if (!is_numeric($value)) {
|
||||
throw new InvalidCacheLifetimeException($value);
|
||||
}
|
||||
|
||||
return array(
|
||||
'lifetime' => $value,
|
||||
'key' => '__LCS__' . $annotation,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function saveBlock($key, $block)
|
||||
{
|
||||
return $this->cache->save($key['key'], $block, $key['lifetime']);
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of twig-cache-extension.
|
||||
*
|
||||
* (c) Alexander <iam.asm89@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Asm89\Twig\CacheExtension;
|
||||
|
||||
/**
|
||||
* Cache strategy interface.
|
||||
*
|
||||
* @author Alexander <iam.asm89@gmail.com>
|
||||
*/
|
||||
interface CacheStrategyInterface
|
||||
{
|
||||
/**
|
||||
* Fetch the block for a given key.
|
||||
*
|
||||
* @param mixed $key
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function fetchBlock($key);
|
||||
|
||||
/**
|
||||
* Generate a key for the value.
|
||||
*
|
||||
* @param string $annotation
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function generateKey($annotation, $value);
|
||||
|
||||
/**
|
||||
* Save the contents of a rendered block.
|
||||
*
|
||||
* @param mixed $key
|
||||
* @param string $block
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function saveBlock($key, $block);
|
||||
}
|
||||
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of twig-cache-extension.
|
||||
*
|
||||
* (c) Alexander <iam.asm89@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Asm89\Twig\CacheExtension\Exception;
|
||||
|
||||
/**
|
||||
* @todo Replace \RuntimeException with \InvalidArgumentException at version 2.0
|
||||
*/
|
||||
class BaseException extends \RuntimeException
|
||||
{
|
||||
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of twig-cache-extension.
|
||||
*
|
||||
* (c) Alexander <iam.asm89@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Asm89\Twig\CacheExtension\Exception;
|
||||
|
||||
class InvalidCacheKeyException extends BaseException
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct($message = 'Key generator did not return a key.', $code = 0, \Exception $previous = null)
|
||||
{
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of twig-cache-extension.
|
||||
*
|
||||
* (c) Alexander <iam.asm89@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Asm89\Twig\CacheExtension\Exception;
|
||||
|
||||
class InvalidCacheLifetimeException extends BaseException
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct($lifetime, $code = 0, \Exception $previous = null)
|
||||
{
|
||||
parent::__construct(sprintf('Value "%s" is not a valid lifetime.', gettype($lifetime)), $code, $previous);
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of twig-cache-extension.
|
||||
*
|
||||
* (c) Alexander <iam.asm89@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Asm89\Twig\CacheExtension\Exception;
|
||||
|
||||
class NonExistingStrategyException extends BaseException
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct($strategyKey, $code = 0, \Exception $previous = null)
|
||||
{
|
||||
parent::__construct(sprintf('No strategy configured with key "%s".', $strategyKey), $code, $previous);
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of twig-cache-extension.
|
||||
*
|
||||
* (c) Alexander <iam.asm89@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Asm89\Twig\CacheExtension\Exception;
|
||||
|
||||
class NonExistingStrategyKeyException extends BaseException
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct($message = 'No strategy key found in value.', $code = 0, \Exception $previous = null)
|
||||
{
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of twig-cache-extension.
|
||||
*
|
||||
* (c) Alexander <iam.asm89@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Asm89\Twig\CacheExtension;
|
||||
|
||||
/**
|
||||
* Extension for caching template blocks with twig.
|
||||
*
|
||||
* @author Alexander <iam.asm89@gmail.com>
|
||||
*/
|
||||
class Extension extends \Twig_Extension
|
||||
{
|
||||
private $cacheStrategy;
|
||||
|
||||
/**
|
||||
* @param CacheStrategyInterface $cacheStrategy
|
||||
*/
|
||||
public function __construct(CacheStrategyInterface $cacheStrategy)
|
||||
{
|
||||
$this->cacheStrategy = $cacheStrategy;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return CacheStrategyInterface
|
||||
*/
|
||||
public function getCacheStrategy()
|
||||
{
|
||||
return $this->cacheStrategy;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
if (version_compare(\Twig_Environment::VERSION, '1.26.0', '>=')) {
|
||||
return __CLASS__;
|
||||
}
|
||||
return 'asm89_cache';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getTokenParsers()
|
||||
{
|
||||
return array(
|
||||
new TokenParser\Cache(),
|
||||
);
|
||||
}
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of twig-cache-extension.
|
||||
*
|
||||
* (c) Alexander <iam.asm89@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Asm89\Twig\CacheExtension\Node;
|
||||
|
||||
/**
|
||||
* Cache twig node.
|
||||
*
|
||||
* @author Alexander <iam.asm89@gmail.com>
|
||||
*/
|
||||
class CacheNode extends \Twig_Node
|
||||
{
|
||||
private static $cacheCount = 1;
|
||||
|
||||
/**
|
||||
* @param \Twig_Node_Expression $annotation
|
||||
* @param \Twig_Node_Expression $keyInfo
|
||||
* @param \Twig_NodeInterface $body
|
||||
* @param integer $lineno
|
||||
* @param string $tag
|
||||
*/
|
||||
public function __construct(\Twig_Node_Expression $annotation, \Twig_Node_Expression $keyInfo, \Twig_Node $body, $lineno, $tag = null)
|
||||
{
|
||||
parent::__construct(array('key_info' => $keyInfo, 'body' => $body, 'annotation' => $annotation), array(), $lineno, $tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function compile(\Twig_Compiler $compiler)
|
||||
{
|
||||
$i = self::$cacheCount++;
|
||||
|
||||
if (version_compare(\Twig_Environment::VERSION, '1.26.0', '>=')) {
|
||||
$extension = 'Asm89\Twig\CacheExtension\Extension';
|
||||
} else {
|
||||
$extension = 'asm89_cache';
|
||||
}
|
||||
|
||||
$compiler
|
||||
->addDebugInfo($this)
|
||||
->write("\$asm89CacheStrategy".$i." = \$this->env->getExtension('{$extension}')->getCacheStrategy();\n")
|
||||
->write("\$asm89Key".$i." = \$asm89CacheStrategy".$i."->generateKey(")
|
||||
->subcompile($this->getNode('annotation'))
|
||||
->raw(", ")
|
||||
->subcompile($this->getNode('key_info'))
|
||||
->write(");\n")
|
||||
->write("\$asm89CacheBody".$i." = \$asm89CacheStrategy".$i."->fetchBlock(\$asm89Key".$i.");\n")
|
||||
->write("if (\$asm89CacheBody".$i." === false) {\n")
|
||||
->indent()
|
||||
->write("ob_start();\n")
|
||||
->indent()
|
||||
->subcompile($this->getNode('body'))
|
||||
->outdent()
|
||||
->write("\n")
|
||||
->write("\$asm89CacheBody".$i." = ob_get_clean();\n")
|
||||
->write("\$asm89CacheStrategy".$i."->saveBlock(\$asm89Key".$i.", \$asm89CacheBody".$i.");\n")
|
||||
->outdent()
|
||||
->write("}\n")
|
||||
->write("echo \$asm89CacheBody".$i.";\n")
|
||||
;
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of twig-cache-extension.
|
||||
*
|
||||
* (c) Alexander <iam.asm89@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Asm89\Twig\CacheExtension\TokenParser;
|
||||
|
||||
use Asm89\Twig\CacheExtension\Node\CacheNode;
|
||||
|
||||
/**
|
||||
* Parser for cache/endcache blocks.
|
||||
*
|
||||
* @author Alexander <iam.asm89@gmail.com>
|
||||
*/
|
||||
class Cache extends \Twig_TokenParser
|
||||
{
|
||||
/**
|
||||
* @param \Twig_Token $token
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function decideCacheEnd(\Twig_Token $token)
|
||||
{
|
||||
return $token->test('endcache');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getTag()
|
||||
{
|
||||
return 'cache';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function parse(\Twig_Token $token)
|
||||
{
|
||||
$lineno = $token->getLine();
|
||||
$stream = $this->parser->getStream();
|
||||
|
||||
$annotation = $this->parser->getExpressionParser()->parseExpression();
|
||||
|
||||
$key = $this->parser->getExpressionParser()->parseExpression();
|
||||
|
||||
$stream->expect(\Twig_Token::BLOCK_END_TYPE);
|
||||
$body = $this->parser->subparse(array($this, 'decideCacheEnd'), true);
|
||||
$stream->expect(\Twig_Token::BLOCK_END_TYPE);
|
||||
|
||||
return new CacheNode($annotation, $key, $body, $lineno, $this->getTag());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<phpunit backupGlobals="false"
|
||||
backupStaticAttributes="false"
|
||||
colors="true"
|
||||
convertErrorsToExceptions="true"
|
||||
convertNoticesToExceptions="true"
|
||||
convertWarningsToExceptions="true"
|
||||
processIsolation="false"
|
||||
stopOnFailure="false"
|
||||
syntaxCheck="false"
|
||||
bootstrap="test/bootstrap.php"
|
||||
>
|
||||
<testsuites>
|
||||
<testsuite name="Twig Cache Extension Test Suite">
|
||||
<directory>./test/Asm89/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<filter>
|
||||
<whitelist>
|
||||
<directory suffix=".php">./lib/</directory>
|
||||
</whitelist>
|
||||
</filter>
|
||||
</phpunit>
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of twig-cache-extension.
|
||||
*
|
||||
* (c) Alexander <iam.asm89@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Asm89\Twig\CacheExtension\Tests\CacheProvider;
|
||||
|
||||
use Asm89\Twig\CacheExtension\CacheProvider\DoctrineCacheAdapter;
|
||||
|
||||
class DoctrineCacheAdapterTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testFetch()
|
||||
{
|
||||
$cacheMock = $this->createCacheMock();
|
||||
$cacheMock->expects($this->any())
|
||||
->method('fetch')
|
||||
->will($this->returnValue('fromcache'));
|
||||
|
||||
$cache = new DoctrineCacheAdapter($cacheMock);
|
||||
|
||||
$this->assertEquals('fromcache', $cache->fetch('test'));
|
||||
}
|
||||
|
||||
public function testSave()
|
||||
{
|
||||
$cacheMock = $this->createCacheMock();
|
||||
$cacheMock->expects($this->once())
|
||||
->method('save')
|
||||
->with('key', 'value', 42);
|
||||
|
||||
$cache = new DoctrineCacheAdapter($cacheMock);
|
||||
|
||||
$cache->save('key', 'value', 42);
|
||||
}
|
||||
|
||||
public function createCacheMock()
|
||||
{
|
||||
return $this->createMock('Doctrine\Common\Cache\Cache');
|
||||
}
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of twig-cache-extension.
|
||||
*
|
||||
* (c) Alexander <iam.asm89@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Asm89\Twig\CacheExtension\Tests\CacheStrategy;
|
||||
|
||||
use Asm89\Twig\CacheExtension\CacheStrategy\GenerationalCacheStrategy;
|
||||
|
||||
class GenerationalCacheStrategyTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
private $keyGeneratorMock;
|
||||
private $cacheProviderMock;
|
||||
|
||||
public function createCacheStrategy($lifetime = 0)
|
||||
{
|
||||
$this->keyGeneratorMock = $this->createKeyGeneratorMock();
|
||||
$this->cacheProviderMock = $this->createCacheProviderMock();
|
||||
|
||||
return new GenerationalCacheStrategy($this->cacheProviderMock, $this->keyGeneratorMock, $lifetime);
|
||||
}
|
||||
|
||||
public function testGenerateKeyContainsAnnotation()
|
||||
{
|
||||
$strategy = $this->createCacheStrategy();
|
||||
$this->keyGeneratorMock->expects($this->any())
|
||||
->method('generateKey')
|
||||
->will($this->returnValue('foo'));
|
||||
|
||||
$this->assertEquals('v42__GCS__foo', $strategy->generateKey('v42', 'value'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Asm89\Twig\CacheExtension\Exception\InvalidCacheKeyException
|
||||
*/
|
||||
public function testGenerationKeyThrowsExceptionWhenKeyGeneratorReturnsNull()
|
||||
{
|
||||
$strategy = $this->createCacheStrategy();
|
||||
|
||||
$strategy->generateKey('v42', 'value');
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getLifeTimes
|
||||
*/
|
||||
public function testSaveBlockUsesConfiguredLifetime($lifetime)
|
||||
{
|
||||
$strategy = $this->createCacheStrategy($lifetime);
|
||||
$this->cacheProviderMock->expects($this->any())
|
||||
->method('save')
|
||||
->with('key', 'value', $lifetime)
|
||||
->will($this->returnValue('foo'));
|
||||
|
||||
$strategy->saveBlock('key', 'value');
|
||||
}
|
||||
|
||||
public function getLifetimes()
|
||||
{
|
||||
return array(
|
||||
array(0),
|
||||
array(60),
|
||||
);
|
||||
}
|
||||
|
||||
public function createKeyGeneratorMock()
|
||||
{
|
||||
return $this->createMock('Asm89\Twig\CacheExtension\CacheStrategy\KeyGeneratorInterface');
|
||||
}
|
||||
|
||||
public function createCacheProviderMock()
|
||||
{
|
||||
return $this->createMock('Asm89\Twig\CacheExtension\CacheProviderInterface');
|
||||
}
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of twig-cache-extension.
|
||||
*
|
||||
* (c) Alexander <iam.asm89@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Asm89\Twig\CacheExtension\Tests\CacheStrategy;
|
||||
|
||||
use Asm89\Twig\CacheExtension\CacheStrategy\IndexedChainingCacheStrategy;
|
||||
|
||||
class IndexedChainingCacheStrategyTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
private $cacheStrategyMocks = array();
|
||||
|
||||
public function createCacheStrategy()
|
||||
{
|
||||
foreach($this->getStrategies() as $config) {
|
||||
list($key, $return) = $config;
|
||||
|
||||
$cacheStrategyMock = $this->createCacheStrategyMock();
|
||||
$cacheStrategyMock->expects($this->any())
|
||||
->method('generateKey')
|
||||
->will($this->returnValue($return));
|
||||
|
||||
$this->cacheStrategyMocks[$key] = $cacheStrategyMock;
|
||||
}
|
||||
|
||||
return new IndexedChainingCacheStrategy($this->cacheStrategyMocks);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getStrategies
|
||||
*/
|
||||
public function testGenerateKeyProxiesToAppropriateStrategy($key, $return)
|
||||
{
|
||||
$strategy = $this->createCacheStrategy();
|
||||
|
||||
$generatedKey = $strategy->generateKey('v42', array($key => 'proxied_value'));
|
||||
|
||||
$this->assertEquals($return, $generatedKey['key']);
|
||||
$this->assertEquals($key, $generatedKey['strategyKey']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Asm89\Twig\CacheExtension\Exception\NonExistingStrategyKeyException
|
||||
*/
|
||||
public function testGenerateKeyThrowsExceptionOnMissingKey()
|
||||
{
|
||||
$strategy = $this->createCacheStrategy();
|
||||
$strategy->generateKey('v42', 'proxied_value');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Asm89\Twig\CacheExtension\Exception\NonExistingStrategyException
|
||||
* @expectedExceptionMessage No strategy configured with key "unknown"
|
||||
*/
|
||||
public function testGenerateKeyThrowsExceptionOnUnknownKey()
|
||||
{
|
||||
$strategy = $this->createCacheStrategy();
|
||||
$strategy->generateKey('v42', array('unknown' => 'proxied_value'));
|
||||
}
|
||||
|
||||
public function getStrategies()
|
||||
{
|
||||
return array(
|
||||
array('foo', 'foo_key'),
|
||||
array('bar', 'bar_key'),
|
||||
);
|
||||
}
|
||||
|
||||
public function createCacheStrategyMock()
|
||||
{
|
||||
return $this->createMock('Asm89\Twig\CacheExtension\CacheStrategyInterface');
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of twig-cache-extension.
|
||||
*
|
||||
* (c) Alexander <iam.asm89@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Asm89\Twig\CacheExtension\Tests\CacheStrategy;
|
||||
|
||||
use Asm89\Twig\CacheExtension\CacheStrategy\LifetimeCacheStrategy;
|
||||
|
||||
class LifetimeCacheStrategyTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
private $cacheProviderMock;
|
||||
|
||||
public function createCacheStrategy()
|
||||
{
|
||||
$this->cacheProviderMock = $this->createCacheProviderMock();
|
||||
|
||||
return new LifetimeCacheStrategy($this->cacheProviderMock);
|
||||
}
|
||||
|
||||
public function testGenerateKeyUsesGivenLifetime()
|
||||
{
|
||||
$strategy = $this->createCacheStrategy();
|
||||
|
||||
$key = $strategy->generateKey('v42', 42);
|
||||
|
||||
$this->assertEquals(42, $key['lifetime']);
|
||||
}
|
||||
|
||||
public function testGenerateKeyAnnotatesKey()
|
||||
{
|
||||
$strategy = $this->createCacheStrategy();
|
||||
|
||||
$key = $strategy->generateKey('the_annotation', 42);
|
||||
|
||||
$this->assertContains('the_annotation', $key['key']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getInvalidLifetimeValues
|
||||
* @expectedException \Asm89\Twig\CacheExtension\Exception\InvalidCacheLifetimeException
|
||||
*/
|
||||
public function testGenerateKeyThrowsExceptionWhenNoLifetimeProvided($value)
|
||||
{
|
||||
$strategy = $this->createCacheStrategy();
|
||||
|
||||
$strategy->generateKey('v42', $value);
|
||||
}
|
||||
|
||||
public function getInvalidLifetimeValues()
|
||||
{
|
||||
return array(
|
||||
array('foo'),
|
||||
array(array('foo')),
|
||||
);
|
||||
}
|
||||
|
||||
public function createCacheProviderMock()
|
||||
{
|
||||
return $this->createMock('Asm89\Twig\CacheExtension\CacheProviderInterface');
|
||||
}
|
||||
}
|
||||
Vendored
+182
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of twig-cache-extension.
|
||||
*
|
||||
* (c) Alexander <iam.asm89@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Asm89\Twig\CacheExtension\Tests;
|
||||
|
||||
use Asm89\Twig\CacheExtension\CacheProvider\DoctrineCacheAdapter;
|
||||
use Asm89\Twig\CacheExtension\CacheStrategy\KeyGeneratorInterface;
|
||||
use Asm89\Twig\CacheExtension\CacheStrategy\GenerationalCacheStrategy;
|
||||
use Asm89\Twig\CacheExtension\CacheStrategy\IndexedChainingCacheStrategy;
|
||||
use Asm89\Twig\CacheExtension\CacheStrategy\LifetimeCacheStrategy;
|
||||
use Asm89\Twig\CacheExtension\Extension;
|
||||
use Doctrine\Common\Cache\ArrayCache;
|
||||
use Twig_Loader_Filesystem;
|
||||
use Twig_Environment;
|
||||
|
||||
class FunctionalExtensionTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
private $cache;
|
||||
|
||||
protected function createCacheProvider()
|
||||
{
|
||||
$this->cache = new ArrayCache();
|
||||
|
||||
return new DoctrineCacheAdapter($this->cache);
|
||||
}
|
||||
|
||||
protected function createCacheStrategy($name = null)
|
||||
{
|
||||
$cacheProvider = $this->createCacheProvider();
|
||||
$keyGenerator = $this->createKeyGenerator();
|
||||
$lifetime = 0;
|
||||
|
||||
switch ($name) {
|
||||
case 'time':
|
||||
return new LifetimeCacheStrategy($cacheProvider);
|
||||
case 'indexed':
|
||||
return new IndexedChainingCacheStrategy(array(
|
||||
'gcs' => new GenerationalCacheStrategy($cacheProvider, $keyGenerator, $lifetime),
|
||||
'time' => new LifetimeCacheStrategy($cacheProvider),
|
||||
));
|
||||
default:
|
||||
return new GenerationalCacheStrategy($cacheProvider, $keyGenerator, $lifetime);
|
||||
}
|
||||
}
|
||||
|
||||
protected function createKeyGenerator()
|
||||
{
|
||||
return new KeyGenerator();
|
||||
}
|
||||
|
||||
protected function createTwig($cacheStrategyName = null)
|
||||
{
|
||||
$loader = new Twig_Loader_Filesystem(__DIR__ . '/fixtures/');
|
||||
$twig = new Twig_Environment($loader);
|
||||
|
||||
$cacheExtension = new Extension($this->createCacheStrategy($cacheStrategyName));
|
||||
|
||||
$twig->addExtension($cacheExtension);
|
||||
|
||||
return $twig;
|
||||
}
|
||||
|
||||
protected function getValue($value, $updatedAt)
|
||||
{
|
||||
return new Value($value, $updatedAt);
|
||||
}
|
||||
|
||||
public function testCachesWithSameCacheKey()
|
||||
{
|
||||
$twig = $this->createTwig();
|
||||
|
||||
$rendered = $twig->render('gcs_value.twig', array('value' => $this->getValue('asm89', 1)));
|
||||
$this->assertEquals('Hello asm89!', $rendered);
|
||||
|
||||
$rendered2 = $twig->render('gcs_value.twig', array('value' => $this->getValue('fabpot', 1)));
|
||||
$this->assertEquals('Hello asm89!', $rendered2);
|
||||
}
|
||||
|
||||
public function testDifferentCacheOnDifferentAnnotation()
|
||||
{
|
||||
$twig = $this->createTwig();
|
||||
|
||||
$rendered = $twig->render('gcs_value.twig', array('value' => $this->getValue('asm89', 1)));
|
||||
$this->assertEquals('Hello asm89!', $rendered);
|
||||
|
||||
$rendered2 = $twig->render('gcs_value.twig', array('value' => $this->getValue('fabpot', 1)));
|
||||
$this->assertEquals('Hello asm89!', $rendered2);
|
||||
|
||||
$rendered3 = $twig->render('gcs_value_v2.twig', array('value' => $this->getValue('fabpot', 1)));
|
||||
$this->assertEquals('Hello fabpot!', $rendered3);
|
||||
}
|
||||
|
||||
public function testLifetimeCacheStrategy()
|
||||
{
|
||||
$twig = $this->createTwig('time');
|
||||
|
||||
$rendered = $twig->render('lcs_value.twig', array('value' => $this->getValue('asm89', 1)));
|
||||
$this->assertEquals('Hello asm89!', $rendered);
|
||||
|
||||
$rendered2 = $twig->render('lcs_value.twig', array('value' => $this->getValue('fabpot', 1)));
|
||||
$this->assertEquals('Hello asm89!', $rendered2);
|
||||
|
||||
$this->cache->flushAll();
|
||||
|
||||
$rendered3 = $twig->render('lcs_value.twig', array('value' => $this->getValue('fabpot', 1)));
|
||||
$this->assertEquals('Hello fabpot!', $rendered3);
|
||||
}
|
||||
|
||||
public function testIndexedChainingStrategy()
|
||||
{
|
||||
$twig = $this->createTwig('indexed');
|
||||
|
||||
$rendered = $twig->render('ics_value.twig', array('value' => $this->getValue('asm89', 1)));
|
||||
$this->assertEquals('Hello asm89!', $rendered);
|
||||
|
||||
$rendered2 = $twig->render('ics_value.twig', array('value' => $this->getValue('fabpot', 1)));
|
||||
$this->assertEquals('Hello asm89!', $rendered2);
|
||||
|
||||
$this->cache->flushAll();
|
||||
|
||||
$rendered3 = $twig->render('ics_value.twig', array('value' => $this->getValue('fabpot', 1)));
|
||||
$this->assertEquals('Hello fabpot!', $rendered3);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException Twig_Error_Runtime
|
||||
* @expectedExceptionMessage An exception has been thrown during the rendering of a template ("No strategy key found in value.")
|
||||
*/
|
||||
public function testIndexedChainingStrategyNeedsKey()
|
||||
{
|
||||
$twig = $this->createTwig('indexed');
|
||||
|
||||
$twig->render('ics_no_key.twig', array('value' => $this->getValue('asm89', 1)));
|
||||
}
|
||||
|
||||
public function testAnnotationExpression()
|
||||
{
|
||||
$twig = $this->createTwig('indexed');
|
||||
|
||||
$rendered = $twig->render('annotation_expression.twig', array('value' => $this->getValue('asm89', 1), 'version' => 1));
|
||||
$this->assertEquals('Hello asm89!Hello asm89!', $rendered);
|
||||
}
|
||||
}
|
||||
|
||||
class KeyGenerator implements KeyGeneratorInterface
|
||||
{
|
||||
public function generateKey($value)
|
||||
{
|
||||
return get_class($value) . '_' . $value->getUpdatedAt();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Value
|
||||
{
|
||||
private $value;
|
||||
private $updatedAt;
|
||||
|
||||
public function __construct($value, $updatedAt)
|
||||
{
|
||||
$this->value = $value;
|
||||
$this->updatedAt = $updatedAt;
|
||||
}
|
||||
|
||||
public function getUpdatedAt()
|
||||
{
|
||||
return $this->updatedAt;
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
{%- set k = "v'" ~ version -%}
|
||||
{%- cache k {time: 10} -%}Hello {{ value }}!{%- endcache -%}
|
||||
{%- cache 'bob'|capitalize {gcs: value} %}Hello {{ value }}!{%- endcache -%}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
{% set annotation = 'v1' %}
|
||||
{% cache annotation value %}Hello {{ value }}!{% endcache %}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{% cache 'v1' value %}Hello {{ value }}!{% endcache %}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{% cache 'v2' value %}Hello {{ value }}!{% endcache %}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{% cache 'v1' 10 %}Hello {{ value }}!{% endcache %}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{% cache 'v1' {time: 10} %}Hello {{ value }}!{% endcache %}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{% cache 'v1' 10 %}Hello {{ value }}!{% endcache %}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of twig-cache-extension.
|
||||
*
|
||||
* (c) Alexander <iam.asm89@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
if (file_exists($file = __DIR__.'/../vendor/autoload.php')) {
|
||||
$autoload = require_once $file;
|
||||
} else {
|
||||
throw new RuntimeException('Install dependencies to run test suite.');
|
||||
}
|
||||
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
filter:
|
||||
paths: ["src/*"]
|
||||
tools:
|
||||
external_code_coverage: true
|
||||
php_code_coverage: true
|
||||
php_sim: true
|
||||
php_mess_detector: true
|
||||
php_pdepend: true
|
||||
php_analyzer: true
|
||||
php_cpd: true
|
||||
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
sudo: false
|
||||
language: php
|
||||
php:
|
||||
- 5.6
|
||||
- hhvm
|
||||
- 7
|
||||
before_script:
|
||||
- composer self-update
|
||||
- composer install
|
||||
script:
|
||||
- phpunit --coverage-clover=coverage.clover
|
||||
after_script:
|
||||
- wget https://scrutinizer-ci.com/ocular.phar
|
||||
- php ocular.phar code-coverage:upload --format=php-clover coverage.clover
|
||||
Vendored
+66
@@ -0,0 +1,66 @@
|
||||
# CHANGELOG
|
||||
|
||||
## 3.0.0
|
||||
|
||||
- Final release. No changes after first beta.
|
||||
|
||||
## 3.0.0-beta1
|
||||
|
||||
- Removed `aura/installer-default` from composer.json which was used by aura framework version 1 to install in package folder.
|
||||
- Fixes [issue 17](https://github.com/auraphp/Aura.Intl/issues/17) by removing Aura.Di wiring trait tests.
|
||||
- Changed directory structure from PSR-0 to PSR-4.
|
||||
- Supported PHP version : 5.6+.
|
||||
- There is no other BC breaks from 1.x version
|
||||
|
||||
|
||||
## 1.1.1
|
||||
|
||||
Hygiene release: Composer update.
|
||||
|
||||
## 1.1.0
|
||||
|
||||
- Merge pull request #11 from lorenzo/feature/format-key. If Translator::translate() cannot translate, it now returns the incoming translation key as the translated message. This makes it easier to use the key as a human readable string, which can contain formatting placeholders. With this feature the developer will be able to see immediate feedback of their translation messages without having to translate to a specific locale beforehand.
|
||||
|
||||
## 1.0.1
|
||||
|
||||
Hygiene release.
|
||||
|
||||
- Merge pull request #10 from harikt/fixdoc; fixes #9, add a test for failure. The expected exception need to be fixed.
|
||||
|
||||
- Merge pull request #8 from harikt/v2config, adding v2 config files.
|
||||
|
||||
- Merge pull request #7 from harikt/issue6, add a test to show it works. Thank you @samdark.
|
||||
|
||||
- Point travis status badge to develop branch
|
||||
|
||||
## 1.0.0
|
||||
|
||||
- [CHG] When interpolating array values into strings, now converts the array
|
||||
to comma-separated values instead of printing 'Array'.
|
||||
|
||||
- [DOC] PHP 5.5 appears to be able to format strings with missing token
|
||||
replacements; updated tests to account for this.
|
||||
|
||||
## 1.0.0-beta2
|
||||
|
||||
- [FIX] Multiple typo fixes; thanks, @pborreli.
|
||||
|
||||
- [CHG] Use {foo} instead of {:foo} in line with PSR-3 placeholders.
|
||||
|
||||
- [NEW] Add a PackageFactory
|
||||
|
||||
- [NEW] Add more-detailed exception classes
|
||||
|
||||
- [ADD] In config, add 'intl_package_factory' as a service
|
||||
|
||||
- [CHG] Throw IcuVersionTooLow Exception if IntlFormatter is instantiated with
|
||||
ICU Version lower than 4.8; Skip all IntlFormatter tests if the intl
|
||||
extension is not loaded. Thanks, @mapthegod.
|
||||
|
||||
- [CHG] In the FormatterLocator, registry entries *must* be wrapped in a
|
||||
callable from now on.
|
||||
|
||||
|
||||
## 1.0.0-beta1
|
||||
|
||||
Initial release.
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
# Contributing
|
||||
|
||||
We are happy to review any contributions you want to make. When contributing, please follow the rules outlined at <http://auraphp.com/contributing>.
|
||||
|
||||
The time between submitting a contribution and its review one may be extensive; do not be discouraged if there is not immediate feedback.
|
||||
|
||||
Thanks!
|
||||
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017, Aura for PHP
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
Vendored
+59
@@ -0,0 +1,59 @@
|
||||
# Aura.Intl
|
||||
|
||||
The Aura.Intl package provides internationalization (I18N) tools, specifically
|
||||
package-oriented per-locale message translation.
|
||||
|
||||
## Installation and Autoloading
|
||||
|
||||
This package is installable and PSR-4 autoloadable via Composer as
|
||||
[aura/intl][].
|
||||
|
||||
Alternatively, [download a release][], or clone this repository, then map the
|
||||
`Aura\Intl\` namespace to the package `src/` directory.
|
||||
|
||||
## Dependencies
|
||||
|
||||
This package requires PHP 5.6 or later; it has been tested on PHP 5.6, 7.0
|
||||
and HHVM. We recommend using the latest available version of PHP as a matter of
|
||||
principle.
|
||||
|
||||
Aura library packages may sometimes depend on external interfaces, but never on
|
||||
external implementations. This allows compliance with community standards
|
||||
without compromising flexibility. For specifics, please examine the package
|
||||
[composer.json][] file.
|
||||
|
||||
## Quality
|
||||
|
||||
[](https://scrutinizer-ci.com/g/auraphp/Aura.Intl/)
|
||||
[](https://scrutinizer-ci.com/g/auraphp/Aura.Intl/)
|
||||
[](https://travis-ci.org/auraphp/Aura.Intl)
|
||||
|
||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
To run the unit tests at the command line, issue `composer install` and then
|
||||
`phpunit` at the package root. This requires [Composer][] to be available as
|
||||
`composer`, and [PHPUnit][] to be available as `phpunit`.
|
||||
|
||||
This package attempts to comply with [PSR-1][], [PSR-2][], and [PSR-4][]. If
|
||||
you notice compliance oversights, please send a patch via pull request.
|
||||
|
||||
## Community
|
||||
|
||||
To ask questions, provide feedback, or otherwise communicate with other Aura
|
||||
users, please join our [Google Group][], follow [@auraphp][], or chat with us
|
||||
on Freenode in the #auraphp channel.
|
||||
|
||||
## Documentation
|
||||
|
||||
This package is fully documented [here](./docs/index.md).
|
||||
|
||||
[PSR-1]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md
|
||||
[PSR-2]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md
|
||||
[PSR-4]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader.md
|
||||
[Composer]: http://getcomposer.org/
|
||||
[PHPUnit]: http://phpunit.de/
|
||||
[Google Group]: http://groups.google.com/group/auraphp
|
||||
[@auraphp]: http://twitter.com/auraphp
|
||||
[download a release]: https://github.com/auraphp/Aura.Intl/releases
|
||||
[aura/intl]: https://packagist.org/packages/aura/intl
|
||||
[composer.json]: ./composer.json
|
||||
Vendored
+35
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "aura/intl",
|
||||
"type": "library",
|
||||
"description": "The Aura Intl package provides internationalization tools, specifically message translation.",
|
||||
"keywords": [
|
||||
"intl",
|
||||
"internationalization",
|
||||
"i18n",
|
||||
"l10n",
|
||||
"localization",
|
||||
"globalization",
|
||||
"g11n"
|
||||
],
|
||||
"homepage": "https://github.com/auraphp/Aura.Intl",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Aura.Intl Contributors",
|
||||
"homepage": "https://github.com/auraphp/Aura.Intl/contributors"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^5.6|^7.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Aura\\Intl\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Aura\\Intl\\": "tests/"
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+218
@@ -0,0 +1,218 @@
|
||||
# Aura.Intl
|
||||
|
||||
The Aura.Intl package provides internationalization (I18N) tools, specifically
|
||||
package-oriented per-locale message translation.
|
||||
|
||||
## Getting Started
|
||||
|
||||
You can instantiate _TranslatorLocator_ object from _TranslatorLocatorFactory_
|
||||
as below
|
||||
|
||||
```php
|
||||
<?php
|
||||
use Aura\Intl\TranslatorLocatorFactory;
|
||||
|
||||
$factory = new TranslatorLocatorFactory();
|
||||
$translators = $factory->newInstance();
|
||||
?>
|
||||
```
|
||||
|
||||
Alternatively, we can add the Aura.Intl package `/path/to/Aura.Intl/src` to
|
||||
our autoloader and build a translator locator manually:
|
||||
|
||||
```php
|
||||
<?php
|
||||
use Aura\Intl\PackageLocator;
|
||||
use Aura\Intl\FormatterLocator;
|
||||
use Aura\Intl\TranslatorFactory;
|
||||
use Aura\Intl\TranslatorLocator;
|
||||
|
||||
return new TranslatorLocator(
|
||||
new PackageLocator,
|
||||
new FormatterLocator([
|
||||
'basic' => function () { return new \Aura\Intl\BasicFormatter; },
|
||||
'intl' => function () { return new \Aura\Intl\IntlFormatter; },
|
||||
]),
|
||||
new TranslatorFactory,
|
||||
'en_US'
|
||||
);
|
||||
?>
|
||||
```
|
||||
|
||||
## Setting Localized Messages For A Package
|
||||
|
||||
We can set localized messages for a package through the `PackageLocator` object
|
||||
from the translator locator. We create a new `Package` with messages and place
|
||||
it into the locator as a callable. The messages take the form of a message key and
|
||||
and message string.
|
||||
|
||||
```php
|
||||
<?php
|
||||
use Aura\Intl\Package;
|
||||
|
||||
// get the package locator
|
||||
$packages = $translators->getPackages();
|
||||
|
||||
// place into the locator for Vendor.Package
|
||||
$packages->set('Vendor.Package', 'en_US', function() {
|
||||
// create a US English message set
|
||||
$package = new Package;
|
||||
$package->setMessages([
|
||||
'FOO' => 'The text for "foo."',
|
||||
'BAR' => 'The text for "bar."',
|
||||
]);
|
||||
return $package;
|
||||
});
|
||||
|
||||
// place into the locator for a Vendor.Package
|
||||
$packages->set('Vendor.Package', 'pt_BR', function() {
|
||||
// a Brazilian Portuguese message set
|
||||
$package = new Package;
|
||||
$package->setMessages([
|
||||
'FOO' => 'O texto de "foo".',
|
||||
'BAR' => 'O texto de "bar".',
|
||||
]);
|
||||
return $package;
|
||||
});
|
||||
?>
|
||||
```
|
||||
|
||||
|
||||
## Setting The Default Locale
|
||||
|
||||
We can set the default locale for translations using the `setLocale()` method:
|
||||
|
||||
```php
|
||||
<?php
|
||||
$translators->setLocale('pt_BR');
|
||||
?>
|
||||
```
|
||||
|
||||
## Getting A Localized Message
|
||||
|
||||
Now that the translator locator has messages and a default locale, we can get
|
||||
an individual package translator. The package translator is suitable for
|
||||
injection into another class, or for standalone use.
|
||||
|
||||
```php
|
||||
<?php
|
||||
// recall that the default locale is pt_BR
|
||||
$translator = $translators->get('Vendor.Package');
|
||||
echo $translator->translate('FOO'); // 'O texto de "foo".'
|
||||
?>
|
||||
```
|
||||
|
||||
You can get a translator for a non-default locale as well:
|
||||
|
||||
```php
|
||||
<?php
|
||||
$translator = $translators->get('Vendor.Package', 'en_US');
|
||||
echo $translator->translate('FOO'); // 'The text for "foo."'
|
||||
?>
|
||||
```
|
||||
|
||||
|
||||
## Replacing Message Tokens With Values
|
||||
|
||||
We often need to use dynamic values in translated messages. First, the
|
||||
message string needs to have a token placeholder for the dynamic value:
|
||||
|
||||
```php
|
||||
<?php
|
||||
// get the packages out of the translator locator
|
||||
$packages = $translators->getPackages();
|
||||
|
||||
$packages->set('Vendor.Dynamic', 'en_US', function() {
|
||||
|
||||
// US English messages
|
||||
$package = new Package;
|
||||
$package->setMessages([
|
||||
'PAGE' => 'Page {page} of {pages} pages.',
|
||||
]);
|
||||
return $package;
|
||||
});
|
||||
|
||||
$packages->set('Vendor.Dynamic', 'pt_BR', function() {
|
||||
// Brazilian Portuguese messages
|
||||
$package = new Package;
|
||||
$package->setMessages([
|
||||
'PAGE' => 'Página {page} de {pages} páginas.',
|
||||
]);
|
||||
return $package;
|
||||
});
|
||||
?>
|
||||
```
|
||||
|
||||
Then, when we translate the message, we provide an array of tokens and
|
||||
replacement values. These will be interpolated into the message string.
|
||||
|
||||
```php
|
||||
<?php
|
||||
// recall that the default locale is pt_BR
|
||||
$translator = $translators->get('Vendor.Dynamic');
|
||||
echo $translator->translate('PAGE', [
|
||||
'page' => 1,
|
||||
'pages' => 1,
|
||||
]); // 'Página 1 de 1 páginas.'
|
||||
?>
|
||||
```
|
||||
|
||||
## Pluralized Messages
|
||||
|
||||
Usually, we need to use different messages when a value is singular or plural.
|
||||
The `BasicFormatter` is not capable of presenting different messages based on
|
||||
different token values. The `IntlFormatter` *is* capable, but the PHP
|
||||
[`intl`](http://php.net/intl) extension must be loaded to take advantage of
|
||||
it, and we must specify the `'intl'` formatter for the package in the catalog.
|
||||
|
||||
When using the `IntlFormatter`, we can build our message strings to present
|
||||
singular or plural messages, as in the following example:
|
||||
|
||||
```php
|
||||
<?php
|
||||
// get the packages out of the translator locator
|
||||
$packages = $translators->getCatalog();
|
||||
|
||||
// get the Vendor.Dynamic package en_US locale and set
|
||||
// US English messages with pluralization. note the use
|
||||
// of # instead of {pages} herein; using the placeholder
|
||||
// "inside itself" with the Intl formatter causes trouble.
|
||||
$package->setMessages([
|
||||
'PAGE' => '{pages,plural,'
|
||||
. '=0{No pages.}'
|
||||
. '=1{One page only.}'
|
||||
. 'other{Page {page} of # pages.}'
|
||||
. '}'
|
||||
]);
|
||||
|
||||
// use the 'intl' formatter for this package and locale
|
||||
$package->setFormatter('intl');
|
||||
|
||||
// now that we have added the pluralizable messages,
|
||||
// get the US English translator for the package
|
||||
$translator = $translators->get('Vendor.Dynamic', 'en_US');
|
||||
|
||||
// zero translation
|
||||
echo $translator->translate('PAGE', [
|
||||
'page' => 0,
|
||||
'pages' => 0,
|
||||
]); // 'No pages.'
|
||||
|
||||
// singular translation
|
||||
echo $translator->translate('PAGE', [
|
||||
'page' => 1,
|
||||
'pages' => 1,
|
||||
]); // 'One page only.'
|
||||
|
||||
// plural translation
|
||||
echo $translator->translate('PAGE', [
|
||||
'page' => 3,
|
||||
'pages' => 10,
|
||||
]); // 'Page 3 of 10 pages.'
|
||||
?>
|
||||
```
|
||||
|
||||
Note that you can use other tokens within a pluralized token string to build
|
||||
more complex messages. For more information, see the following:
|
||||
|
||||
<http://icu-project.org/apiref/icu4j/com/ibm/icu/text/MessageFormat.html>
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
error_reporting(E_ALL);
|
||||
$autoloader = __DIR__ . '/vendor/autoload.php';
|
||||
if (! file_exists($autoloader)) {
|
||||
echo "Composer autoloader not found: $autoloader" . PHP_EOL;
|
||||
echo "Please issue 'composer install' and try again." . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
require $autoloader;
|
||||
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
<phpunit bootstrap="./phpunit.php">
|
||||
<testsuites>
|
||||
<testsuite>
|
||||
<directory>./tests</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<filter>
|
||||
<whitelist processUncoveredFilesFromWhitelist="true">
|
||||
<directory suffix=".php">./src/</directory>
|
||||
</whitelist>
|
||||
</filter>
|
||||
</phpunit>
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/**
|
||||
*
|
||||
* This file is part of the Aura Project for PHP.
|
||||
*
|
||||
* @package Aura.Intl
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
*/
|
||||
namespace Aura\Intl;
|
||||
|
||||
/**
|
||||
*
|
||||
* BasicFormatter
|
||||
*
|
||||
* @package Aura.Intl
|
||||
*
|
||||
*/
|
||||
class BasicFormatter implements FormatterInterface
|
||||
{
|
||||
/**
|
||||
*
|
||||
* Format message
|
||||
*
|
||||
* @param string $locale
|
||||
*
|
||||
* @param string $string
|
||||
*
|
||||
* @param array $tokens_values
|
||||
*
|
||||
* @return string A string replaced with the token values
|
||||
*
|
||||
*/
|
||||
public function format($locale, $string, array $tokens_values)
|
||||
{
|
||||
$replace = [];
|
||||
foreach ($tokens_values as $token => $value) {
|
||||
// convert an array to a CSV string
|
||||
if (is_array($value)) {
|
||||
$value = '"' . implode('", "', $value) . '"';
|
||||
}
|
||||
$replace['{' . $token . '}'] = $value;
|
||||
}
|
||||
return strtr($string, $replace);
|
||||
}
|
||||
}
|
||||
Vendored
+22
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
/**
|
||||
*
|
||||
* This file is part of the Aura Project for PHP.
|
||||
*
|
||||
* @package Aura.Intl
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
*/
|
||||
namespace Aura\Intl;
|
||||
|
||||
/**
|
||||
*
|
||||
* Exception
|
||||
*
|
||||
* @package Aura.Intl
|
||||
*
|
||||
*/
|
||||
class Exception extends \Exception
|
||||
{
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
/**
|
||||
*
|
||||
* This file is part of the Aura Project for PHP.
|
||||
*
|
||||
* @package Aura.Intl
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
*/
|
||||
namespace Aura\Intl\Exception;
|
||||
|
||||
use Aura\Intl\Exception;
|
||||
|
||||
/**
|
||||
*
|
||||
* Could not format a message.
|
||||
*
|
||||
* @package Aura.Intl
|
||||
*
|
||||
*/
|
||||
class CannotFormat extends \Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
/**
|
||||
*
|
||||
* This file is part of the Aura Project for PHP.
|
||||
*
|
||||
* @package Aura.Intl
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
*/
|
||||
namespace Aura\Intl\Exception;
|
||||
|
||||
use Aura\Intl\Exception;
|
||||
|
||||
/**
|
||||
*
|
||||
* Could not instantiate a formatter object.
|
||||
*
|
||||
* @package Aura.Intl
|
||||
*
|
||||
*/
|
||||
class CannotInstantiateFormatter extends \Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
/**
|
||||
*
|
||||
* This file is part of the Aura Project for PHP.
|
||||
*
|
||||
* @package Aura.Intl
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
*/
|
||||
namespace Aura\Intl\Exception;
|
||||
|
||||
use Aura\Intl\Exception;
|
||||
|
||||
/**
|
||||
*
|
||||
* A formatter name is not mapped in the locator.
|
||||
*
|
||||
* @package Aura.Intl
|
||||
*
|
||||
*/
|
||||
class FormatterNotMapped extends \Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
/**
|
||||
*
|
||||
* This file is part of the Aura Project for PHP.
|
||||
*
|
||||
* @package Aura.Intl
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
*/
|
||||
namespace Aura\Intl\Exception;
|
||||
|
||||
use Aura\Intl\Exception;
|
||||
|
||||
/**
|
||||
*
|
||||
* ICU Version is too low for Aura.Intl to work properly.
|
||||
*
|
||||
* @package Aura.Intl
|
||||
*
|
||||
*/
|
||||
class IcuVersionTooLow extends \Exception
|
||||
{
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
/**
|
||||
*
|
||||
* This file is part of the Aura Project for PHP.
|
||||
*
|
||||
* @package Aura.Intl
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
*/
|
||||
namespace Aura\Intl;
|
||||
|
||||
/**
|
||||
*
|
||||
* Formatter Interface
|
||||
*
|
||||
* @package Aura.Intl
|
||||
*
|
||||
*/
|
||||
interface FormatterInterface
|
||||
{
|
||||
/**
|
||||
*
|
||||
* Format message
|
||||
*
|
||||
* @param string $locale
|
||||
*
|
||||
* @param string $string
|
||||
*
|
||||
* @param array $tokens_values
|
||||
*
|
||||
*/
|
||||
public function format($locale, $string, array $tokens_values);
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
/**
|
||||
*
|
||||
* This file is part of the Aura Project for PHP.
|
||||
*
|
||||
* @package Aura.Intl
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
*/
|
||||
namespace Aura\Intl;
|
||||
|
||||
/**
|
||||
*
|
||||
* A ServiceLocator implementation for loading and retaining formatter objects.
|
||||
*
|
||||
* @package Aura.Intl
|
||||
*
|
||||
*/
|
||||
class FormatterLocator
|
||||
{
|
||||
/**
|
||||
*
|
||||
* A registry to retain formatter objects.
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
*/
|
||||
protected $registry;
|
||||
|
||||
/**
|
||||
*
|
||||
* Tracks whether or not a registry entry has been converted from a
|
||||
* callable to a formatter object.
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
*/
|
||||
protected $converted = [];
|
||||
|
||||
/**
|
||||
*
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $registry An array of key-value pairs where the key is the
|
||||
* formatter name the value is a callable that returns a formatter object.
|
||||
*
|
||||
*/
|
||||
public function __construct(array $registry = [])
|
||||
{
|
||||
foreach ($registry as $name => $spec) {
|
||||
$this->set($name, $spec);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Sets a formatter into the registry by name.
|
||||
*
|
||||
* @param string $name The formatter name.
|
||||
*
|
||||
* @param callable $spec A callable that returns a formatter object.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
*/
|
||||
public function set($name, $spec)
|
||||
{
|
||||
$this->registry[$name] = $spec;
|
||||
$this->converted[$name] = false;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets a formatter from the registry by name.
|
||||
*
|
||||
* @param string $name The formatter to retrieve.
|
||||
*
|
||||
* @return FormatterInterface A formatter object.
|
||||
*
|
||||
* @throws Exception\FormatterNotMapped
|
||||
*
|
||||
*/
|
||||
public function get($name)
|
||||
{
|
||||
if (! isset($this->registry[$name])) {
|
||||
throw new Exception\FormatterNotMapped($name);
|
||||
}
|
||||
|
||||
if (! $this->converted[$name]) {
|
||||
$func = $this->registry[$name];
|
||||
$this->registry[$name] = $func();
|
||||
$this->converted[$name] = true;
|
||||
}
|
||||
|
||||
return $this->registry[$name];
|
||||
}
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
/**
|
||||
*
|
||||
* This file is part of the Aura Project for PHP.
|
||||
*
|
||||
* @package Aura.Intl
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
*/
|
||||
namespace Aura\Intl;
|
||||
|
||||
use MessageFormatter;
|
||||
use Aura\Intl\Exception;
|
||||
|
||||
/**
|
||||
*
|
||||
* Uses php intl extension to format messages
|
||||
*
|
||||
* @package Aura.Intl
|
||||
*
|
||||
*/
|
||||
class IntlFormatter implements FormatterInterface
|
||||
{
|
||||
/**
|
||||
*
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $icu_version The current ICU version; mostly used for
|
||||
* testing.
|
||||
*
|
||||
* @throws Exception\IcuVersionTooLow when the Version of ICU installed
|
||||
* is too low for Aura.Intl to work properly.
|
||||
*
|
||||
*/
|
||||
public function __construct($icu_version = INTL_ICU_VERSION)
|
||||
{
|
||||
if (version_compare($icu_version, '4.8') < 0) {
|
||||
throw new Exception\IcuVersionTooLow('ICU Version 4.8 or higher required.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Format the message with the help of php intl extension
|
||||
*
|
||||
* @param string $locale
|
||||
* @param string $string
|
||||
* @param array $tokens_values
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
public function format($locale, $string, array $tokens_values)
|
||||
{
|
||||
$values = [];
|
||||
foreach ($tokens_values as $token => $value) {
|
||||
// convert an array to a CSV string
|
||||
if (is_array($value)) {
|
||||
$value = '"' . implode('", "', $value) . '"';
|
||||
}
|
||||
|
||||
$values[$token] = $value;
|
||||
}
|
||||
|
||||
try {
|
||||
$formatter = new MessageFormatter($locale, $string);
|
||||
if (! $formatter) {
|
||||
$this->throwCannotInstantiateFormatter();
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$this->throwCannotInstantiateFormatter();
|
||||
}
|
||||
|
||||
$result = $formatter->format($values);
|
||||
if ($result === false) {
|
||||
throw new Exception\CannotFormat(
|
||||
$formatter->getErrorMessage(),
|
||||
$formatter->getErrorCode()
|
||||
);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Throws exception
|
||||
*
|
||||
* @throws Exception\CannotInstantiateFormatter
|
||||
*/
|
||||
protected function throwCannotInstantiateFormatter()
|
||||
{
|
||||
throw new Exception\CannotInstantiateFormatter(
|
||||
intl_get_error_message(),
|
||||
intl_get_error_code()
|
||||
);
|
||||
}
|
||||
}
|
||||
Vendored
+197
@@ -0,0 +1,197 @@
|
||||
<?php
|
||||
/**
|
||||
*
|
||||
* This file is part of the Aura Project for PHP.
|
||||
*
|
||||
* @package Aura.Intl
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
*/
|
||||
namespace Aura\Intl;
|
||||
|
||||
/**
|
||||
*
|
||||
* Message Catalog
|
||||
*
|
||||
* @package Aura.Intl
|
||||
*
|
||||
*/
|
||||
class Package
|
||||
{
|
||||
/**
|
||||
*
|
||||
* Message keys and translations in this package.
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
*/
|
||||
protected $messages;
|
||||
|
||||
/**
|
||||
*
|
||||
* The name of a fallback package to use when a message key does not
|
||||
* exist.
|
||||
*
|
||||
* @var string
|
||||
*
|
||||
*/
|
||||
protected $fallback;
|
||||
|
||||
/**
|
||||
*
|
||||
* The name of the formatter to use when formatting translated messages.
|
||||
*
|
||||
* @var string
|
||||
*
|
||||
*/
|
||||
protected $formatter;
|
||||
|
||||
/**
|
||||
*
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $formatter The name of the formatter to use.
|
||||
*
|
||||
* @param string $fallback The name of the fallback package to use.
|
||||
*
|
||||
* @param array $messages The messages in this package.
|
||||
*
|
||||
*/
|
||||
public function __construct(
|
||||
$formatter = 'basic',
|
||||
$fallback = null,
|
||||
array $messages = []
|
||||
) {
|
||||
$this->formatter = $formatter;
|
||||
$this->fallback = $fallback;
|
||||
$this->messages = $messages;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Sets the messages for this package.
|
||||
*
|
||||
* @param array $messages The messages for this package.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
*/
|
||||
public function setMessages(array $messages)
|
||||
{
|
||||
$this->messages = $messages;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Adds one message for this package.
|
||||
*
|
||||
* @param string $key the key of the message
|
||||
*
|
||||
* @param string $message the actual message
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
*/
|
||||
public function addMessage($key, $message)
|
||||
{
|
||||
$this->messages[$key] = $message;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Adds new messages for this package.
|
||||
*
|
||||
* @param array $messages The messages to add in this package.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
*/
|
||||
public function addMessages($messages)
|
||||
{
|
||||
$this->messages = array_merge($this->messages, $messages);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the messages for this package.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
*/
|
||||
public function getMessages()
|
||||
{
|
||||
return $this->messages;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the message of the given key for this package.
|
||||
*
|
||||
* @param string $key the key of the message to return
|
||||
*
|
||||
* @return mixed The message translation string, or false if not found.
|
||||
*
|
||||
*/
|
||||
public function getMessage($key)
|
||||
{
|
||||
if (isset($this->messages[$key])) {
|
||||
return $this->messages[$key];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Sets the formatter name for this package.
|
||||
*
|
||||
* @param string $formatter The formatter name for this package.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
*/
|
||||
public function setFormatter($formatter)
|
||||
{
|
||||
$this->formatter = $formatter;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the formatter name for this package.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
*/
|
||||
public function getFormatter()
|
||||
{
|
||||
return $this->formatter;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Sets the fallback package name.
|
||||
*
|
||||
* @param string $fallback The fallback package name.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
*/
|
||||
public function setFallback($fallback)
|
||||
{
|
||||
$this->fallback = $fallback;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the fallback package name.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
*/
|
||||
public function getFallback()
|
||||
{
|
||||
return $this->fallback;
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/**
|
||||
*
|
||||
* This file is part of the Aura Project for PHP.
|
||||
*
|
||||
* @package Aura.Intl
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
*/
|
||||
namespace Aura\Intl;
|
||||
|
||||
/**
|
||||
*
|
||||
* Creates new package instances.
|
||||
*
|
||||
* @package Aura.Intl
|
||||
*
|
||||
*/
|
||||
class PackageFactory
|
||||
{
|
||||
/**
|
||||
*
|
||||
* Returns a new Package instance.
|
||||
*
|
||||
* @param array $info An array of package information with keys
|
||||
* 'formatter', 'fallback', and 'messages'. Typically read from an
|
||||
* `intl/xx_XX.php` file.
|
||||
*
|
||||
* @return Package
|
||||
*
|
||||
*/
|
||||
public function newInstance(array $info)
|
||||
{
|
||||
$package = new Package;
|
||||
if (isset($info['fallback'])) {
|
||||
$package->setFallback($info['fallback']);
|
||||
}
|
||||
if (isset($info['formatter'])) {
|
||||
$package->setFormatter($info['formatter']);
|
||||
}
|
||||
if (isset($info['messages'])) {
|
||||
$package->setMessages($info['messages']);
|
||||
}
|
||||
return $package;
|
||||
}
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
/**
|
||||
*
|
||||
* This file is part of the Aura Project for PHP.
|
||||
*
|
||||
* @package Aura.Intl
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
*/
|
||||
namespace Aura\Intl;
|
||||
|
||||
/**
|
||||
*
|
||||
* A ServiceLocator implementation for loading and retaining translator objects.
|
||||
*
|
||||
* @package Aura.Intl
|
||||
*
|
||||
*/
|
||||
class PackageLocator implements PackageLocatorInterface
|
||||
{
|
||||
/**
|
||||
*
|
||||
* A registry of packages.
|
||||
*
|
||||
* Unlike many other registries, this one is two layers deep. The first
|
||||
* key is a package name, the second key is a locale code, and the value
|
||||
* is a callable that returns a Package object for that name and locale.
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
*/
|
||||
protected $registry = [];
|
||||
|
||||
/**
|
||||
*
|
||||
* Tracks whether or not a registry entry has been converted from a
|
||||
* callable to a Package object.
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
*/
|
||||
protected $converted = [];
|
||||
|
||||
/**
|
||||
*
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $registry A registry of packages.
|
||||
*
|
||||
* @see $registry
|
||||
*
|
||||
*/
|
||||
public function __construct(array $registry = [])
|
||||
{
|
||||
foreach ($registry as $name => $locales) {
|
||||
foreach ($locales as $locale => $spec) {
|
||||
$this->set($name, $locale, $spec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Sets a Package object.
|
||||
*
|
||||
* @param string $name The package name.
|
||||
*
|
||||
* @param string $locale The locale for the package.
|
||||
*
|
||||
* @param callable $spec A callable that returns a package.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
*/
|
||||
public function set($name, $locale, callable $spec)
|
||||
{
|
||||
$this->registry[$name][$locale] = $spec;
|
||||
$this->converted[$name][$locale] = false;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets a Package object.
|
||||
*
|
||||
* @param string $name The package name.
|
||||
*
|
||||
* @param string $locale The locale for the package.
|
||||
*
|
||||
* @return Package
|
||||
*
|
||||
*/
|
||||
public function get($name, $locale)
|
||||
{
|
||||
if (! isset($this->registry[$name][$locale])) {
|
||||
throw new Exception("Package '$name' with locale '$locale' is not registered.");
|
||||
}
|
||||
|
||||
if (! $this->converted[$name][$locale]) {
|
||||
$func = $this->registry[$name][$locale];
|
||||
$this->registry[$name][$locale] = $func();
|
||||
$this->converted[$name][$locale] = true;
|
||||
}
|
||||
|
||||
return $this->registry[$name][$locale];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
/**
|
||||
*
|
||||
* This file is part of the Aura Project for PHP.
|
||||
*
|
||||
* @package Aura.Intl
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
*/
|
||||
namespace Aura\Intl;
|
||||
|
||||
/**
|
||||
*
|
||||
* Package locator interface.
|
||||
*
|
||||
* @package Aura.Intl
|
||||
*
|
||||
*/
|
||||
interface PackageLocatorInterface
|
||||
{
|
||||
/**
|
||||
*
|
||||
* Sets a Package object.
|
||||
*
|
||||
* @param string $name The package name.
|
||||
*
|
||||
* @param string $locale The locale for the package.
|
||||
*
|
||||
* @param callable $spec A callable that returns a Package object.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
*/
|
||||
public function set($name, $locale, callable $spec);
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets a Package object.
|
||||
*
|
||||
* @param string $name The package name.
|
||||
*
|
||||
* @param string $locale The locale for the package.
|
||||
*
|
||||
* @return Package
|
||||
*
|
||||
*/
|
||||
public function get($name, $locale);
|
||||
}
|
||||
Vendored
+158
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
/**
|
||||
*
|
||||
* This file is part of the Aura Project for PHP.
|
||||
*
|
||||
* @package Aura.Intl
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
*/
|
||||
namespace Aura\Intl;
|
||||
|
||||
/**
|
||||
*
|
||||
* Translator to translate the message
|
||||
*
|
||||
* @package Aura.Intl
|
||||
*
|
||||
*/
|
||||
class Translator implements TranslatorInterface
|
||||
{
|
||||
/**
|
||||
*
|
||||
* A fallback translator.
|
||||
*
|
||||
* @var TranslatorInterface
|
||||
*
|
||||
*/
|
||||
protected $fallback;
|
||||
|
||||
/**
|
||||
*
|
||||
* The formatter to use when translating messages.
|
||||
*
|
||||
* @var FormatterInterface
|
||||
*
|
||||
*/
|
||||
protected $formatter;
|
||||
|
||||
/**
|
||||
*
|
||||
* The locale being used for translations.
|
||||
*
|
||||
* @var string
|
||||
*
|
||||
*/
|
||||
protected $locale;
|
||||
|
||||
/**
|
||||
* The Package containing keys and translations.
|
||||
*
|
||||
* @var Package
|
||||
*
|
||||
*/
|
||||
protected $package;
|
||||
|
||||
/**
|
||||
*
|
||||
* Constructor
|
||||
*
|
||||
* @param string $locale The locale being used.
|
||||
*
|
||||
* @param Package $package The Package containing keys and translations.
|
||||
*
|
||||
* @param FormatterInterface $formatter A message formatter.
|
||||
*
|
||||
* @param TranslatorInterface $fallback A fallback translator.
|
||||
*
|
||||
*/
|
||||
public function __construct(
|
||||
$locale,
|
||||
Package $package,
|
||||
FormatterInterface $formatter,
|
||||
TranslatorInterface $fallback = null
|
||||
) {
|
||||
$this->locale = $locale;
|
||||
$this->package = $package;
|
||||
$this->formatter = $formatter;
|
||||
$this->fallback = $fallback;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets the message translation by its key.
|
||||
*
|
||||
* @param string $key The message key.
|
||||
*
|
||||
* @return mixed The message translation string, or false if not found.
|
||||
*
|
||||
*/
|
||||
protected function getMessage($key)
|
||||
{
|
||||
$message = $this->package->getMessage($key);
|
||||
if ($message) {
|
||||
return $message;
|
||||
}
|
||||
|
||||
if ($this->fallback) {
|
||||
// get the message from the fallback translator
|
||||
$message = $this->fallback->getMessage($key);
|
||||
if ($message) {
|
||||
// speed optimization: retain locally
|
||||
$this->package->addMessage($key, $message);
|
||||
// done!
|
||||
return $message;
|
||||
}
|
||||
}
|
||||
|
||||
// no local message, no fallback
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Translates the message indicated by they key, replacing token values
|
||||
* along the way.
|
||||
*
|
||||
* @param string $key The message key.
|
||||
*
|
||||
* @param array $tokens_values Token values to interpolate into the
|
||||
* message.
|
||||
*
|
||||
* @return string The translated message with tokens replaced.
|
||||
*
|
||||
*/
|
||||
public function translate($key, array $tokens_values = [])
|
||||
{
|
||||
// get the message string
|
||||
$message = $this->getMessage($key);
|
||||
|
||||
// do we have a message string?
|
||||
if (! $message) {
|
||||
// no, return the message key as-is
|
||||
$message = $key;
|
||||
}
|
||||
|
||||
// are there token replacement values?
|
||||
if (empty($tokens_values)) {
|
||||
// no, return the message string as-is
|
||||
return $message;
|
||||
}
|
||||
|
||||
// run message string through formatter to replace tokens with values
|
||||
return $this->formatter->format($this->locale, $message, $tokens_values);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* An object of type Package
|
||||
*
|
||||
* @return Package
|
||||
*
|
||||
*/
|
||||
public function getPackage()
|
||||
{
|
||||
return $this->package;
|
||||
}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
/**
|
||||
*
|
||||
* This file is part of the Aura Project for PHP.
|
||||
*
|
||||
* @package Aura.Intl
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
*/
|
||||
namespace Aura\Intl;
|
||||
|
||||
/**
|
||||
*
|
||||
* Factory to create Translator objects.
|
||||
*
|
||||
* @package Aura.Intl
|
||||
*
|
||||
*/
|
||||
class TranslatorFactory
|
||||
{
|
||||
/**
|
||||
*
|
||||
* The class to use for new instances.
|
||||
*
|
||||
* @var string
|
||||
*
|
||||
*/
|
||||
protected $class = Translator::class;
|
||||
|
||||
/**
|
||||
*
|
||||
* Returns a new Translator.
|
||||
*
|
||||
* @param string $locale The locale code for the translator.
|
||||
*
|
||||
* @param Package $package The localized package for the translator.
|
||||
*
|
||||
* @param FormatterInterface $formatter The formatter to use for
|
||||
* interpolating token values.
|
||||
*
|
||||
* @param TranslatorInterface $fallback A fallback translator to use, if
|
||||
* any.
|
||||
*
|
||||
* @return Translator
|
||||
*
|
||||
*/
|
||||
public function newInstance(
|
||||
$locale,
|
||||
Package $package,
|
||||
FormatterInterface $formatter,
|
||||
TranslatorInterface $fallback = null
|
||||
) {
|
||||
$class = $this->class;
|
||||
return new $class($locale, $package, $formatter, $fallback);
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
/**
|
||||
*
|
||||
* This file is part of the Aura Project for PHP.
|
||||
*
|
||||
* @package Aura.Intl
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
*/
|
||||
namespace Aura\Intl;
|
||||
|
||||
/**
|
||||
*
|
||||
* TranslatorInterface
|
||||
*
|
||||
* @package Aura.Intl
|
||||
*
|
||||
*/
|
||||
interface TranslatorInterface
|
||||
{
|
||||
/**
|
||||
*
|
||||
* translate
|
||||
*
|
||||
* @param string $key
|
||||
*
|
||||
* @param array $tokens_values
|
||||
*
|
||||
*/
|
||||
public function translate($key, array $tokens_values = []);
|
||||
}
|
||||
+197
@@ -0,0 +1,197 @@
|
||||
<?php
|
||||
/**
|
||||
*
|
||||
* This file is part of the Aura Project for PHP.
|
||||
*
|
||||
* @package Aura.Intl
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
*/
|
||||
namespace Aura\Intl;
|
||||
|
||||
/**
|
||||
*
|
||||
* A ServiceLocator implementation for loading and retaining translator objects.
|
||||
*
|
||||
* @package Aura.Intl
|
||||
*
|
||||
*/
|
||||
class TranslatorLocator
|
||||
{
|
||||
/**
|
||||
*
|
||||
* A registry to retain translator objects.
|
||||
*
|
||||
* @var array
|
||||
*
|
||||
*/
|
||||
protected $registry;
|
||||
|
||||
/**
|
||||
*
|
||||
* The current locale code.
|
||||
*
|
||||
* @var string
|
||||
*
|
||||
*/
|
||||
protected $locale;
|
||||
|
||||
/**
|
||||
*
|
||||
* A translator factory.
|
||||
*
|
||||
* @var TranslatorFactory
|
||||
*
|
||||
*
|
||||
*/
|
||||
protected $factory;
|
||||
|
||||
/**
|
||||
*
|
||||
* A package locator.
|
||||
*
|
||||
* @var PackageLocator
|
||||
*
|
||||
*/
|
||||
protected $packages;
|
||||
|
||||
/**
|
||||
*
|
||||
* A formatter locator.
|
||||
*
|
||||
* @var FormatterLocator
|
||||
*
|
||||
*/
|
||||
protected $formatters;
|
||||
|
||||
/**
|
||||
*
|
||||
* Constructor.
|
||||
*
|
||||
* @param PackageLocator $packages The package locator.
|
||||
*
|
||||
* @param FormatterLocator $formatters The formatter locator.
|
||||
*
|
||||
* @param TranslatorFactory $factory A translator factory to
|
||||
* create translator objects for the locale and package.
|
||||
*
|
||||
* @param string $locale The default locale code to use.
|
||||
*
|
||||
*/
|
||||
public function __construct(
|
||||
PackageLocator $packages,
|
||||
FormatterLocator $formatters,
|
||||
TranslatorFactory $factory,
|
||||
$locale
|
||||
) {
|
||||
$this->packages = $packages;
|
||||
$this->factory = $factory;
|
||||
$this->formatters = $formatters;
|
||||
$this->setLocale($locale);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Sets the default locale code.
|
||||
*
|
||||
* @param string $locale The new locale code.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
*/
|
||||
public function setLocale($locale)
|
||||
{
|
||||
$this->locale = $locale;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Returns the default locale code.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
*/
|
||||
public function getLocale()
|
||||
{
|
||||
return $this->locale;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* The TranslatorFactory object
|
||||
*
|
||||
* @return TranslatorFactory
|
||||
*/
|
||||
public function getFactory()
|
||||
{
|
||||
return $this->factory;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* An object of type PackageLocator
|
||||
*
|
||||
* @return PackageLocator
|
||||
*
|
||||
*/
|
||||
public function getPackages()
|
||||
{
|
||||
return $this->packages;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* An object of type FormatterLocator
|
||||
*
|
||||
* @return FormatterLocator
|
||||
*
|
||||
*/
|
||||
public function getFormatters()
|
||||
{
|
||||
return $this->formatters;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Gets a translator from the registry by package for a locale.
|
||||
*
|
||||
* @param string $name The translator package to retrieve.
|
||||
*
|
||||
* @param string $locale The locale to use; if empty, uses the default
|
||||
* locale.
|
||||
*
|
||||
* @return TranslatorInterface A translator object.
|
||||
*
|
||||
*/
|
||||
public function get($name, $locale = null)
|
||||
{
|
||||
if (! $name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! $locale) {
|
||||
$locale = $this->getLocale();
|
||||
}
|
||||
|
||||
if (! isset($this->registry[$name][$locale])) {
|
||||
|
||||
// get the package descriptor
|
||||
$package = $this->packages->get($name, $locale);
|
||||
|
||||
// build a translator; note the recursive nature of the
|
||||
// 'fallback' param at the very end.
|
||||
$translator = $this->factory->newInstance(
|
||||
$locale,
|
||||
$package,
|
||||
$this->formatters->get($package->getFormatter()),
|
||||
$this->get($package->getFallback(), $locale)
|
||||
);
|
||||
|
||||
// retain in the registry
|
||||
$this->registry[$name][$locale] = $translator;
|
||||
}
|
||||
|
||||
return $this->registry[$name][$locale];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
/**
|
||||
*
|
||||
* This file is part of the Aura Project for PHP.
|
||||
*
|
||||
* @package Aura.Intl
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
*/
|
||||
namespace Aura\Intl;
|
||||
|
||||
/**
|
||||
*
|
||||
* Formatter Interface
|
||||
*
|
||||
* @package Aura.Intl
|
||||
*
|
||||
*/
|
||||
class TranslatorLocatorFactory
|
||||
{
|
||||
/**
|
||||
*
|
||||
* Returns a new TranslatorLocator.
|
||||
*
|
||||
* @return TranslatorLocator
|
||||
*
|
||||
*/
|
||||
public function newInstance()
|
||||
{
|
||||
return new TranslatorLocator(
|
||||
new PackageLocator,
|
||||
new FormatterLocator([
|
||||
'basic' => function () {
|
||||
return new \Aura\Intl\BasicFormatter;
|
||||
},
|
||||
'intl' => function () {
|
||||
return new \Aura\Intl\IntlFormatter;
|
||||
},
|
||||
]),
|
||||
new TranslatorFactory,
|
||||
'en_US'
|
||||
);
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
namespace Aura\Intl;
|
||||
|
||||
class BasicFormatterTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
protected function newFormatter()
|
||||
{
|
||||
return new BasicFormatter;
|
||||
}
|
||||
|
||||
public function testFormat()
|
||||
{
|
||||
$formatter = $this->newFormatter();
|
||||
|
||||
$locale = 'en_US';
|
||||
$expect = 'Hello world 88!';
|
||||
$tokens_values = ['foo' => 'world', 'bar' => '88', 'baz' => '!'];
|
||||
|
||||
$string = 'Hello {foo} {bar}{baz}';
|
||||
$actual = $formatter->format($locale, $string, $tokens_values);
|
||||
$this->assertSame($expect, $actual);
|
||||
|
||||
$tokens_values = ['array' => ['foo', 'bar', 'baz']];
|
||||
$string = 'Array {array}';
|
||||
$expect = 'Array "foo", "bar", "baz"';
|
||||
$actual = $formatter->format($locale, $string, $tokens_values);
|
||||
$this->assertSame($expect, $actual);
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
namespace Aura\Intl;
|
||||
|
||||
class FormatterLocatorTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function test__constructAndGet()
|
||||
{
|
||||
$formatters = new FormatterLocator([
|
||||
'mock' => function () {
|
||||
return new \Aura\Intl\MockFormatter;
|
||||
},
|
||||
]);
|
||||
|
||||
$expect = 'Aura\Intl\MockFormatter';
|
||||
$actual = $formatters->get('mock');
|
||||
$this->assertInstanceOf($expect, $actual);
|
||||
}
|
||||
|
||||
public function testSetAndGet()
|
||||
{
|
||||
$formatters = new FormatterLocator;
|
||||
$formatters->set('mock', function () {
|
||||
return new \Aura\Intl\MockFormatter;
|
||||
});
|
||||
|
||||
$expect = 'Aura\Intl\MockFormatter';
|
||||
$actual = $formatters->get('mock');
|
||||
$this->assertInstanceOf($expect, $actual);
|
||||
}
|
||||
|
||||
public function testGet_noSuchFormatter()
|
||||
{
|
||||
$formatters = new FormatterLocator;
|
||||
$this->setExpectedException('Aura\Intl\Exception\FormatterNotMapped');
|
||||
$formatters->get('noSuchFormatter');
|
||||
}
|
||||
}
|
||||
Loaded 100 of 6566 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user