├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml.dist ├── src ├── TwigGenerator │ └── Builder │ │ ├── BaseBuilder.php │ │ ├── BuilderInterface.php │ │ └── Generator.php └── autoload.php └── tests ├── TwigGenerator └── Tests │ └── Builder │ ├── BaseBuilderTest.php │ ├── Fixtures │ ├── Builder │ │ └── DemoBuilder.php │ └── Templates │ │ └── DemoBuilder.php.twig │ └── GeneratorTest.php └── bootstrap.php /.gitignore: -------------------------------------------------------------------------------- 1 | # Eclipse ide 2 | .settings 3 | .project 4 | .buildpath 5 | 6 | # Composer 7 | composer.lock 8 | composer.phar 9 | 10 | vendor -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | php: 5.3 3 | before_script: 4 | - wget http://getcomposer.org/composer.phar 5 | - php composer.phar install 6 | script: phpunit 7 | notifications: 8 | email: 9 | - cedric.lombardot@gmail.com 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Francois Zaninotto, Cedric Lombardot 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TwigGenerator ![project status](http://stillmaintained.com/cedriclombardot/TwigGenerator.png) ![build status](https://secure.travis-ci.org/cedriclombardot/TwigGenerator.png)# 2 | 3 | TwigGenerator is a PHP code generator based on the [Twig](https://github.com/fabpot/Twig) template engine. It leverages the power of Twig templates to simplify the generation of PHP code, to make it more extensible, and more readable. 4 | 5 | ## Installation 6 | 7 | Checkout this GitHub repository and setup the composer dependencies (Twig and Symfony ClassLoader): 8 | 9 | ``` 10 | git clone https://github.com/cedriclombardot/TwigGenerator.git 11 | cd TwigGenerator 12 | wget -nc http://getcomposer.org/composer.phar 13 | php composer.phar install 14 | ``` 15 | 16 | ## Usage 17 | 18 | To generate PHP classes, you need to create a "Builder", and one or more Twig templates. Then, add the new Builder to a "Generator", and generate the result. 19 | 20 | ### Creating a Builder class 21 | 22 | First, create a class extending `TwigGenerator\Builder\BaseBuilder` - no need for methods at start. 23 | 24 | ```php 25 | setOutputName('MyBuilder.php'); 81 | 82 | // add specific configuration for my builder 83 | $builder->setVariable('className', 'MyBuilder'); 84 | 85 | // create a generator 86 | $generator = new TwigGenerator\Builder\Generator(); 87 | $generator->setTemplateDirs(array( 88 | __DIR__.'/templates', 89 | )); 90 | 91 | // allways regenerate classes even if they exist -> no cache 92 | $generator->setMustOverwriteIfExists(true); 93 | 94 | // set common variables 95 | $generator->setVariables(array( 96 | 'namespace' => 'MyProject\Generated', 97 | )); 98 | 99 | // add the builder to the generator 100 | $generator->addBuilder($builder); 101 | 102 | // You can add other builders here 103 | 104 | // Run generation for all builders 105 | $generator->writeOnDisk(__DIR__.'/Generated'); 106 | ``` 107 | 108 | The file will be generated in `MyProject\Generated\MyBuilder.php`, as follows: 109 | 110 | ```php 111 | =5.3.0", 18 | "twig/twig": ">=1.1, <2.0-dev" 19 | }, 20 | "autoload": { 21 | "psr-0": { "TwigGenerator": "src/" } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | 16 | ./tests/ 17 | 18 | 19 | 20 | 21 | 22 | ./src/TwigGenerator/ 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/TwigGenerator/Builder/BaseBuilder.php: -------------------------------------------------------------------------------- 1 | templateDirectories = $this->getDefaultTemplateDirs(); 76 | $this->templateName = $this->getDefaultTemplateName(); 77 | } 78 | 79 | /** 80 | * {@inheritDoc} 81 | */ 82 | public function setGenerator(Generator $generator) 83 | { 84 | $this->generator = $generator; 85 | } 86 | 87 | /** 88 | * {@inheritDoc} 89 | */ 90 | public function getGenerator() 91 | { 92 | return $this->generator; 93 | } 94 | 95 | /** 96 | * {@inheritDoc} 97 | */ 98 | public function addTemplateDir($templateDir) 99 | { 100 | $this->templateDirectories[$templateDir] = $templateDir; 101 | } 102 | 103 | /** 104 | * {@inheritDoc} 105 | */ 106 | public function setTemplateDirs(array $templateDirs) 107 | { 108 | $this->templateDirectories = $templateDirs; 109 | } 110 | 111 | /** 112 | * {@inheritDoc} 113 | */ 114 | public function getTemplateDirs() 115 | { 116 | return $this->templateDirectories; 117 | } 118 | 119 | /** 120 | * {@inheritDoc} 121 | */ 122 | public function getDefaultTemplateDirs() 123 | { 124 | return array(); 125 | } 126 | 127 | /** 128 | * {@inheritDoc} 129 | */ 130 | public function setTemplateName($templateName) 131 | { 132 | $this->templateName = $templateName; 133 | } 134 | 135 | /** 136 | * {@inheritDoc} 137 | */ 138 | public function getTemplateName() 139 | { 140 | return $this->templateName; 141 | } 142 | 143 | /** 144 | * {@inheritDoc} 145 | */ 146 | public function getDefaultTemplateName() 147 | { 148 | return $this->getSimpleClassName() . self::TWIG_EXTENSION; 149 | } 150 | 151 | /** 152 | * {@inheritDoc} 153 | */ 154 | public function getSimpleClassName($class = null) 155 | { 156 | if (null === $class) { 157 | $class = get_class($this); 158 | } 159 | 160 | $classParts = explode('\\', $class); 161 | $simpleClassName = array_pop($classParts); 162 | 163 | return $simpleClassName; 164 | } 165 | 166 | /** 167 | * {@inheritDoc} 168 | */ 169 | public function setOutputName($outputName) 170 | { 171 | $this->outputName = $outputName; 172 | } 173 | 174 | /** 175 | * {@inheritDoc} 176 | */ 177 | public function getOutputName() 178 | { 179 | return $this->outputName; 180 | } 181 | 182 | /** 183 | * {@inheritDoc} 184 | */ 185 | public function mustOverwriteIfExists() 186 | { 187 | return $this->mustOverwriteIfExists; 188 | } 189 | 190 | /** 191 | * {@inheritDoc} 192 | */ 193 | public function setMustOverwriteIfExists($status = true) 194 | { 195 | $this->mustOverwriteIfExists = $status; 196 | } 197 | 198 | /** 199 | * {@inheritDoc} 200 | */ 201 | public function setVariables(array $variables) 202 | { 203 | $this->variables = $variables; 204 | } 205 | 206 | /** 207 | * {@inheritDoc} 208 | */ 209 | public function setVariable($key, $value) 210 | { 211 | $this->variables[$key] = $value; 212 | } 213 | 214 | /** 215 | * {@inheritDoc} 216 | */ 217 | public function getVariables() 218 | { 219 | return $this->variables; 220 | } 221 | 222 | /** 223 | * {@inheritDoc} 224 | */ 225 | public function hasVariable($key) 226 | { 227 | return isset($this->variables[$key]); 228 | } 229 | 230 | /** 231 | * {@inheritDoc} 232 | */ 233 | public function getVariable($key, $default = null) 234 | { 235 | return $this->hasVariable($key) ? $this->variables[$key] : $default; 236 | } 237 | 238 | /** 239 | * {@inheritDoc} 240 | */ 241 | public function writeOnDisk($outputDirectory) 242 | { 243 | $path = $outputDirectory . DIRECTORY_SEPARATOR . $this->getOutputName(); 244 | $dir = dirname($path); 245 | 246 | if (!is_dir($dir)) { 247 | mkdir($dir, 0777, true); 248 | } 249 | 250 | if (!file_exists($path) || (file_exists($path) && $this->mustOverwriteIfExists)) { 251 | file_put_contents($path, $this->getCode()); 252 | } 253 | } 254 | 255 | /** 256 | * {@inheritDoc} 257 | */ 258 | public function getCode() 259 | { 260 | $loader = new \Twig_Loader_Filesystem($this->getTemplateDirs()); 261 | $twig = new \Twig_Environment($loader, array( 262 | 'autoescape' => false, 263 | 'strict_variables' => true, 264 | 'debug' => true, 265 | 'cache' => $this->getGenerator()->getTempDir(), 266 | )); 267 | 268 | $this->addTwigExtensions($twig, $loader); 269 | $this->addTwigFilters($twig); 270 | $template = $twig->loadTemplate($this->getTemplateName()); 271 | 272 | $variables = $this->getVariables(); 273 | $variables['builder'] = $this; 274 | 275 | return $template->render($variables); 276 | } 277 | 278 | /** 279 | * {@inheritDoc} 280 | */ 281 | public function addTwigFilters(\Twig_Environment $twig) 282 | { 283 | foreach ($this->twigFilters as $twigFilter) { 284 | if (($pos = strpos($twigFilter, ':')) !== false) { 285 | $twigFilterName = substr($twigFilter, $pos + 2); 286 | } else { 287 | $twigFilterName = $twigFilter; 288 | } 289 | $twig->addFilter($twigFilterName, new \Twig_Filter_Function($twigFilter)); 290 | } 291 | } 292 | 293 | /** 294 | * {@inheritDoc} 295 | */ 296 | public function addTwigExtensions(\Twig_Environment $twig, \Twig_LoaderInterface $loader) 297 | { 298 | foreach ($this->twigExtensions as $twigExtensionName) { 299 | $twigExtension = new $twigExtensionName($loader); 300 | $twig->addExtension($twigExtension); 301 | } 302 | } 303 | } 304 | -------------------------------------------------------------------------------- /src/TwigGenerator/Builder/BuilderInterface.php: -------------------------------------------------------------------------------- 1 | tempDir = realpath($baseTempDir).DIRECTORY_SEPARATOR.self::TEMP_DIR_PREFIX; 64 | 65 | if (!is_dir($this->tempDir)) { 66 | mkdir($this->tempDir, 0777, true); 67 | } 68 | } 69 | 70 | public function setAutoRemoveTempDir($autoRemoveTempDir = true) 71 | { 72 | $this->autoRemoveTempDir = $autoRemoveTempDir; 73 | } 74 | 75 | public function setMustOverwriteIfExists($status = true) 76 | { 77 | $this->mustOverwriteIfExists = $status; 78 | } 79 | 80 | public function setTemplateDirs(array $templateDirs) 81 | { 82 | $this->templateDirectories = $templateDirs; 83 | } 84 | 85 | /** 86 | * Ensure to remove the temp directory. 87 | */ 88 | public function __destruct() 89 | { 90 | if ($this->tempDir && is_dir($this->tempDir) && $this->autoRemoveTempDir) { 91 | $this->removeDir($this->tempDir); 92 | } 93 | } 94 | 95 | /** 96 | * @param string The temporary directory path 97 | */ 98 | public function setTempDir($tempDir) 99 | { 100 | $this->tempDir = $tempDir; 101 | } 102 | 103 | /** 104 | * @return string The temporary directory. 105 | */ 106 | public function getTempDir() 107 | { 108 | return $this->tempDir; 109 | } 110 | 111 | /** 112 | * @return array The list of builders. 113 | */ 114 | public function getBuilders() 115 | { 116 | return $this->builders; 117 | } 118 | 119 | /** 120 | * Add a builder. 121 | * 122 | * @param \TwigGenerator\Builder\BuilderInterface $builder A builder. 123 | * 124 | * @return \TwigGenerator\Builder\BuilderInterface The builder 125 | */ 126 | public function addBuilder(BuilderInterface $builder) 127 | { 128 | $builder->setGenerator($this); 129 | $builder->setTemplateDirs($this->templateDirectories); 130 | $builder->setMustOverwriteIfExists($this->mustOverwriteIfExists); 131 | $builder->setVariables(array_merge($this->variables, $builder->getVariables())); 132 | 133 | $this->builders[] = $builder; 134 | 135 | return $builder; 136 | } 137 | 138 | /** 139 | * Add an array of variables to pass to builders. 140 | * 141 | * @param array $variables A set of variables. 142 | */ 143 | public function setVariables(array $variables = array()) 144 | { 145 | $this->variables = $variables; 146 | } 147 | 148 | /** 149 | * Generate and write classes to disk. 150 | * 151 | * @param string $outputDirectory An output directory. 152 | */ 153 | public function writeOnDisk($outputDirectory) 154 | { 155 | foreach ($this->getBuilders() as $builder) { 156 | $builder->writeOnDisk($outputDirectory); 157 | } 158 | } 159 | 160 | /** 161 | * Remove a directory. 162 | * 163 | * @param string $target A directory. 164 | */ 165 | private function removeDir($target) 166 | { 167 | $fp = opendir($target); 168 | while (false !== $file = readdir($fp)) { 169 | if (in_array($file, array('.', '..'))) { 170 | continue; 171 | } 172 | 173 | if (is_dir($target.'/'.$file)) { 174 | self::removeDir($target.'/'.$file); 175 | } else { 176 | unlink($target.'/'.$file); 177 | } 178 | } 179 | closedir($fp); 180 | rmdir($target); 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/autoload.php: -------------------------------------------------------------------------------- 1 | assertEquals('DemoBuilder', $builder->getSimpleClassName(), 'getSimpleClassName remove the namespaced part of get_class'); 13 | 14 | $this->assertEquals('Bar', $builder->getSimpleClassName('\\Foo\\Bar'), 'getSimpleClassName remove the namespaced part of get_class'); 15 | } 16 | 17 | public function testGetDefaultTemplateName() 18 | { 19 | $builder = new DemoBuilder(); 20 | $this->assertEquals('DemoBuilder.php.twig', $builder->getDefaultTemplateName(), 'getDefaultTemplateName return the twig file path'); 21 | } 22 | 23 | public function testSetVariables() 24 | { 25 | $builder = new DemoBuilder(); 26 | $builder->setVariables(array('foo' => 'bar')); 27 | $this->assertEquals(array('foo' => 'bar'), $builder->getVariables(), 'setVariables accept an array'); 28 | } 29 | 30 | public function testGetVariable() 31 | { 32 | $builder = new DemoBuilder(); 33 | $builder->setVariables(array('foo' => 'bar')); 34 | $this->assertEquals('bar', $builder->getVariable('foo','default')); 35 | $this->assertEquals('default', $builder->getVariable('nonexistant','default')); 36 | } 37 | 38 | public function testSetVariable() 39 | { 40 | $builder = new DemoBuilder(); 41 | $builder->setVariables(array('foo' => 'bar')); 42 | 43 | $builder->setVariable('foo', 'demo'); 44 | $this->assertEquals('demo', $builder->getVariable('foo'), 'setVariable overwrite the default variables setted'); 45 | 46 | $builder->setVariable('add', 'bar'); 47 | $this->assertEquals('bar', $builder->getVariable('add'), 'setVariable can also add a variable'); 48 | } 49 | 50 | public function testHasVariable() 51 | { 52 | $builder = new DemoBuilder(); 53 | $builder->setVariables(array('foo' => 'bar')); 54 | $this->assertTrue($builder->hasVariable('foo'), 'hasVariable return true on a valid key'); 55 | $this->assertFalse($builder->hasVariable('var'), 'hasVariable return false on a invalid key'); 56 | } 57 | 58 | public function testGetCode() 59 | { 60 | $builder = $this->initBuilder(); 61 | 62 | $this->assertEquals('Hello cedric !', $builder->getCode()); 63 | 64 | $builder->setVariables(array('name' => 'Tux')); 65 | $this->assertEquals('Hello Tux !', $builder->getCode(), 'If i change variables code is changed'); 66 | } 67 | 68 | public function testWriteOnDisk() 69 | { 70 | $builder = $this->initBuilder(); 71 | 72 | $builder->writeOnDisk(sys_get_temp_dir()); 73 | $this->assertTrue(file_exists(sys_get_temp_dir() . '/test.php')); 74 | $this->assertEquals('Hello cedric !', file_get_contents(sys_get_temp_dir() . '/test.php')); 75 | 76 | $builder->setVariables(array('name' => 'Tux')); 77 | $builder->writeOnDisk(sys_get_temp_dir()); 78 | $this->assertTrue($builder->mustOverwriteIfExists()); 79 | $this->assertTrue(file_exists(sys_get_temp_dir() . '/test.php')); 80 | $this->assertEquals('Hello Tux !', file_get_contents(sys_get_temp_dir() . '/test.php'), 'If i change variables code is changed'); 81 | 82 | $builder->setVariables(array('name' => 'cedric')); 83 | $builder->setMustOverwriteIfExists(false); 84 | $builder->writeOnDisk(sys_get_temp_dir()); 85 | $this->assertFalse($builder->mustOverwriteIfExists()); 86 | $this->assertTrue(file_exists(sys_get_temp_dir() . '/test.php')); 87 | $this->assertEquals('Hello Tux !', file_get_contents(sys_get_temp_dir() . '/test.php'), 'If i change variables on an existant files code is not generated'); 88 | 89 | unlink(sys_get_temp_dir() . '/test.php'); 90 | $this->assertFalse(file_exists(sys_get_temp_dir() . '/test.php')); 91 | $builder->writeOnDisk(sys_get_temp_dir()); 92 | $this->assertEquals('Hello cedric !', file_get_contents(sys_get_temp_dir() . '/test.php'), 'If i change variables on a non existant files code is generated'); 93 | } 94 | 95 | protected function initBuilder() 96 | { 97 | $builder = new DemoBuilder(); 98 | $generator = $this->getMockBuilder('TwigGenerator\Builder\Generator') 99 | ->disableOriginalConstructor() 100 | ->getMock(); 101 | 102 | $builder->setGenerator($generator); 103 | $builder->setMustOverwriteIfExists(true); 104 | $builder->setOutputName('test.php'); 105 | $builder->setTemplateDirs(array(__DIR__.'/Fixtures/Templates')); 106 | $builder->setVariables(array('name' => 'cedric')); 107 | $builder->setTemplateName($builder->getDefaultTemplateName()); 108 | 109 | return $builder; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /tests/TwigGenerator/Tests/Builder/Fixtures/Builder/DemoBuilder.php: -------------------------------------------------------------------------------- 1 | setVariables(array('foo' => 'bar')); 14 | 15 | $generator = new Generator(); 16 | $generator->setVariables(array('foo' => 'common bar', 'baz' => 'common baz' )); 17 | $generator->addBuilder($builder); 18 | 19 | $this->assertEquals('bar', $builder->getVariable('foo'), 'Builder variable is more important than common builder variable'); 20 | $this->assertEquals('common baz', $builder->getVariable('baz'), 'Expected common builder variable'); 21 | } 22 | 23 | public function testAddBuilderOnlyBuilderVariables() 24 | { 25 | $builder = new DemoBuilder(); 26 | $builder->setVariables(array('foo' => 'bar')); 27 | 28 | $generator = new Generator(); 29 | $generator->addBuilder($builder); 30 | 31 | $this->assertEquals('bar', $builder->getVariable('foo'), 'Expected builder variable'); 32 | } 33 | 34 | public function testAddBuilderOnlyGeneratorCommonVariables() 35 | { 36 | $builder1 = new DemoBuilder(); 37 | $builder2 = new DemoBuilder(); 38 | 39 | $generator = new Generator(); 40 | $generator->setVariables(array('foo' => 'common foo')); 41 | $generator->addBuilder($builder1); 42 | $generator->addBuilder($builder2); 43 | 44 | $this->assertEquals('common foo', $builder1->getVariable('foo'), 'Expected common builder variable'); 45 | $this->assertEquals('common foo', $builder2->getVariable('foo'), 'Expected common builder variable'); 46 | } 47 | 48 | public function testCallAddBuilderBeforeSetVariables() 49 | { 50 | $builder = new DemoBuilder(); 51 | 52 | $generator = new Generator(); 53 | $generator->addBuilder($builder); 54 | $generator->setVariables(array('foo' => 'bar')); 55 | 56 | $this->assertNull($builder->getVariable('foo'), ' If addBuilder is called before setVariables then common builder variables will be skipped'); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | add('TwigGenerator\\Tests', 'tests'); 9 | --------------------------------------------------------------------------------