├── LICENSE ├── README.md ├── composer.json ├── doc ├── configuration.md ├── templating-exclamation.md ├── twig-defer.md ├── twig-doctrine-loader.md └── twig-syntax-validator.md └── src ├── Assetic └── TwigFormulaLoader.php ├── BoekkooiTwigJackBundle.php ├── CacheWarmer └── TemplatePathsCacheWarmer.php ├── DependencyInjection ├── BoekkooiTwigJackExtension.php ├── Compiler │ └── ExclamationPass.php └── Configuration.php ├── Model ├── TemplateInterface.php └── TranslatableTemplateInterface.php ├── Resources └── config │ ├── constraint.yml │ ├── defer.yml │ ├── exclamation.yml │ └── loader.yml ├── Templating ├── TemplateNameParser.php └── TemplateReference.php ├── Twig ├── Extension │ └── DeferExtension.php ├── Loader │ └── DoctrineLoader.php ├── Node │ ├── Defer.php │ ├── DeferReference.php │ └── Expression │ │ └── DeferReference.php └── TokenParser │ └── Defer.php └── Validator └── Constraint ├── TwigSyntax.php └── TwigSyntaxValidator.php /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Warnar Boekkooi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Twig Jack Repository 2 | ============= 3 | [![Build Status](https://travis-ci.org/boekkooi/TwigJackBundle.svg?branch=master)](https://travis-ci.org/boekkooi/TwigJackBundle) 4 | [![Code Coverage](https://scrutinizer-ci.com/g/boekkooi/TwigJackBundle/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/boekkooi/TwigJackBundle/?branch=master) 5 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/boekkooi/TwigJackBundle/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/boekkooi/TwigJackBundle/?branch=master) 6 | [![Total Downloads](https://poser.pugx.org/boekkooi/twig-jack-bundle/downloads.svg)](https://packagist.org/packages/boekkooi/twig-jack-bundle) 7 | [![Latest Stable Version](https://poser.pugx.org/boekkooi/twig-jack-bundle/v/stable.svg)](https://packagist.org/packages/boekkooi/twig-jack-bundle) 8 | [![License](https://poser.pugx.org/boekkooi/twig-jack-bundle/license.svg)](https://packagist.org/packages/boekkooi/twig-jack-bundle) 9 | [![Reference Status](https://www.versioneye.com/php/boekkooi:twig-jack-bundle/reference_badge.svg?style=flat)](https://www.versioneye.com/php/boekkooi:twig-jack-bundle/references) 10 | [![SensioLabsInsight](https://insight.sensiolabs.com/projects/53a6e635-78ef-4c6c-be8d-760e978839ff/mini.png)](https://insight.sensiolabs.com/projects/53a6e635-78ef-4c6c-be8d-760e978839ff) 11 | 12 | This repository hosts Twig Extensions and Template tweaks for the symfony 2 framework. 13 | 14 | BoekkooiTwigJackBundle is using [Semantic Versioning](http://semver.org/) starting with version 1.1.0. 15 | 16 | Fork this repository, add your extension, and request a pull. 17 | 18 | [Install and configure](doc/configuration.md) 19 | ------------- 20 | `composer require boekkooi/twig-jack-bundle dev-master` 21 | 22 | [The Defer Block](doc/twig-defer.md) 23 | ------------- 24 | A defer/append twig block. [more...](doc/twig-defer.md) 25 | 26 | [The Exclamation Syntax](doc/templating-exclamation.md) 27 | ------------- 28 | Use `{% extends "!@" %}` to inherit from the root bundle. [more...](doc/templating-exclamation.md) 29 | 30 | [The Doctrine Loader](doc/twig-doctrine-loader.md) 31 | ------------- 32 | Add one or multiple doctrine/database template loaders to twig with optional translation support. [more...](doc/twig-doctrine-loader.md) 33 | 34 | [Twig syntax constraint](doc/twig-syntax-validator.md) 35 | ------------- 36 | Validate that a string is a valid twig template. [more...](doc/twig-syntax-validator.md) 37 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "boekkooi/twig-jack-bundle", 3 | "type": "symfony-bundle", 4 | "keywords": [ "twig", "symfony", "defer", "inheritance", "extension", "doctrine", "loader" ], 5 | "description": "Handy additional features for Twig within symfony 2", 6 | "require": { 7 | "php": ">=5.3.3", 8 | "symfony/framework-bundle": "~2.3|~3.0", 9 | "symfony/twig-bundle": "~2.3|~3.0", 10 | "symfony/yaml": "~2.3|~3.0", 11 | "symfony/dependency-injection": "~2.3|~3.0", 12 | "symfony/finder": "~2.3|~3.0", 13 | 14 | "twig/twig": "~1.15|~2.0" 15 | }, 16 | "suggest": { 17 | "doctrine/common": "Needed to use the twig doctrine loader functionality.", 18 | "symfony/validator": "Allows the twig constraint to be used" 19 | }, 20 | "require-dev": { 21 | "phpunit/phpunit": "4.6.4", 22 | "phpunit/phpunit-mock-objects": "2.3.1", 23 | "fabpot/php-cs-fixer": "1.6.2", 24 | 25 | "symfony/assetic-bundle": "~2.3", 26 | "symfony/validator": "~2.3, >=2.3.19|~3.0", 27 | "doctrine/common": "~2.2", 28 | 29 | "matthiasnoback/symfony-dependency-injection-test": "^0.7" 30 | }, 31 | "license": "MIT", 32 | "authors": [ 33 | { 34 | "name": "Warnar Boekkooi", 35 | "email": "warnar@boekkooi.net" 36 | } 37 | ], 38 | "autoload": { 39 | "psr-4": { 40 | "Boekkooi\\Bundle\\TwigJackBundle\\": "src/" 41 | } 42 | }, 43 | "autoload-dev": { 44 | "psr-4": { 45 | "Tests\\Boekkooi\\Bundle\\TwigJackBundle\\": "tests/" 46 | } 47 | }, 48 | "extra": { 49 | "branch-alias": { 50 | "dev-master": "1.4-dev" 51 | } 52 | }, 53 | "prefer-stable": true, 54 | "minimum-stability": "dev" 55 | } 56 | -------------------------------------------------------------------------------- /doc/configuration.md: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | Add BoekkooiTwigJackBundle by running the command: 5 | ``` 6 | composer require boekkooi/twig-jack-bundle dev-master 7 | ``` 8 | 9 | Enable the bundle in the kernel: 10 | ```php 11 | 12 | {% enddefer %} 13 | {% defer js %} 14 | 18 | * {% endjavascripts %} 19 | * {% enddefer %} 20 | * 21 | * 22 | * @author Warnar Boekkooi 23 | */ 24 | class Defer extends Twig_TokenParser 25 | { 26 | protected $blockPrefix; 27 | 28 | /** 29 | * @param string $blockPrefix 30 | */ 31 | public function __construct($blockPrefix) 32 | { 33 | $this->blockPrefix = $blockPrefix; 34 | } 35 | 36 | /** 37 | * Parses a token and returns a node. 38 | * 39 | * @param Twig_Token $token A Twig_Token instance 40 | * @return null|Twig_Token A Twig_NodeInterface instance 41 | */ 42 | public function parse(Twig_Token $token) 43 | { 44 | $lineno = $token->getLine(); 45 | $stream = $this->parser->getStream(); 46 | $reference = $stream->expect(Twig_Token::NAME_TYPE)->getValue(); 47 | 48 | $name = $stream->nextIf(\Twig_Token::STRING_TYPE); 49 | $name = $name !== null ? $name->getValue() : false; 50 | 51 | $unique = $name !== false; 52 | 53 | $variableName = $stream->nextIf(\Twig_Token::NAME_TYPE); 54 | $variableName = $variableName !== null ? $variableName->getValue() : false; 55 | 56 | $offset = $stream->nextIf(\Twig_Token::NUMBER_TYPE); 57 | $offset = $offset !== null ? $offset->getValue() : false; 58 | 59 | if ($name) { 60 | $name = $this->blockPrefix . $reference . $name; 61 | if ($this->parser->hasBlock($name)) { 62 | $this->bodyParse($stream, $name); 63 | 64 | return null; 65 | } 66 | } else { 67 | $name = $this->createUniqueBlockName($token, $reference); 68 | } 69 | 70 | $this->parser->setBlock($name, $block = new Node\Defer($name, new Twig_Node(array()), $lineno)); 71 | $this->parser->pushLocalScope(); 72 | 73 | $body = $this->bodyParse($stream, $name); 74 | 75 | $block->setNode('body', $body); 76 | $this->parser->popLocalScope(); 77 | 78 | return new Node\DeferReference($name, $variableName, $unique, $reference, $offset, $lineno, $this->getTag()); 79 | } 80 | 81 | public function decideBlockEnd(Twig_Token $token) 82 | { 83 | return $token->test('enddefer'); 84 | } 85 | 86 | /** 87 | * {@inheritdoc} 88 | */ 89 | public function getTag() 90 | { 91 | return 'defer'; 92 | } 93 | 94 | /** 95 | * @param Twig_TokenStream $stream 96 | * @param string $name 97 | * @return Twig_Node 98 | */ 99 | protected function bodyParse(Twig_TokenStream $stream, $name) 100 | { 101 | if ($stream->nextIf(Twig_Token::BLOCK_END_TYPE)) { 102 | $body = $this->parser->subparse(array($this, 'decideBlockEnd'), true); 103 | if ($token = $stream->nextIf(Twig_Token::NAME_TYPE)) { 104 | $value = $token->getValue(); 105 | 106 | if ($value != $name) { 107 | throw new Twig_Error_Syntax( 108 | sprintf("Expected enddefer for defer '$name' (but %s given)", $value), 109 | $stream->getCurrent()->getLine(), 110 | $stream->getFilename() 111 | ); 112 | } 113 | } 114 | } else { 115 | throw new Twig_Error_Syntax( 116 | "Expected enddefer for defer '$name'", 117 | $stream->getCurrent()->getLine(), 118 | $stream->getFilename() 119 | ); 120 | } 121 | $stream->expect(Twig_Token::BLOCK_END_TYPE); 122 | 123 | return $body; 124 | } 125 | 126 | /** 127 | * @param Twig_Token $token 128 | * @param $reference 129 | * @return string 130 | */ 131 | private function createUniqueBlockName(Twig_Token $token, $reference) 132 | { 133 | $name = $this->blockPrefix . $reference . sha1($this->parser->getFilename() . $token->getLine()); 134 | if (!$this->parser->hasBlock($name)) { 135 | return $name; 136 | } 137 | 138 | $i = 0; 139 | do { 140 | $tmpName = $name . '_' . ($i++); 141 | } while ($this->parser->hasBlock($tmpName)); 142 | 143 | return $tmpName; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/Validator/Constraint/TwigSyntax.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class TwigSyntax extends Constraint 11 | { 12 | public $message = 'This value is not a valid twig template.'; 13 | public $parse = true; 14 | public $environment = null; 15 | 16 | public function __construct($options = null) 17 | { 18 | parent::__construct($options); 19 | 20 | if ($this->environment !== null && !$this->environment instanceof \Twig_Environment) { 21 | throw new InvalidOptionsException(sprintf('Option "environment" must be null or a Twig_Environment instance for constraint %s', __CLASS__), array('environment')); 22 | } 23 | } 24 | 25 | public function validatedBy() 26 | { 27 | return 'TwigSyntaxValidator'; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Validator/Constraint/TwigSyntaxValidator.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class TwigSyntaxValidator extends ConstraintValidator 12 | { 13 | protected $environment; 14 | 15 | public function __construct(\Twig_Environment $environment) 16 | { 17 | $this->environment = $environment; 18 | } 19 | 20 | public function validate($value, Constraint $constraint) 21 | { 22 | if (!$constraint instanceof TwigSyntax) { 23 | throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\TwigSyntax'); 24 | } 25 | 26 | if ($value === '' || $value === null) { 27 | return; 28 | } 29 | 30 | $env = $this->getEnvironment($constraint); 31 | try { 32 | $tokeStream = $env->tokenize($value); 33 | if ($constraint->parse) { 34 | $env->parse($tokeStream); 35 | } 36 | } catch (\Twig_Error_Syntax $e) { 37 | $this->context->addViolation($constraint->message, array( 38 | '{{ value }}' => $this->formatValue($value), 39 | )); 40 | } 41 | } 42 | 43 | protected function getEnvironment(TwigSyntax $constraint) 44 | { 45 | $env = $constraint->environment; 46 | if ($env !== null) { 47 | return $env; 48 | } 49 | 50 | return $this->environment; 51 | } 52 | } 53 | --------------------------------------------------------------------------------