├── tests ├── coverage │ └── .gitkeep └── spec │ ├── FactorySpec.php │ ├── BuilderSpec.php │ └── ParserSpec.php ├── .gitignore ├── .coveralls.yml ├── test.conf ├── phpspec.yml ├── phpcs.xml ├── src ├── Exception │ ├── GrammarException.php │ └── UnrecognizedContextException.php ├── Node │ ├── Literal.php │ ├── RootNode.php │ ├── Param.php │ ├── Context.php │ ├── Directive.php │ └── Node.php ├── Command │ ├── AddUpstreamCommand.php │ ├── RemoveUpstream.php │ ├── RemoveLocationCommand.php │ ├── AddUpstreamServerCommand.php │ ├── RemoveUpstreamServerCommand.php │ ├── BaseCommand.php │ ├── RemoveServerCommand.php │ ├── AddServerCommand.php │ └── AddLocationCommand.php ├── Config │ ├── Server.php │ ├── Http.php │ ├── Events.php │ ├── Upstream.php │ └── Location.php ├── Factory.php ├── Builder.php └── Parser.php ├── .travis.yml ├── examples ├── default.conf ├── parse.php └── build.php ├── phpunit.xml ├── LICENSE ├── composer.json ├── bin └── ngxconf └── README.md /tests/coverage/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | /.sonar/ 3 | /.idea/ 4 | /bin/* 5 | !/bin/ngxconf 6 | /tests/coverage/* 7 | !tests/coverage/.gitkeep 8 | -------------------------------------------------------------------------------- /.coveralls.yml: -------------------------------------------------------------------------------- 1 | # for php-coveralls 2 | coverage_clover: tests/coverage/clover.xml 3 | json_path: tests/coverage/coveralls-upload.json -------------------------------------------------------------------------------- /test.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 90; 3 | listen [::]:90 default ipv6only=on; 4 | server_name test-ms; 5 | } 6 | 7 | server { 8 | listen 90; 9 | listen [::]:90 default ipv6only=on; 10 | server_name test-ms; 11 | } 12 | -------------------------------------------------------------------------------- /phpspec.yml: -------------------------------------------------------------------------------- 1 | extensions: 2 | - PhpSpec\Extension\CodeCoverageExtension 3 | - Knp\PhpSpec\WellDone\Extension 4 | 5 | suites: 6 | types: 7 | namespace: Madkom\NginxConfigurator 8 | psr4_prefix: Madkom\NginxConfigurator 9 | spec_path: tests 10 | 11 | code_coverage: 12 | output: tests/coverage/phpspec.cov 13 | format: php 14 | 15 | formatter.name: pretty -------------------------------------------------------------------------------- /phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Project Coding Standard 4 | ./src 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Exception/GrammarException.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class GrammarException extends Exception 18 | { 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/Exception/UnrecognizedContextException.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class UnrecognizedContextException extends Exception 18 | { 19 | 20 | } 21 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 7.0 5 | - nightly 6 | 7 | sudo: false 8 | 9 | matrix: 10 | allow_failures: 11 | - php: hhvm 12 | - php: nightly 13 | 14 | before_install: 15 | - composer self-update 16 | 17 | install: 18 | - composer install --prefer-dist --no-interaction 19 | 20 | script: 21 | - bin/phpspec run --format=pretty --no-code-generation 22 | - bin/phpcov merge --clover tests/coverage/clover.xml tests/coverage 23 | 24 | after_success: 25 | - travis_retry php bin/coveralls -v -------------------------------------------------------------------------------- /src/Node/Literal.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class Literal extends Param 16 | { 17 | /** 18 | * @return string 19 | */ 20 | public function __toString() : string 21 | { 22 | return '"' . addslashes($this->value) . '"'; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Command/AddUpstreamCommand.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class AddUpstreamCommand extends BaseCommand 16 | { 17 | protected function configure() 18 | { 19 | parent::configure(); 20 | $this->setName('upstream:add'); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Command/RemoveUpstream.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class RemoveUpstreamCommand extends BaseCommand 16 | { 17 | protected function configure() 18 | { 19 | parent::configure(); 20 | $this->setName('upstream:remove'); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Command/RemoveLocationCommand.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class RemoveLocationCommand extends BaseCommand 16 | { 17 | protected function configure() 18 | { 19 | parent::configure(); 20 | $this->setName('location:remove'); 21 | $this->setDescription("Remove location context and it's configuration"); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Config/Server.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class Server extends Context 18 | { 19 | /** 20 | * Server constructor. 21 | * @param array $directives 22 | */ 23 | public function __construct(array $directives = []) 24 | { 25 | parent::__construct('server', $directives); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Node/RootNode.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class RootNode extends Node 16 | { 17 | /** 18 | * RootNode constructor. 19 | * @param Node[] $nodes 20 | */ 21 | public function __construct(array $nodes = []) 22 | { 23 | parent::__construct(''); 24 | foreach ($nodes as $node) { 25 | $this->append($node); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Command/AddUpstreamServerCommand.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class AddUpstreamServerCommand extends BaseCommand 16 | { 17 | protected function configure() 18 | { 19 | parent::configure(); 20 | $this->setName('upstream:server:add'); 21 | $this->setDescription("Adds server directive to upstream context and configuration"); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Command/RemoveUpstreamServerCommand.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class RemoveUpstreamServerCommand extends BaseCommand 16 | { 17 | protected function configure() 18 | { 19 | parent::configure(); 20 | $this->setName('upstream:server:remove'); 21 | $this->setDescription("Removes server directive from upstream context and configuration"); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Config/Http.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | class Http extends Context 19 | { 20 | /** 21 | * Http constructor. 22 | * @param Directive[] $directives 23 | */ 24 | public function __construct(array $directives = []) 25 | { 26 | parent::__construct('http', $directives); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Config/Events.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | class Events extends Context 19 | { 20 | /** 21 | * Events constructor. 22 | * @param Directive[] $directives 23 | */ 24 | public function __construct(array $directives = []) 25 | { 26 | parent::__construct('events', $directives); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/default.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 8080; 3 | root /data/www/web; 4 | index index.php index.html index.htm; 5 | 6 | location / { 7 | try_files $uri $uri/ /index.php; 8 | } 9 | 10 | error_page 404 /404.html; 11 | 12 | error_page 500 502 503 504 /50x.html; 13 | location = /50x.html { 14 | root /usr/share/nginx/www; 15 | } 16 | 17 | # pass the PHP scripts to FastCGI server listening on the php-fpm socket 18 | location ~ \.php$ { 19 | try_files $uri =404; 20 | fastcgi_pass unix:/var/run/php5-fpm.sock; 21 | fastcgi_index index.php; 22 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 23 | include fastcgi_params; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/spec/FactorySpec.php: -------------------------------------------------------------------------------- 1 | 14 | * @mixin Factory 15 | */ 16 | class FactorySpec extends ObjectBehavior 17 | { 18 | function it_is_initializable() 19 | { 20 | $this->shouldHaveType(Factory::class); 21 | } 22 | 23 | function it_can_create_Server_node() 24 | { 25 | $this->createServer(80)->shouldReturnAnInstanceOf(Server::class); 26 | } 27 | 28 | function it_can_create_Location_node() 29 | { 30 | $this->createLocation('/test', '~')->shouldReturnAnInstanceOf(Location::class); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Node/Param.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class Param 16 | { 17 | /** 18 | * @var string 19 | */ 20 | protected $value; 21 | 22 | /** 23 | * Param constructor. 24 | * @param string $value 25 | */ 26 | public function __construct(string $value) 27 | { 28 | $this->value = $value; 29 | } 30 | 31 | /** 32 | * Retrieve param value 33 | * @return string 34 | */ 35 | public function getValue() : string 36 | { 37 | return $this->value; 38 | } 39 | 40 | /** 41 | * @return string 42 | */ 43 | public function __toString() : string 44 | { 45 | return $this->value; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | ./tests/phpunit/ 10 | ./vendor 11 | ./app 12 | 13 | 14 | 15 | 16 | ./vendor 17 | ./app 18 | ./bin 19 | ./tests 20 | ./var/bootstrap.php.cache 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /tests/spec/BuilderSpec.php: -------------------------------------------------------------------------------- 1 | 14 | * @mixin Builder 15 | */ 16 | class BuilderSpec extends ObjectBehavior 17 | { 18 | function it_is_initializable() 19 | { 20 | $this->shouldHaveType(Builder::class); 21 | } 22 | 23 | function it_can_build_with_Server_node(Server $server) 24 | { 25 | $server->__toString()->willReturn("server { 26 | }"); 27 | $this->append($server); 28 | $this->dump()->shouldBeString(); 29 | } 30 | 31 | function it_can_build_with_Upstream_node(Upstream $upstream) 32 | { 33 | $upstream->__toString()->willReturn("upstream name { 34 | }"); 35 | $this->append($upstream); 36 | $this->dump()->shouldBeString(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Madkom S.A. 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 13 | all 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 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /src/Node/Context.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | abstract class Context extends Node 16 | { 17 | /** 18 | * Context constructor. 19 | * @param string $name 20 | * @param Directive[] $directives 21 | */ 22 | public function __construct($name, array $directives = []) 23 | { 24 | parent::__construct($name); 25 | foreach ($directives as $directive) { 26 | $this->append($directive); 27 | } 28 | } 29 | 30 | public function __toString() : string 31 | { 32 | $childStrings = []; 33 | foreach ($this->childNodes as $childNode) { 34 | $childStrings[] = implode("\n\t", explode("\n", (string)$childNode)); 35 | } 36 | 37 | return sprintf( 38 | "{$this->name} {\n\t%s\n}\n", 39 | implode("\n\t", $childStrings) 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "madkom/nginx-configurator", 3 | "license": "MIT", 4 | "homepage": "http://madkom.pl/", 5 | "minimum-stability": "dev", 6 | "require": { 7 | "madkom/collection": "1.*", 8 | "ferno/loco": "@dev", 9 | "madkom/uri": "1.*" 10 | }, 11 | "require-dev": { 12 | "henrikbjorn/phpspec-code-coverage": "^2.0.2", 13 | "phpspec/phpspec": "^2.5", 14 | "phpunit/phpunit": "~4", 15 | "knplabs/phpspec-welldone-extension": "dev-master", 16 | "squizlabs/php_codesniffer": "^2.3", 17 | "phpunit/phpcov": "*", 18 | "jakub-onderka/php-parallel-lint": "0.*", 19 | "jakub-onderka/php-console-highlighter": "0.*", 20 | "satooshi/php-coveralls": "dev-master", 21 | "clover/dump": "dev-master", 22 | "symfony/var-dumper": "^3.1" 23 | }, 24 | "repositories": [ 25 | { 26 | "type": "vcs", 27 | "url": "git@github.com:madkom/loco.git" 28 | } 29 | ], 30 | "autoload": { 31 | "psr-4": { 32 | "Madkom\\NginxConfigurator\\": "src/" 33 | } 34 | }, 35 | "authors": [ 36 | { 37 | "name": "Michał Brzuchalski", 38 | "email": "m.brzuchalski@madkom.pl" 39 | } 40 | ], 41 | "config": { 42 | "bin-dir": "bin/" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Config/Upstream.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class Upstream extends Context 20 | { 21 | /** 22 | * Holds upstream name 23 | * @var Param 24 | */ 25 | private $upstream; 26 | 27 | /** 28 | * Upstream constructor. 29 | * @param Param $upstream 30 | * @param Directive[] $directives 31 | */ 32 | public function __construct(Param $upstream, array $directives = []) 33 | { 34 | $this->upstream = $upstream; 35 | parent::__construct('upstream', $directives); 36 | } 37 | 38 | public function getName() : string 39 | { 40 | return (string)$this->upstream->getValue(); 41 | } 42 | 43 | public function __toString() : string 44 | { 45 | return sprintf( 46 | "{$this->name} %s {\n\t%s\n}\n", 47 | $this->upstream, 48 | implode("\n\t", (array)$this->childNodes->getIterator()) 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Factory.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | class Factory 21 | { 22 | /** 23 | * Creates Server node 24 | * @param int $port 25 | * @return Server 26 | */ 27 | public function createServer(int $port = 80) : Server 28 | { 29 | $listenIPv4 = new Directive('listen', [new Param($port)]); 30 | $listenIPv6 = new Directive('listen', [new Param("[::]:{$port}"), new Param('default'), new Param('ipv6only=on')]); 31 | 32 | return new Server([$listenIPv4, $listenIPv6]); 33 | } 34 | 35 | /** 36 | * Creates Location node 37 | * @param string $location 38 | * @param string|null $match 39 | * @return Location 40 | */ 41 | public function createLocation(string $location, string $match = null) : Location 42 | { 43 | return new Location(new Param($location), is_null($match) ? null : new Param($match)); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /bin/ngxconf: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | add(new AddServerCommand()); 30 | $app->add(new RemoveServerCommand()); 31 | $app->add(new AddLocationCommand()); 32 | $app->add(new RemoveLocationCommand()); 33 | $app->add(new AddUpstreamServerCommand()); 34 | $app->add(new RemoveUpstreamServerCommand()); 35 | $app->run(); 36 | -------------------------------------------------------------------------------- /examples/parse.php: -------------------------------------------------------------------------------- 1 | parse($config); 40 | 41 | /** @var Server $defaultServers[] */ 42 | $defaultServers = $defaultConfig->search(function (Node $node) { 43 | return $node instanceof Server; 44 | }); 45 | 46 | 47 | 48 | $builder = new Builder(); 49 | if (count($defaultServers) > 0) { 50 | /** @var Server $defaultServer */ 51 | foreach ($defaultServers as $defaultServer) { 52 | $builder->appendServerNode($defaultServer); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Config/Location.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class Location extends Context 20 | { 21 | /** 22 | * Holds location match 23 | * @var Param 24 | */ 25 | private $location; 26 | 27 | /** 28 | * Holds location match modifier 29 | * @var Param 30 | */ 31 | private $match; 32 | 33 | /** 34 | * Location constructor. 35 | * @param Param $location 36 | * @param Param $match 37 | * @param array $directives 38 | */ 39 | public function __construct(Param $location, Param $match = null, array $directives = []) 40 | { 41 | $this->location = $location; 42 | $this->match = $match; 43 | parent::__construct('location', $directives); 44 | } 45 | 46 | public function __toString() : string 47 | { 48 | return sprintf( 49 | "{$this->name} %s %s {\n\t%s\n}\n", 50 | $this->match, 51 | $this->location, 52 | implode("\n\t", (array)$this->childNodes->getIterator()) 53 | ); 54 | } 55 | 56 | /** 57 | * @return Param 58 | */ 59 | public function getLocation() : string 60 | { 61 | return (string)$this->location; 62 | } 63 | 64 | /** 65 | * @return Param 66 | */ 67 | public function getMatch() : string 68 | { 69 | return (string)$this->match; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Command/BaseCommand.php: -------------------------------------------------------------------------------- 1 | 24 | */ 25 | abstract class BaseCommand extends Command 26 | { 27 | protected function configure() 28 | { 29 | $this->addOption('file', 'f', InputOption::VALUE_OPTIONAL, 'Output filename', 'php://stdout'); 30 | } 31 | 32 | /** 33 | * @param InputInterface $input 34 | * @return RootNode 35 | * @throws Exception 36 | */ 37 | protected function getConfig(InputInterface $input) : RootNode 38 | { 39 | $filename = $input->getOption('file'); 40 | if ($filename != 'php://stdout' && !file_exists($filename)) { 41 | @touch($filename); 42 | } 43 | if ($filename != 'php://stdout' && file_exists($filename)) { 44 | if (!is_writable($filename)) { 45 | throw new Exception('Given filename is not writable!'); 46 | } 47 | } 48 | if ($filename != 'php://stdout' && file_exists($filename)) { 49 | $parser = new Parser(); 50 | return $parser->parseFile($filename); 51 | } 52 | 53 | return new RootNode(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Node/Directive.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | class Directive extends Node 19 | { 20 | /** 21 | * Holds directive name 22 | * @var string 23 | */ 24 | protected $name; 25 | /** 26 | * Holds param collection 27 | * @var CustomTypedCollection|Param[] 28 | */ 29 | protected $params; 30 | 31 | /** 32 | * Directive constructor. 33 | * @param string $name 34 | * @param array $params 35 | */ 36 | public function __construct(string $name, array $params = []) 37 | { 38 | parent::__construct($name); 39 | $this->params = new class($params) extends CustomTypedCollection { 40 | 41 | /** 42 | * Retrieves collection type 43 | * @return string 44 | */ 45 | protected function getType() : string 46 | { 47 | return Param::class; 48 | } 49 | }; 50 | } 51 | 52 | /** 53 | * Retrieve directive name 54 | * @return string 55 | */ 56 | public function getName() : string 57 | { 58 | return $this->name; 59 | } 60 | 61 | /** 62 | * Retrieve params iterator 63 | * @return Traversable|Param[] 64 | */ 65 | public function getParams() : Traversable 66 | { 67 | return $this->params->getIterator(); 68 | } 69 | 70 | /** 71 | * @return string 72 | */ 73 | public function __toString() : string 74 | { 75 | return sprintf("{$this->name} %s;", implode(' ', (array)$this->params->getIterator())); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Command/RemoveServerCommand.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | class RemoveServerCommand extends BaseCommand 25 | { 26 | protected function configure() 27 | { 28 | parent::configure(); 29 | $this->setName('server:remove'); 30 | $this->setDescription("Removes server context and it's configuration"); 31 | $this->addArgument('name', InputArgument::OPTIONAL, 'Server hostname:port', 'localhost:80'); 32 | } 33 | 34 | protected function execute(InputInterface $input, OutputInterface $output) 35 | { 36 | $filename = $input->getOption('file'); 37 | $builder = $this->getConfig($input); 38 | 39 | list($name, $port) = explode(':', $input->getArgument('name') . ':80'); 40 | 41 | $listenIPv4 = new Directive('listen', [new Param($port)]); 42 | $listenIPv6 = new Directive('listen', [new Param("[::]:{$port}"), new Param('default'), new Param('ipv6only=on')]); 43 | 44 | $server = new Server([$listenIPv4, $listenIPv6]); 45 | if ($name != 'localhost' && !empty($name)) { 46 | $server->append(new Directive('server_name', [new Param($name)])); 47 | } 48 | 49 | $builder->appendServerNode($server); 50 | $builder->dumpFile($filename); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /examples/build.php: -------------------------------------------------------------------------------- 1 | addServerNode(80); 14 | $server->append(new Directive('error_log', [new Param('/var/log/nginx/error.log'), new Param('debug')])); 15 | $server->append(new Location(new Param('/test'), null, [ 16 | new Directive('error_page', [new Param('401'), new Param('@unauthorized')]), 17 | new Directive('set', [new Param('$auth_user'), new Literal('none')]), 18 | new Directive('auth_request', [new Param('/auth')]), 19 | new Directive('proxy_pass', [new Param('http://test-service')]), 20 | ])); 21 | $server->append(new Location(new Param('/auth'), null, [ 22 | new Directive('proxy_pass', [new Param('http://auth-service:9999')]), 23 | new Directive('proxy_bind', [new Param('$server_addr')]), 24 | new Directive('proxy_redirect', [new Param('http://$host'), new Param('https://$host')]), 25 | new Directive('proxy_set_header', [new Param('Content-Length'), new Literal("")]), 26 | new Directive('proxy_pass_request_body', [new Param('off')]), 27 | ])); 28 | $server->append(new Location(new Param('@unauthorized'), null, [ 29 | new Directive('return', [new Param('302'), new Param('/login?backurl=$request_uri')]), 30 | ])); 31 | $server->append(new Location(new Param('/login'), null, [ 32 | new Directive('expires', [new Param('-1')]), 33 | new Directive('proxy_pass', [new Param('http://identity-provider-service')]), 34 | new Directive('proxy_bind', [new Param('$server_addr')]), 35 | new Directive('proxy_redirect', [new Param('http://$host'), new Param('https://$host')]), 36 | new Directive('proxy_set_header', [new Param('Content-Length'), new Literal("")]), 37 | new Directive('proxy_pass_request_body', [new Param('off')]), 38 | ])); 39 | 40 | print($builder->dump()); -------------------------------------------------------------------------------- /src/Command/AddServerCommand.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | class AddServerCommand extends BaseCommand 25 | { 26 | protected function configure() 27 | { 28 | parent::configure(); 29 | $this->setName('server:add'); 30 | $this->setDescription("Adds server context and configuration of port and name"); 31 | $this->addArgument('name', InputArgument::OPTIONAL, 'Server hostname:port', 'localhost:80'); 32 | } 33 | 34 | protected function execute(InputInterface $input, OutputInterface $output) 35 | { 36 | $filename = $input->getOption('file'); 37 | $config = $this->getConfig($input); 38 | 39 | list($name, $port) = explode(':', $input->getArgument('name') . ':80'); 40 | 41 | $listenIPv4 = new Directive('listen', [new Param($port)]); 42 | $listenIPv6 = new Directive('listen', [new Param("[::]:{$port}"), new Param('default'), new Param('ipv6only=on')]); 43 | 44 | // TODO: Find server by name 45 | $server = new Server([$listenIPv4, $listenIPv6]); 46 | if ($name != 'localhost' && !empty($name)) { 47 | $server->append(new Directive('server_name', [new Param($name)])); 48 | } 49 | $config->append($server); 50 | 51 | $builder = new Builder(); 52 | $builder->append($server); 53 | $builder->dumpFile($filename); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Builder.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | class Builder implements Countable 22 | { 23 | /** 24 | * @var RootNode Holds configuration root node 25 | */ 26 | protected $rootNode; 27 | 28 | /** 29 | * Builder constructor. 30 | */ 31 | public function __construct() 32 | { 33 | $this->clear(); 34 | } 35 | 36 | /** 37 | * Clears builder root node 38 | */ 39 | public function clear() 40 | { 41 | $this->rootNode = new RootNode(); 42 | } 43 | 44 | /** 45 | * Append child node 46 | * @param Node $node 47 | * @return Node 48 | */ 49 | public function append(Node $node) : Node 50 | { 51 | $this->rootNode->append($node); 52 | 53 | return $node; 54 | } 55 | 56 | /** 57 | * Remove child node 58 | * @param Node $node 59 | * @return bool 60 | */ 61 | public function remove(Node $node) : bool 62 | { 63 | return $this->rootNode->remove($node); 64 | } 65 | 66 | /** 67 | * Search for specified nodes 68 | * @param callable $checker 69 | * @return CustomTypedCollection 70 | */ 71 | public function search(callable $checker) : CustomTypedCollection 72 | { 73 | return $this->rootNode->filter($checker); 74 | } 75 | 76 | /** 77 | * Count elements of an object 78 | * @link http://php.net/manual/en/countable.count.php 79 | * @return int The custom count as an integer. 80 | */ 81 | public function count() 82 | { 83 | return count($this->rootNode); 84 | } 85 | 86 | /** 87 | * Retrieve an external iterator 88 | * @link http://php.net/manual/en/iteratoraggregate.getiterator.php 89 | * @return Traversable An instance of an object implementing Iterator or 90 | */ 91 | public function getIterator() 92 | { 93 | return $this->rootNode->getIterator(); 94 | } 95 | 96 | /** 97 | * @return string 98 | */ 99 | public function dump() : string 100 | { 101 | return (string)$this->rootNode; 102 | } 103 | 104 | /** 105 | * @param string $filename 106 | * @return bool 107 | */ 108 | public function dumpFile(string $filename) : bool 109 | { 110 | return file_put_contents($filename, $this->dump()); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/Command/AddLocationCommand.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | class AddLocationCommand extends BaseCommand 21 | { 22 | protected function configure() 23 | { 24 | parent::configure(); 25 | $this->setName('location:add'); 26 | $this->setDescription("Adds location context and configuration"); 27 | $this->addArgument('name', InputArgument::OPTIONAL, 'Server hostname:port', 'localhost:80'); 28 | $this->addOption('internal', null, InputOption::VALUE_NONE, 'Adds internal directive'); 29 | $this->addOption( 30 | 'proxy_pass', 31 | null, 32 | InputOption::VALUE_OPTIONAL, 33 | 'Adds proxy_pass url (eg. http://proxy/)' 34 | ); 35 | $this->addOption( 36 | 'proxy_bind', 37 | null, 38 | InputOption::VALUE_OPTIONAL, 39 | 'Adds proxy_bind directive url or variable (eg. $server_addr)' 40 | ); 41 | $this->addOption( 42 | 'proxy_redirect', 43 | null, 44 | InputOption::VALUE_OPTIONAL ^ InputOption::VALUE_IS_ARRAY, 45 | 'Adds proxy_redirect directive (eg. http://$host or https://$host)' 46 | ); 47 | $this->addOption( 48 | 'proxy_set_header', 49 | null, 50 | InputOption::VALUE_OPTIONAL ^ InputOption::VALUE_IS_ARRAY, 51 | 'Adds proxy_set_header directive (eg. "Content-Type: text/html"' 52 | ); 53 | $this->addOption( 54 | 'proxy_pass_request_body', 55 | null, 56 | InputOption::VALUE_OPTIONAL, 57 | 'Adds proxy_pass_requeest_body directive (on|off)', 58 | 'on' 59 | ); 60 | 61 | // // new Directive('internal'), 62 | // new Directive('expires', [new Param('-1')]), 63 | // new Directive('proxy_pass', [new Param('http://172.17.0.1:7777')]), 64 | // new Directive('proxy_bind', [new Param('$server_addr')]), 65 | // new Directive('proxy_redirect', [new Param('http://$host'), new Param('https://$host')]), 66 | // new Directive('proxy_set_header', [new Param('Content-Length'), new Literal("")]), 67 | // new Directive('proxy_pass_request_body', [new Param('off')]), 68 | } 69 | 70 | protected function execute(InputInterface $input, OutputInterface $output) 71 | { 72 | 73 | $proxy_pass = $input->getOption('proxy_pass'); 74 | 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Node/Node.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | abstract class Node implements Countable, IteratorAggregate 21 | { 22 | /** 23 | * Holds parent node 24 | * @var Node 25 | */ 26 | protected $parent; 27 | /** 28 | * Holds node name 29 | * @var string 30 | */ 31 | protected $name = ''; 32 | /** 33 | * Holds node children 34 | * @var CustomTypedCollection 35 | */ 36 | protected $childNodes; 37 | 38 | /** 39 | * Node constructor. 40 | * @param string $name 41 | */ 42 | public function __construct(string $name) 43 | { 44 | $this->name = $name; 45 | $this->childNodes = new class extends CustomTypedCollection { 46 | /** 47 | * Retrieves collection type 48 | * @return string 49 | */ 50 | protected function getType() : string 51 | { 52 | return Node::class; 53 | } 54 | }; 55 | } 56 | 57 | /** 58 | * Append new child node 59 | * @param Node $node 60 | * @return bool 61 | */ 62 | public function append(Node $node) : bool 63 | { 64 | $node->parent = $this; 65 | 66 | return $this->childNodes->add($node); 67 | } 68 | 69 | /** 70 | * Remove child node 71 | * @param Node $node 72 | * @return bool 73 | */ 74 | public function remove(Node $node) : bool 75 | { 76 | return $this->childNodes->remove($node); 77 | } 78 | 79 | /** 80 | * Search for specified nodes 81 | * @param callable $checker 82 | * @return CustomTypedCollection 83 | */ 84 | public function search(callable $checker) : CustomTypedCollection 85 | { 86 | return $this->childNodes->filter($checker); 87 | } 88 | 89 | /** 90 | * Count elements of an object 91 | * @link http://php.net/manual/en/countable.count.php 92 | * @return int The custom count as an integer. 93 | *

94 | *

95 | * The return protocol is cast to an integer. 96 | * @since 5.1.0 97 | */ 98 | public function count() 99 | { 100 | return count($this->childNodes); 101 | } 102 | 103 | /** 104 | * Retrieve an external iterator 105 | * @link http://php.net/manual/en/iteratoraggregate.getiterator.php 106 | * @return Traversable An instance of an object implementing Iterator or 107 | * Traversable 108 | * @since 5.0.0 109 | */ 110 | public function getIterator() 111 | { 112 | return $this->childNodes->getIterator(); 113 | } 114 | 115 | /** 116 | * @return string 117 | */ 118 | public function __toString() : string 119 | { 120 | return (string)implode("\n", (array)$this->childNodes->getIterator()); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/Parser.php: -------------------------------------------------------------------------------- 1 | 35 | */ 36 | class Parser extends Grammar 37 | { 38 | /** 39 | * Holds parsed filename 40 | * @var string 41 | */ 42 | protected $filename; 43 | /** 44 | * Holds parsed string 45 | * @var string 46 | */ 47 | protected $content; 48 | 49 | /** 50 | * Parser constructor. 51 | */ 52 | public function __construct() 53 | { 54 | parent::__construct('syntax', [ 55 | 'syntax' => new GreedyStarParser(new LazyAltParser(['directive', 'section'])), 56 | 'sections' => new GreedyMultiParser('section', 0, 2), 57 | 'section' => new ConcParser( 58 | [ 59 | 'section-name', 60 | new LazyAltParser(['space', 'opt-space']), 61 | new LazyAltParser(['params', new LazyAltParser(['space', 'opt-space'])]), 62 | new StringParser('{'), 63 | new LazyAltParser(['space', 'opt-space']), 64 | new GreedyMultiParser(new LazyAltParser(['directive', 'section']), 0, null), 65 | new LazyAltParser(['space', 'opt-space']), 66 | new StringParser('}'), 67 | new LazyAltParser(['space', 'opt-space']), 68 | ], 69 | [$this, 'parseSection'] 70 | ), 71 | 'section-name' => new RegexParser('/^[a-z0-9\_]+/i'), 72 | 73 | 'directives' => new GreedyMultiParser('directive', 0, null), 74 | 'directive' => new LazyAltParser([ 75 | new ConcParser([ 76 | 'directive-name', 77 | 'semicolon', 78 | new LazyAltParser(['space', 'opt-space']), 79 | ], [$this, 'parseDirective']), 80 | new ConcParser([ 81 | 'directive-name', 82 | 'space', 83 | 'params', 84 | 'semicolon', 85 | new LazyAltParser(['space', 'opt-space']), 86 | ], [$this, 'parseDirective']) 87 | ]), 88 | 'directive-name' => new RegexParser('/^[a-z0-9\_]+/i'), 89 | 90 | 'params' => new GreedyMultiParser(new ConcParser(['param', 'opt-space'], function ($param, $space) { 91 | return $param; 92 | }), 0, null), 93 | 'param' => new LazyAltParser(['literal', 'param-name']), 94 | 'param-name' => new RegexParser('/^[^\s\r\n\{\}\;\"\']+/i', function ($match) { 95 | return new Param($match); 96 | }), 97 | 'literal' => new LazyAltParser([ 98 | new RegexParser('/^"([^"]*)"/', function ($match0, $match1) { 99 | return new Literal($match1); 100 | }), 101 | new RegexParser("/^'([^']*)'/", function ($match0, $match1) { 102 | return new Literal($match1); 103 | }) 104 | ]), 105 | 106 | 'semicolon' => new StringParser(';', function () { 107 | return null; 108 | }), 109 | 'space' => new GreedyStarParser('whitespace/comment', function () { 110 | return null; 111 | }), 112 | 'whitespace/comment' => new LazyAltParser(['whitespace', 'comment'], function () { 113 | return null; 114 | }), 115 | 'comment' => new RegexParser("/^#+([^\r\n]*)/", function () { 116 | return null; 117 | }), 118 | 'whitespace' => new RegexParser("/^[ \t\r\n]+/"), 119 | 'opt-space' => new RegexParser("/^[ \t\r\n]?/"), 120 | 'eol' => new LazyAltParser([new StringParser("\r"), new StringParser("\n")], function () { 121 | return null; 122 | }) 123 | ], function (array $nodes = []) { 124 | return new RootNode($nodes); 125 | }); 126 | } 127 | 128 | /** 129 | * Parses config file 130 | * @param string $filename 131 | * @return mixed 132 | * @throws ParseFailureException 133 | */ 134 | public function parseFile(string $filename) : RootNode 135 | { 136 | $this->content = null; 137 | $this->filename = $filename; 138 | 139 | return $this->parse(file_get_contents($filename)); 140 | } 141 | 142 | /** 143 | * Parses string 144 | * @param string $string 145 | * @return mixed 146 | * @throws ParseFailureException 147 | */ 148 | public function parse($string) : RootNode 149 | { 150 | $this->content = $string; 151 | $this->filename = null; 152 | 153 | return parent::parse($string); 154 | } 155 | 156 | /** 157 | * Parses section entries 158 | * @param string $section Section name 159 | * @param null $space0 Ignored 160 | * @param Param[] $params Params collection 161 | * @param null $open Ignored 162 | * @param null $space1 Ignored 163 | * @param Directive[] $directives Directives collection 164 | * @return Context 165 | * @throws GrammarException 166 | * @throws UnrecognizedContextException 167 | */ 168 | protected function parseSection($section, $space0, $params, $open, $space1, $directives) : Context 169 | { 170 | switch ($section) { 171 | case 'server': 172 | return new Server($directives); 173 | 174 | case 'http': 175 | return new Http($directives); 176 | 177 | case 'location': 178 | $modifier = null; 179 | if (sizeof($params) == 2) { 180 | list($modifier, $location) = $params; 181 | } elseif (sizeof($params) == 1) { 182 | $location = $params[0]; 183 | } else { 184 | throw new GrammarException( 185 | sprintf( 186 | "Location context missing in %s", 187 | $this->filename ? var_export($this->filename, true) : var_export($this->content, true) 188 | ) 189 | ); 190 | } 191 | return new Location($location, $modifier, $directives); 192 | 193 | case 'events': 194 | return new Events($directives); 195 | 196 | case 'upstream': 197 | list($upstream) = $params; 198 | return new Upstream($upstream, $directives); 199 | } 200 | 201 | throw new UnrecognizedContextException( 202 | sprintf( 203 | "Unrecognized context: {$section} found in %s", 204 | $this->filename ? var_export($this->filename, true) : var_export($this->content, true) 205 | ) 206 | ); 207 | } 208 | 209 | /** 210 | * Parses directive 211 | * @param string $name 212 | * @param null $space 213 | * @param array $params 214 | * @return Directive 215 | */ 216 | protected function parseDirective(string $name, $space = null, $params = []) : Directive 217 | { 218 | return new Directive($name, is_null($params) ? [] : $params); 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | NGINX Configurator 2 | ================== 3 | 4 | PHP Library for NGINX configuration parser/generator 5 | 6 | ![PHP 7.0](https://img.shields.io/badge/PHP-7.0-8C9CB6.svg?style=flat) 7 | [![Build Status](https://travis-ci.org/madkom/nginx-configurator.svg?branch=master)](https://travis-ci.org/madkom/nginx-configurator) 8 | [![Latest Stable Version](https://poser.pugx.org/madkom/nginx-configurator/v/stable)](https://packagist.org/packages/madkom/nginx-configurator) 9 | [![Total Downloads](https://poser.pugx.org/madkom/nginx-configurator/downloads)](https://packagist.org/packages/madkom/nginx-configurator) 10 | [![License](https://poser.pugx.org/madkom/nginx-configurator/license)](https://packagist.org/packages/madkom/nginx-configurator) 11 | [![Coverage Status](https://coveralls.io/repos/github/madkom/nginx-configurator/badge.svg?branch=master)](https://coveralls.io/github/madkom/nginx-configurator?branch=master) 12 | [![Code Climate](https://codeclimate.com/github/madkom/nginx-configurator/badges/gpa.svg)](https://codeclimate.com/github/madkom/nginx-configurator) 13 | [![Issue Count](https://codeclimate.com/github/madkom/nginx-configurator/badges/issue_count.svg)](https://codeclimate.com/github/madkom/nginx-configurator) 14 | 15 | --- 16 | 17 | ## Features 18 | 19 | This library can parse and generate NGINX configuration files. 20 | In near future will provide CLI commands for NGINX configuration. 21 | 22 | 23 | ## Installation 24 | 25 | Install with Composer 26 | 27 | ``` 28 | composer require madkom/nginx-configurator 29 | ``` 30 | 31 | ## Requirements 32 | 33 | This library requires *PHP* in `~7` version. 34 | 35 | ## Usage 36 | 37 | Parsing configuration string: 38 | 39 | ```php 40 | use Madkom\NginxConfigurator\Builder; 41 | use Madkom\NginxConfigurator\Config\Server; 42 | use Madkom\NginxConfigurator\Parser; 43 | 44 | require 'vendor/autoload.php'; 45 | 46 | $config = <<parse($config); 76 | /** @var Server $defaultServers[] */ 77 | $defaultServers = $defaultConfig->search(function (Node $node) { 78 | return $node instanceof Server; 79 | }); 80 | 81 | 82 | $builder = new Builder(); 83 | if (count($defaultServers) > 0) { 84 | /** @var Server $defaultServer */ 85 | foreach ($defaultServers as $defaultServer) { 86 | $builder->append($defaultServer); 87 | } 88 | } 89 | ``` 90 | 91 | Generating configuration string: 92 | 93 | ```php 94 | use Madkom\NginxConfigurator\Factory; 95 | use Madkom\NginxConfigurator\Builder; 96 | use Madkom\NginxConfigurator\Config\Location; 97 | use Madkom\NginxConfigurator\Node\Directive; 98 | use Madkom\NginxConfigurator\Node\Literal; 99 | use Madkom\NginxConfigurator\Node\Param; 100 | 101 | require __DIR__ . '/../vendor/autoload.php'; 102 | 103 | $factory = new Factory() 104 | $builder = new Builder(); 105 | 106 | $server = $builder->append($factory->createServer(80)); 107 | $server->append(new Directive('error_log', [ 108 | new Param('/var/log/nginx/error.log'), 109 | new Param('debug'), 110 | ])); 111 | $server->append(new Location(new Param('/test'), null, [ 112 | new Directive('error_page', [new Param('401'), new Param('@unauthorized')]), 113 | new Directive('set', [new Param('$auth_user'), new Literal('none')]), 114 | new Directive('auth_request', [new Param('/auth')]), 115 | new Directive('proxy_pass', [new Param('http://test-service')]), 116 | ])); 117 | $server->append(new Location(new Param('/auth'), null, [ 118 | new Directive('proxy_pass', [new Param('http://auth-service:9999')]), 119 | new Directive('proxy_bind', [new Param('$server_addr')]), 120 | new Directive('proxy_redirect', [new Param('http://$host'), new Param('https://$host')]), 121 | new Directive('proxy_set_header', [new Param('Content-Length'), new Literal("")]), 122 | new Directive('proxy_pass_request_body', [new Param('off')]), 123 | ])); 124 | $server->append(new Location(new Param('@unauthorized'), null, [ 125 | new Directive('return', [new Param('302'), new Param('/login?backurl=$request_uri')]), 126 | ])); 127 | $server->append(new Location(new Param('/login'), null, [ 128 | new Directive('expires', [new Param('-1')]), 129 | new Directive('proxy_pass', [new Param('http://identity-provider-service')]), 130 | new Directive('proxy_bind', [new Param('$server_addr')]), 131 | new Directive('proxy_redirect', [new Param('http://$host'), new Param('https://$host')]), 132 | new Directive('proxy_set_header', [new Param('Content-Length'), new Literal("")]), 133 | new Directive('proxy_pass_request_body', [new Param('off')]), 134 | ])); 135 | 136 | print($builder->dump()); 137 | ``` 138 | 139 | Generated configuration output: 140 | 141 | ``` 142 | server { 143 | listen 80; 144 | listen [::]:80 default ipv6only=on; 145 | error_log /var/log/nginx/error.log debug; 146 | location /test { 147 | error_page 401 @unauthorized; 148 | set $auth_user "none"; 149 | auth_request /auth; 150 | proxy_pass http://test-service; 151 | } 152 | 153 | location /auth { 154 | proxy_pass http://auth-service:9999; 155 | proxy_bind $server_addr; 156 | proxy_redirect http://$host https://$host; 157 | proxy_set_header Content-Length ""; 158 | proxy_pass_request_body off; 159 | } 160 | 161 | location @unauthorized { 162 | return 302 /login?backurl=$request_uri; 163 | } 164 | 165 | location /login { 166 | expires -1; 167 | proxy_pass http://identity-provider-service; 168 | proxy_bind $server_addr; 169 | proxy_redirect http://$host https://$host; 170 | proxy_set_header Content-Length ""; 171 | proxy_pass_request_body off; 172 | } 173 | 174 | } 175 | ``` 176 | 177 | There are also methods to read and dump file: 178 | 179 | ```php 180 | use Madkom\NginxConfigurator\Builder; 181 | use Madkom\NginxConfigurator\Config\Location; 182 | use Madkom\NginxConfigurator\Config\Server; 183 | use Madkom\NginxConfigurator\Node\Directive; 184 | use Madkom\NginxConfigurator\Node\Literal; 185 | use Madkom\NginxConfigurator\Parser; 186 | 187 | require __DIR__ . '/../vendor/autoload.php'; 188 | 189 | $parser = new Parser(); 190 | $builder = new Builder(); 191 | 192 | $configuration = $parser->parseFile('default.conf'); 193 | 194 | /** @var Server $servers[] */ 195 | $servers = $configuration->search(function (Node $node) { 196 | return $node instanceof Server; 197 | }); 198 | if (count($servers) > 0) { 199 | /** @var Server $server */ 200 | foreach ($servers as $server) { 201 | $builder->append($server); 202 | } 203 | } 204 | 205 | $builder->dumpFile('generated.conf'); 206 | ``` 207 | 208 | ## TODO 209 | 210 | * [ ] Implement comments parsing 211 | * [ ] Implement commands for config manipulation from CLI 212 | 213 | ## License 214 | 215 | The MIT License (MIT) 216 | 217 | Copyright (c) 2016 Madkom S.A. 218 | 219 | Permission is hereby granted, free of charge, to any person obtaining a copy 220 | of this software and associated documentation files (the "Software"), to deal 221 | in the Software without restriction, including without limitation the rights 222 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 223 | copies of the Software, and to permit persons to whom the Software is 224 | furnished to do so, subject to the following conditions: 225 | 226 | The above copyright notice and this permission notice shall be included in 227 | all copies or substantial portions of the Software. 228 | 229 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 230 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 231 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 232 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 233 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 234 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 235 | THE SOFTWARE. -------------------------------------------------------------------------------- /tests/spec/ParserSpec.php: -------------------------------------------------------------------------------- 1 | 26 | * @mixin Parser 27 | */ 28 | class ParserSpec extends ObjectBehavior 29 | { 30 | function it_is_initializable() 31 | { 32 | $this->shouldHaveType(Parser::class); 33 | } 34 | 35 | /** 36 | * @throws ParseFailureException 37 | */ 38 | function it_can_parse_directive() 39 | { 40 | /** @var RootNode $root */ 41 | $root = $this->parse(<<shouldReturnAnInstanceOf(RootNode::class); 46 | 47 | $directives = $root->search(function (Node $node) { 48 | return $node; 49 | })->getWrappedObject(); 50 | /** @var Directive $directive */ 51 | foreach ($directives as $directive) { 52 | break; 53 | } 54 | Assert::assertEquals($directive->getName(), 'internal'); 55 | Assert::assertInstanceOf(Traversable::class, $directive->getParams()); 56 | } 57 | 58 | function it_can_parse_multiple_directives_with_params() 59 | { 60 | $root = $this->parse(<<shouldReturnAnInstanceOf(RootNode::class); 69 | 70 | $directives = $root->search(function (Node $node) { 71 | return $node; 72 | })->getWrappedObject(); 73 | /** @var Directive $directive */ 74 | foreach ($directives as $index => $directive) { 75 | switch ($index) { 76 | case 0: 77 | Assert::assertEquals($directive->getName(), 'internal'); 78 | Assert::assertInstanceOf(Traversable::class, $directive->getParams()); 79 | break; 80 | case 1: 81 | Assert::assertEquals($directive->getName(), 'sendfile'); 82 | Assert::assertInstanceOf(Traversable::class, $directive->getParams()); 83 | break; 84 | case 2: 85 | Assert::assertEquals($directive->getName(), 'set'); 86 | Assert::assertInstanceOf(Traversable::class, $directive->getParams()); 87 | break; 88 | } 89 | } 90 | } 91 | 92 | function it_can_parse_multiple_directives_with_params_in_context() 93 | { 94 | $root = $this->parse(<<shouldReturnAnInstanceOf(RootNode::class); 105 | 106 | $contexts = $root->search(function (Node $node) { 107 | return $node; 108 | })->getWrappedObject(); 109 | /** @var Context $context */ 110 | foreach ($contexts as $index => $context) { 111 | switch ($index) { 112 | case 0: 113 | Assert::assertInstanceOf(Server::class, $context); 114 | /** @var Directive $directive */ 115 | foreach ($context as $index => $directive) { 116 | switch ($index) { 117 | case 0: 118 | Assert::assertEquals('internal', $directive->getName()); 119 | break; 120 | case 1: 121 | Assert::assertEquals('sendfile', $directive->getName()); 122 | break; 123 | case 2: 124 | Assert::assertEquals('set', $directive->getName()); 125 | } 126 | } 127 | break; 128 | } 129 | } 130 | } 131 | 132 | function it_can_parse_multiple_directives_with_params_in_multiple_contexts() 133 | { 134 | $root = $this->parse(<<shouldReturnAnInstanceOf(RootNode::class); 155 | 156 | $contexts = $root->search(function (Node $node) { 157 | return $node; 158 | })->getWrappedObject(); 159 | /** @var Context $context */ 160 | foreach ($contexts as $index => $context) { 161 | switch ($index) { 162 | case 0: 163 | Assert::assertInstanceOf(Server::class, $context); 164 | /** @var Directive $directive */ 165 | foreach ($context as $index => $directive) { 166 | switch ($index) { 167 | case 0: 168 | Assert::assertEquals('internal', $directive->getName()); 169 | break; 170 | case 1: 171 | Assert::assertEquals('sendfile', $directive->getName()); 172 | break; 173 | case 2: 174 | Assert::assertEquals('set', $directive->getName()); 175 | break; 176 | case 3: 177 | Assert::assertInstanceOf(Location::class, $directive); 178 | break; 179 | } 180 | } 181 | break; 182 | 183 | case 1: 184 | Assert::assertInstanceOf(Directive::class, $context); 185 | Assert::assertEquals('sendfile', $context->getName()); 186 | break; 187 | 188 | } 189 | } 190 | } 191 | 192 | function it_can_parse_Upstream_and_Http_contexts() 193 | { 194 | $root = $this->parse(<<shouldReturnAnInstanceOf(RootNode::class); 203 | 204 | $contexts = $root->search(function (Node $node) { 205 | return $node; 206 | })->getWrappedObject(); 207 | /** @var Context $context */ 208 | foreach ($contexts as $index => $context) { 209 | switch ($index) { 210 | case 0: 211 | Assert::assertInstanceOf(Http::class, $context); 212 | break; 213 | case 1: 214 | Assert::assertInstanceOf(Upstream::class, $context); 215 | /** @var Directive $directive */ 216 | foreach ($context as $index => $directive) { 217 | switch ($index) { 218 | case 0: 219 | Assert::assertEquals('internal', $directive->getName()); 220 | break; 221 | } 222 | } 223 | break; 224 | } 225 | } 226 | } 227 | 228 | /** 229 | * @throws ParseFailureException 230 | */ 231 | function it_can_parse_Literal_directive() 232 | { 233 | /** @var RootNode $root */ 234 | $root = $this->parse(<<shouldReturnAnInstanceOf(RootNode::class); 239 | 240 | $directives = $root->search(function (Node $node) { 241 | return $node; 242 | })->getWrappedObject(); 243 | /** @var Directive $directive */ 244 | foreach ($directives as $directive) { 245 | break; 246 | } 247 | Assert::assertEquals($directive->getName(), 'internal'); 248 | Assert::assertInstanceOf(Traversable::class, $directive->getParams()); 249 | /** @var Param $param */ 250 | foreach ($directive->getParams() as $param) { 251 | Assert::assertInstanceOf(Literal::class, $param); 252 | } 253 | } 254 | } 255 | --------------------------------------------------------------------------------