Исключить воспроизводимые зависимости Composer из Git

Сохранить ручные библиотеки, изменённые и непроверенные пакеты.
Добавить исключения и порядок восстановления зависимостей.
This commit is contained in:
Zlocorp committed 2026-10-26 21:25:54 +03:00
1 parent e955e1da36
commit d4e51a5dc2
3821 files changed
+44 -588945

No files matched your search

+28
View File
@@ -3,3 +3,31 @@ logs
.idea
files
archive/*
# Зависимости Composer без локальных изменений не храним в Git.
/vendor/*
!/vendor/browser_detection/
!/vendor/dbf/
!/vendor/sms/
!/vendor/cakephp/
/vendor/cakephp/*
!/vendor/cakephp/cakephp/
!/vendor/composer/
/vendor/composer/*
!/vendor/composer/composer/
!/vendor/dompdf/
/vendor/dompdf/*
!/vendor/dompdf/dompdf/
!/vendor/m1/
/vendor/m1/*
!/vendor/m1/env/
!/vendor/parsecsv/
/vendor/parsecsv/*
!/vendor/parsecsv/php-parsecsv/
!/vendor/squizlabs/
/vendor/squizlabs/*
!/vendor/squizlabs/php_codesniffer/
!/vendor/symfony/
/vendor/symfony/*
!/vendor/symfony/debug/
!/vendor/empty
+16
View File
@@ -0,0 +1,16 @@
# Зависимости приложения
Версии зависимостей зафиксированы в `composer.lock`. Пакеты, совпавшие с
официальными архивами этих версий, и создаваемые Composer служебные файлы
исключены из Git.
В `vendor` сохранены ручные DBF/SMS/BrowserDetection-библиотеки, пакеты с
локальными отличиями и пакеты без подтверждённого эталона. В том числе
сохраняются правки CakePHP и дополнительные шрифты Dompdf. Состав каталогов
задан исключениями в `.gitignore`.
Перед восстановлением зависимостей нужно сохранить отслеживаемую часть
`vendor` в отдельную копию. После восстановления пакетов по `composer.lock`
в изолированном окружении сохранённые файлы возвращаются поверх пакетов.
Это сохраняет существующие правки и шрифты; слепая переустановка может их
перезаписать. Восстановление и проверка работы приложения выполняются отдельно.
@@ -1,4 +0,0 @@
/vendor/
*.cache
composer.lock
phpunit.xml
-36
View File
@@ -1,36 +0,0 @@
<?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')
)
;
@@ -1,7 +0,0 @@
# .scrutinizer.yml
checks:
php: true
tools:
external_code_coverage:
timeout: 900
-68
View File
@@ -1,68 +0,0 @@
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
-60
View File
@@ -1,60 +0,0 @@
# [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
View File
@@ -1,23 +0,0 @@
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
View File
@@ -1,124 +0,0 @@
AjglBreakpointTwigExtension
===========================
The AjglBreakpointTwigExtension component allows you set breakpoints in twig templates.
[![Build Status](https://travis-ci.org/ajgarlag/AjglBreakpointTwigExtension.png?branch=master)](https://travis-ci.org/ajgarlag/AjglBreakpointTwigExtension)
[![Latest Stable Version](https://poser.pugx.org/ajgl/breakpoint-twig-extension/v/stable.png)](https://packagist.org/packages/ajgl/breakpoint-twig-extension)
[![Latest Unstable Version](https://poser.pugx.org/ajgl/breakpoint-twig-extension/v/unstable.png)](https://packagist.org/packages/ajgl/breakpoint-twig-extension)
[![Total Downloads](https://poser.pugx.org/ajgl/breakpoint-twig-extension/downloads.png)](https://packagist.org/packages/ajgl/breakpoint-twig-extension)
[![Montly Downloads](https://poser.pugx.org/ajgl/breakpoint-twig-extension/d/monthly.png)](https://packagist.org/packages/ajgl/breakpoint-twig-extension)
[![Daily Downloads](https://poser.pugx.org/ajgl/breakpoint-twig-extension/d/daily.png)](https://packagist.org/packages/ajgl/breakpoint-twig-extension)
[![License](https://poser.pugx.org/ajgl/breakpoint-twig-extension/license.png)](https://packagist.org/packages/ajgl/breakpoint-twig-extension)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/ajgarlag/AjglBreakpointTwigExtension/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/ajgarlag/AjglBreakpointTwigExtension/?branch=master)
[![Code Coverage](https://scrutinizer-ci.com/g/ajgarlag/AjglBreakpointTwigExtension/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/ajgarlag/AjglBreakpointTwigExtension/?branch=master)
[![SensioLabsInsight](https://insight.sensiolabs.com/projects/e0f1276d-6ded-4a20-9b3f-1a7c77a92015/mini.png)](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
-46
View File
@@ -1,46 +0,0 @@
{
"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"
}
}
}
-22
View File
@@ -1,22 +0,0 @@
<?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>
@@ -1,49 +0,0 @@
<?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();
}
}
}
@@ -1,21 +0,0 @@
<?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
{
}
@@ -1,29 +0,0 @@
<?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');
}
}
@@ -1,13 +0,0 @@
<?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>
@@ -1,49 +0,0 @@
<?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]);
}
}
@@ -1,50 +0,0 @@
<?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
View File
@@ -1,27 +0,0 @@
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
View File
@@ -1,23 +0,0 @@
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
View File
@@ -1,171 +0,0 @@
Twig Markdown Extension
=======================
[![Build Status](https://secure.travis-ci.org/aptoma/twig-markdown.png?branch=master)](http://travis-ci.org/aptoma/twig-markdown)
[![Coverage Status](https://img.shields.io/coveralls/aptoma/twig-markdown.svg)](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
View File
@@ -1,35 +0,0 @@
{
"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
View File
File diff suppressed because it is too large. Load diff
-18
View File
@@ -1,18 +0,0 @@
<?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>
@@ -1,76 +0,0 @@
<?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;
}
@@ -1,32 +0,0 @@
<?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';
}
}
@@ -1,49 +0,0 @@
<?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';
}
}
@@ -1,45 +0,0 @@
<?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';
}
}
@@ -1,28 +0,0 @@
<?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();
}
@@ -1,69 +0,0 @@
<?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';
}
}
@@ -1,38 +0,0 @@
<?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);
}
}
@@ -1,76 +0,0 @@
<?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';
}
}
@@ -1,31 +0,0 @@
<?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();
}
}
@@ -1,23 +0,0 @@
<?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();
}
}
@@ -1,32 +0,0 @@
<?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();
}
}
@@ -1,41 +0,0 @@
<?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');
}
}
@@ -1,109 +0,0 @@
<?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;
}
}
@@ -1,4 +0,0 @@
phpunit.xml
composer.lock
composer.phar
/vendor/
-44
View File
@@ -1,44 +0,0 @@
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
View File
@@ -1,19 +0,0 @@
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
View File
@@ -1,238 +0,0 @@
Twig cache extension
====================
The missing cache extension for Twig. The extension allows for caching rendered parts of
templates using several cache strategies.
[![Build Status](https://secure.travis-ci.org/asm89/twig-cache-extension.png?branch=master)](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
View File
@@ -1,43 +0,0 @@
{
"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"
}
}
}
@@ -1,49 +0,0 @@
<?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);
}
}
@@ -1,68 +0,0 @@
<?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);
}
}
@@ -1,36 +0,0 @@
<?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);
}
@@ -1,53 +0,0 @@
<?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
}
}
@@ -1,79 +0,0 @@
<?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);
}
}
@@ -1,76 +0,0 @@
<?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);
}
}
@@ -1,29 +0,0 @@
<?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);
}
@@ -1,68 +0,0 @@
<?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']);
}
}
@@ -1,49 +0,0 @@
<?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);
}
@@ -1,20 +0,0 @@
<?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
{
}
@@ -1,23 +0,0 @@
<?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);
}
}
@@ -1,23 +0,0 @@
<?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);
}
}
@@ -1,23 +0,0 @@
<?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);
}
}
@@ -1,23 +0,0 @@
<?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);
}
}
@@ -1,59 +0,0 @@
<?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(),
);
}
}
@@ -1,71 +0,0 @@
<?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")
;
}
}
@@ -1,59 +0,0 @@
<?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());
}
}
-25
View File
@@ -1,25 +0,0 @@
<?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>
@@ -1,46 +0,0 @@
<?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');
}
}
@@ -1,80 +0,0 @@
<?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');
}
}
@@ -1,80 +0,0 @@
<?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');
}
}
@@ -1,68 +0,0 @@
<?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');
}
}
@@ -1,182 +0,0 @@
<?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;
}
}
@@ -1,3 +0,0 @@
{%- set k = "v'" ~ version -%}
{%- cache k {time: 10} -%}Hello {{ value }}!{%- endcache -%}
{%- cache 'bob'|capitalize {gcs: value} %}Hello {{ value }}!{%- endcache -%}
@@ -1,2 +0,0 @@
{% set annotation = 'v1' %}
{% cache annotation value %}Hello {{ value }}!{% endcache %}
@@ -1 +0,0 @@
{% cache 'v1' value %}Hello {{ value }}!{% endcache %}
@@ -1 +0,0 @@
{% cache 'v2' value %}Hello {{ value }}!{% endcache %}
@@ -1 +0,0 @@
{% cache 'v1' 10 %}Hello {{ value }}!{% endcache %}
@@ -1 +0,0 @@
{% cache 'v1' {time: 10} %}Hello {{ value }}!{% endcache %}
@@ -1 +0,0 @@
{% cache 'v1' 10 %}Hello {{ value }}!{% endcache %}
-16
View File
@@ -1,16 +0,0 @@
<?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.');
}
-10
View File
@@ -1,10 +0,0 @@
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
-14
View File
@@ -1,14 +0,0 @@
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
-66
View File
@@ -1,66 +0,0 @@
# 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.
-7
View File
@@ -1,7 +0,0 @@
# 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!
-21
View File
@@ -1,21 +0,0 @@
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.
-59
View File
@@ -1,59 +0,0 @@
# 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
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/auraphp/Aura.Intl/badges/quality-score.png?b=3.x)](https://scrutinizer-ci.com/g/auraphp/Aura.Intl/)
[![Code Coverage](https://scrutinizer-ci.com/g/auraphp/Aura.Intl/badges/coverage.png?b=3.x)](https://scrutinizer-ci.com/g/auraphp/Aura.Intl/)
[![Build Status](https://travis-ci.org/auraphp/Aura.Intl.png?branch=3.x)](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
-35
View File
@@ -1,35 +0,0 @@
{
"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/"
}
}
}
-218
View File
@@ -1,218 +0,0 @@
# 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>
-9
View File
@@ -1,9 +0,0 @@
<?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;
-12
View File
@@ -1,12 +0,0 @@
<phpunit bootstrap="./phpunit.php">
<testsuites>
<testsuite>
<directory>./tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./src/</directory>
</whitelist>
</filter>
</phpunit>
-47
View File
@@ -1,47 +0,0 @@
<?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);
}
}
-22
View File
@@ -1,22 +0,0 @@
<?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
View File
@@ -1,24 +0,0 @@
<?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
{
}
@@ -1,24 +0,0 @@
<?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
{
}
-24
View File
@@ -1,24 +0,0 @@
<?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
{
}
-24
View File
@@ -1,24 +0,0 @@
<?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
View File
@@ -1,34 +0,0 @@
<?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
View File
@@ -1,98 +0,0 @@
<?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
View File
@@ -1,98 +0,0 @@
<?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()
);
}
}
-197
View File
@@ -1,197 +0,0 @@
<?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
View File
@@ -1,47 +0,0 @@
<?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
View File
@@ -1,107 +0,0 @@
<?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];
}
}
-49
View File
@@ -1,49 +0,0 @@
<?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);
}
-158
View File
@@ -1,158 +0,0 @@
<?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
View File
@@ -1,57 +0,0 @@
<?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
View File
@@ -1,32 +0,0 @@
<?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
View File
@@ -1,197 +0,0 @@
<?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];
}
}
-45
View File
@@ -1,45 +0,0 @@
<?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'
);
}
}
Loaded 100 of 3821 files, more files were not shown because too many files have changed in this diff. Show more