├── 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 | [](https://travis-ci.org/esvit/php-angular) [](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 | }
--------------------------------------------------------------------------------