├── examples ├── views │ ├── include.html │ └── index.html └── index.php ├── .gitignore ├── .coveralls.yml ├── tests ├── BaseCase.php └── bootstrap.php ├── src └── Bazalt │ ├── Angular │ ├── Directive │ │ ├── NgApp.php │ │ ├── NgModel.php │ │ ├── NgIf.php │ │ ├── NgBind.php │ │ ├── NgInclude.php │ │ └── NgRepeat.php │ ├── DOMNode.php │ ├── Directive.php │ ├── Module.php │ ├── Scope.php │ └── Parse.php │ └── Angular.php ├── .travis.yml ├── README.md ├── composer.json └── phpunit.xml.dist /examples/views/include.html: -------------------------------------------------------------------------------- 1 | Test template -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | composer.lock 3 | composer.phar 4 | build -------------------------------------------------------------------------------- /.coveralls.yml: -------------------------------------------------------------------------------- 1 | src_dir: src 2 | 3 | coverage_clover: build/logs/clover.xml -------------------------------------------------------------------------------- /tests/BaseCase.php: -------------------------------------------------------------------------------- 1 | add('tests', __DIR__ . '/..'); 9 | $loader->register(); -------------------------------------------------------------------------------- /src/Bazalt/Angular/Directive/NgModel.php: -------------------------------------------------------------------------------- 1 | attributes(); 10 | $attrValue = $attrs['ng-model']; 11 | 12 | $this->element->setAttribute('value', $this->scope[$attrValue]); 13 | } 14 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.4 5 | - 5.5 6 | 7 | env: 8 | - APPLICATION_ENV="testing" 9 | 10 | before_script: 11 | - composer self-update 12 | - composer install --dev --no-interaction 13 | 14 | script: 15 | - mkdir -p tmp/logs 16 | - mkdir -p build/logs 17 | - phpunit --configuration phpunit.xml.dist 18 | 19 | after_script: 20 | - php vendor/bin/coveralls -v -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | AngularJS template engine on PHP 2 | ================================ 3 | 4 | [![Build Status](https://travis-ci.org/esvit/php-angular.png)](https://travis-ci.org/esvit/php-angular) [![Coverage Status](https://coveralls.io/repos/esvit/php-angular/badge.png)](https://coveralls.io/r/esvit/php-angular) 5 | 6 | Code licensed under New BSD License. 7 | 8 | The implementation of template engine on PHP for compile AngularJS templates. -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bazalt/angular", 3 | "license": "New BSD License", 4 | "authors": [ 5 | { 6 | "name": "Vitalii Savchuk", 7 | "email": "esvit666@gmail.com" 8 | } 9 | ], 10 | "autoload": { 11 | "psr-0": { 12 | "Bazalt\\Angular": "src/" 13 | } 14 | }, 15 | "require": { 16 | "php": ">=5.4.0", 17 | "analog/analog": "dev-master" 18 | }, 19 | "require-dev": { 20 | "satooshi/php-coveralls": "dev-master" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Bazalt/Angular/Directive/NgIf.php: -------------------------------------------------------------------------------- 1 | attributes(); 10 | $attrValue = $attrs['ng-if']; 11 | 12 | $value = $this->scope->getValue($attrValue); 13 | $this->element->removeAttribute('ng-if'); 14 | if (!$value) { 15 | $parent = $this->element->parentNode; 16 | $parent->removeChild($this->element); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/views/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | 9 |
10 |

Hello {{ yourName | upper }}! 11 | {{test | upper|lower}} 12 |

13 | 14 |
15 |
16 | {{item.title}} 17 |
18 |
19 | 20 |
21 |
22 | 23 | -------------------------------------------------------------------------------- /src/Bazalt/Angular.php: -------------------------------------------------------------------------------- 1 | modules[$name] = new Angular\Module($name, $options); 13 | return $this->modules[$name]; 14 | } 15 | if (isset($this->modules[$name])) { 16 | return $this->modules[$name]; 17 | } 18 | return null; 19 | } 20 | 21 | public function bootstrap($name) 22 | { 23 | $module = $this->modules[$name]; 24 | return $module->html(); 25 | } 26 | } -------------------------------------------------------------------------------- /src/Bazalt/Angular/Directive/NgBind.php: -------------------------------------------------------------------------------- 1 | scope->getValue($key); 13 | if (!$value) { 14 | return $matches[0]; 15 | } 16 | return $value; 17 | } 18 | 19 | public function apply() 20 | { 21 | $this->element->nodeValue = preg_replace_callback('|{{\s*(?[a-z0-9\.]*)\s*(\|\s*(?.*))?\s*}}|im', [$this, 'parseValue'], $this->element->wholeText); 22 | } 23 | } -------------------------------------------------------------------------------- /src/Bazalt/Angular/DOMNode.php: -------------------------------------------------------------------------------- 1 | element = $element; 16 | $this->directives = $directives; 17 | 18 | foreach ($directives as $directive) { 19 | $directive->node($this); 20 | } 21 | } 22 | 23 | public function apply() 24 | { 25 | foreach ($this->directives as $directive) { 26 | $directive->apply(); 27 | } 28 | foreach ($this->childNodes as $node) { 29 | $node->apply(); 30 | } 31 | } 32 | 33 | public function nodes($nodes) 34 | { 35 | $this->childNodes = $nodes; 36 | } 37 | } -------------------------------------------------------------------------------- /src/Bazalt/Angular/Directive/NgInclude.php: -------------------------------------------------------------------------------- 1 | attributes(); 12 | $attrValue = trim($attrs['ng-include'], ' \''); 13 | $this->element->removeAttribute('ng-include'); 14 | 15 | $options = $this->module->options(); 16 | $filename = $options['dir'] . $attrValue; 17 | 18 | $fragment = file_get_contents($filename); 19 | 20 | $frag = $this->element->ownerDocument->createDocumentFragment(); 21 | $frag->appendXML($fragment); 22 | 23 | $this->element->appendChild($frag); 24 | 25 | $nodes = [ $this->module->parser->parse($frag, $this->scope) ]; 26 | $this->node->nodes($nodes); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Bazalt/Angular/Directive.php: -------------------------------------------------------------------------------- 1 | scope = $scope; 18 | $this->element = $element; 19 | $this->module = $module; 20 | } 21 | 22 | public function link($scope, \DOMElement $element, $attributes = []) 23 | { 24 | } 25 | 26 | public function node($node) 27 | { 28 | $this->node = $node; 29 | } 30 | 31 | public function attributes() 32 | { 33 | $attributes = []; 34 | for ($j = 0; $j < $this->element->attributes->length; $j++) { 35 | $attribute = $this->element->attributes->item($j); 36 | $attributes[$attribute->name] = $attribute->value; 37 | } 38 | return $attributes; 39 | } 40 | } -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | tests 23 | 24 | 25 | 26 | 27 | 28 | benchmark 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /examples/index.php: -------------------------------------------------------------------------------- 1 | module('app', [ 7 | 'dir' => __DIR__, 8 | 'file' => '/views/index.html' 9 | ]); 10 | 11 | $app->rootScope['test'] = '123'; 12 | $app->rootScope['yourName'] = 'Vitalik'; 13 | $app->rootScope['items'] = [ 14 | [ 'title' => 'Item 1', 'show' => true ], 15 | [ 'title' => 'Item 2', 'show' => false ], 16 | [ 'title' => 'Item 3', 'show' => true ], 17 | [ 'title' => 'Item 4', 'show' => false ], 18 | [ 'title' => 'Item 5', 'show' => true ] 19 | ]; 20 | 21 | $app->directive('ng-app', [ 22 | 'restrict' => 'A', 23 | 'class' => 'Bazalt\\Angular\\Directive\\NgApp' 24 | ]); 25 | $app->directive('ng-model', [ 26 | 'restrict' => 'A', 27 | 'class' => 'Bazalt\\Angular\\Directive\\NgModel' 28 | ]); 29 | $app->directive('ng-repeat', [ 30 | 'restrict' => 'A', 31 | 'class' => 'Bazalt\\Angular\\Directive\\NgRepeat' 32 | ]); 33 | $app->directive('ng-include', [ 34 | 'restrict' => 'A', 35 | 'class' => 'Bazalt\\Angular\\Directive\\NgInclude' 36 | ]); 37 | $app->directive('ng-if', [ 38 | 'restrict' => 'A', 39 | 'class' => 'Bazalt\\Angular\\Directive\\NgIf' 40 | ]); 41 | 42 | echo $angular->bootstrap('app'); -------------------------------------------------------------------------------- /src/Bazalt/Angular/Module.php: -------------------------------------------------------------------------------- 1 | name = $name; 22 | $this->options = $options; 23 | $this->rootScope = new Scope(); 24 | } 25 | 26 | public function directives() 27 | { 28 | return $this->directives; 29 | } 30 | 31 | public function options() 32 | { 33 | return $this->options; 34 | } 35 | 36 | public function directive($name, $func) 37 | { 38 | $this->directives[$name] = $func; 39 | } 40 | 41 | public function html() 42 | { 43 | $filename = $this->options['dir'] . DIRECTORY_SEPARATOR . $this->options['file']; 44 | 45 | $this->document = new \DOMDocument(); 46 | $this->document->loadHTMLFile($filename); 47 | 48 | $this->parser = new Parse($this); 49 | 50 | $node = $this->parser->parse($this->document, $this->rootScope); 51 | $node->apply(); 52 | 53 | return $this->document->saveHtml(); 54 | } 55 | } -------------------------------------------------------------------------------- /src/Bazalt/Angular/Directive/NgRepeat.php: -------------------------------------------------------------------------------- 1 | attributes(); 13 | $attrValue = $attrs['ng-repeat']; 14 | if (!preg_match('|(?.*)\s*in\s*(?.*)|im', $attrValue, $matches)) { 15 | Analog::error(sprintf('Invalid value "%s" for ng-repeat directive', $attrValue)); 16 | throw new \Exception(sprintf('Invalid value "%s" for ng-repeat directive', $attrValue)); 17 | return; 18 | } 19 | $item = trim($matches['item']); 20 | $array = trim($matches['array']); 21 | 22 | //print_r($this->scope[$array]); 23 | $parent = $this->element->parentNode; 24 | 25 | $nodes = []; 26 | foreach ($this->scope[$array] as $value) { 27 | $node = $this->element->cloneNode(true); 28 | $parent->insertBefore($node, $this->element); 29 | 30 | $scope = $this->scope->newScope(); 31 | $scope[$item] = $value; 32 | 33 | $node->removeAttribute('ng-repeat'); 34 | $nodes []= $this->module->parser->parse($node, $scope); 35 | } 36 | $parent->removeChild($this->element); 37 | $this->node->nodes($nodes); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Bazalt/Angular/Scope.php: -------------------------------------------------------------------------------- 1 | variables = $variables; 15 | $this->parent = $parent; 16 | } 17 | 18 | public function newScope() 19 | { 20 | $scope = new Scope(); 21 | $scope->parent = $this; 22 | 23 | return $scope; 24 | } 25 | 26 | public function getValue($compositeKey) { 27 | $root = $this; 28 | 29 | $keys = explode('.', $compositeKey); 30 | while(count($keys) > 0) { 31 | $key = array_shift($keys); 32 | if(!isset($root[$key])) { 33 | return null; 34 | } 35 | $root = $root[$key]; 36 | } 37 | return $root; 38 | } 39 | 40 | public function offsetExists($offset) 41 | { 42 | $localExists = isset($this->variables[$offset]); 43 | $parentExists = isset($this->parent) && $this->parent->offsetExists($offset); 44 | 45 | return $localExists || $parentExists; 46 | } 47 | 48 | public function offsetGet($offset) 49 | { 50 | if (!isset($this->variables[$offset]) && $this->parent) { 51 | return $this->parent->offsetGet($offset); 52 | } 53 | return $this->variables[$offset]; 54 | } 55 | 56 | public function offsetSet($offset, $value) 57 | { 58 | $this->variables[$offset] = $value; 59 | } 60 | 61 | public function offsetUnset($offset) 62 | { 63 | unset($this->variables[$offset]); 64 | } 65 | } -------------------------------------------------------------------------------- /src/Bazalt/Angular/Parse.php: -------------------------------------------------------------------------------- 1 | module = $module; 12 | } 13 | 14 | public function parse(\DOMNode $node, $scope) 15 | { 16 | $directive = $this->detectDirective($node, $scope); 17 | 18 | $nodes = []; 19 | // traverse nodes 20 | if ($node->childNodes) { 21 | for ($i = 0; $i < $node->childNodes->length; $i++) { 22 | $childNode = $node->childNodes->item($i); 23 | 24 | $nodes []= $this->parse($childNode, $scope); 25 | } 26 | } 27 | $directive->nodes($nodes); 28 | return $directive; 29 | } 30 | 31 | protected function detectDirective(\DOMNode $node, $scope) 32 | { 33 | $directives = $this->module->directives(); 34 | $nodeDirectives = []; 35 | 36 | // if element - check attributes 37 | if ($node instanceof \DOMElement) { 38 | for ($j = 0; $j < $node->attributes->length; $j++) { 39 | $attribute = $node->attributes->item($j); 40 | foreach ($directives as $name => $directive) { 41 | if (strpos($directive['restrict'], 'A') !== false) { 42 | if ($name == $attribute->name) { 43 | $nodeDirectives []= new $directive['class']($node, $scope, $this->module); 44 | } 45 | } 46 | } 47 | } 48 | } 49 | // text 50 | if ($node instanceof \DOMText) { 51 | $nodeDirectives []= new Directive\NgBind($node, $scope, $this); 52 | } 53 | 54 | return new DOMNode($node, $nodeDirectives); 55 | } 56 | } --------------------------------------------------------------------------------