Initial commit

This commit is contained in:
Michał Brzuchalski
2016-06-10 13:19:45 +02:00
commit 09139d2b14
28 changed files with 1561 additions and 0 deletions

4
.coveralls.yml Normal file
View File

@@ -0,0 +1,4 @@
# for php-coveralls
src_dir: src
coverage_clover: tests/coverage/clover.xml
json_path: tests/coverage/coveralls-upload.json

21
.gitignore vendored Normal file
View File

@@ -0,0 +1,21 @@
/app/config/parameters.yml
/build/
/var/*
!/var/.gitkeep
/vendor/
/web/bundles/
/.sonar/
/.idea/
/bin/*
!/bin/console
!/bin/symfony_requirements
/tests/coverage/*
!tests/coverage/.gitkeep
/tests/pdepend/*
!tests/pdepend/.gitkeep
/docker/rebuild/build/*
!/docker/rebuild/build/.gitkeep
/docker/rebuild/partials/*
!/docker/rebuild/partials/.gitkeep
/docker/rebuild/resources/*
!/docker/rebuild/resources/.gitkeep

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Madkom S.A.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

233
README.md Normal file
View File

@@ -0,0 +1,233 @@
NGINX Configurator
==================
PHP Library for NGINX configuration parser/generator
![PHP 7.0](https://img.shields.io/badge/PHP-7.0-8C9CB6.svg?style=flat)
[![Build Status](https://travis-ci.org/madkom/nginx-configurator.svg?branch=master)](https://travis-ci.org/madkom/nginx-configurator)
[![Latest Stable Version](https://poser.pugx.org/madkom/nginx-configurator/v/stable)](https://packagist.org/packages/madkom/nginx-configurator)
[![Total Downloads](https://poser.pugx.org/madkom/nginx-configurator/downloads)](https://packagist.org/packages/madkom/nginx-configurator)
[![License](https://poser.pugx.org/madkom/nginx-configurator/license)](https://packagist.org/packages/madkom/nginx-configurator)
[![Coverage Status](https://coveralls.io/repos/github/madkom/nginx-configurator/badge.svg?branch=master)](https://coveralls.io/github/madkom/nginx-configurator?branch=master)
[![Code Climate](https://codeclimate.com/github/madkom/nginx-configurator/badges/gpa.svg)](https://codeclimate.com/github/madkom/nginx-configurator)
[![Issue Count](https://codeclimate.com/github/madkom/nginx-configurator/badges/issue_count.svg)](https://codeclimate.com/github/madkom/nginx-configurator)
---
## Features
This library can parse and generate NGINX configuration files.
## Installation
Install with Composer
```
composer require madkom/nginx-configurator
```
## Requirements
This library requires *PHP* in `~7` version.
## Usage
Parsing configuration string:
```php
<?php
use Madkom\NginxConfigurator\Builder;
use Madkom\NginxConfigurator\Config\Server;
use Madkom\NginxConfigurator\Parser;
require 'vendor/autoload.php';
$config = <<<CONFIG
server {
listen 8080;
root /data/www/web;
index index.php index.html index.htm;
location / {
try_files $uri $uri/ /index.php;
}
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/www;
}
# pass the PHP scripts to FastCGI server listening on the php-fpm socket
location ~ \.php$ {
try_files $uri =404;
fastcgi_pass unix:/var/run/php5-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
CONFIG;
$parser = new Parser();
$defaultConfig = $parser->parse($config);
/** @var Server $defaultServers[] */
$defaultServers = $defaultConfig->search(function (Node $node) {
return $node instanceof Server;
});
$builder = new Builder();
if (count($defaultServers) > 0) {
/** @var Server $defaultServer */
foreach ($defaultServers as $defaultServer) {
$builder->appendServerNode($defaultServer);
}
}
```
Generating configuration string:
```php
<?php
use Madkom\NginxConfigurator\Builder;
use Madkom\NginxConfigurator\Config\Location;
use Madkom\NginxConfigurator\Node\Directive;
use Madkom\NginxConfigurator\Node\Literal;
use Madkom\NginxConfigurator\Node\Param;
require __DIR__ . '/../vendor/autoload.php';
$builder = new Builder();
$server = $builder->addServerNode(80);
$server->append(new Directive('error_log', [new Param('/var/log/nginx/error.log'), new Param('debug')]));
$server->append(new Location(new Param('/test'), null, [
new Directive('error_page', [new Param('401'), new Param('@unauthorized')]),
new Directive('set', [new Param('$auth_user'), new Literal('none')]),
new Directive('auth_request', [new Param('/auth')]),
new Directive('proxy_pass', [new Param('http://test-service')]),
]));
$server->append(new Location(new Param('/auth'), null, [
new Directive('proxy_pass', [new Param('http://auth-service:9999')]),
new Directive('proxy_bind', [new Param('$server_addr')]),
new Directive('proxy_redirect', [new Param('http://$host'), new Param('https://$host')]),
new Directive('proxy_set_header', [new Param('Content-Length'), new Literal("")]),
new Directive('proxy_pass_request_body', [new Param('off')]),
]));
$server->append(new Location(new Param('@unauthorized'), null, [
new Directive('return', [new Param('302'), new Param('/login?backurl=$request_uri')]),
]));
$server->append(new Location(new Param('/login'), null, [
new Directive('expires', [new Param('-1')]),
new Directive('proxy_pass', [new Param('http://identity-provider-service')]),
new Directive('proxy_bind', [new Param('$server_addr')]),
new Directive('proxy_redirect', [new Param('http://$host'), new Param('https://$host')]),
new Directive('proxy_set_header', [new Param('Content-Length'), new Literal("")]),
new Directive('proxy_pass_request_body', [new Param('off')]),
]));
print($builder->dump());
```
Generated configuration output:
```
server {
listen 80;
listen [::]:80 default ipv6only=on;
error_log /var/log/nginx/error.log debug;
location /test {
error_page 401 @unauthorized;
set $auth_user "none";
auth_request /auth;
proxy_pass http://test-service;
}
location /auth {
proxy_pass http://auth-service:9999;
proxy_bind $server_addr;
proxy_redirect http://$host https://$host;
proxy_set_header Content-Length "";
proxy_pass_request_body off;
}
location @unauthorized {
return 302 /login?backurl=$request_uri;
}
location /login {
expires -1;
proxy_pass http://identity-provider-service;
proxy_bind $server_addr;
proxy_redirect http://$host https://$host;
proxy_set_header Content-Length "";
proxy_pass_request_body off;
}
}
```
There are also methods to read and dump file:
```php
use Madkom\NginxConfigurator\Builder;
use Madkom\NginxConfigurator\Config\Location;
use Madkom\NginxConfigurator\Config\Server;
use Madkom\NginxConfigurator\Node\Directive;
use Madkom\NginxConfigurator\Node\Literal;
use Madkom\NginxConfigurator\Parser;
require __DIR__ . '/../vendor/autoload.php';
$parser = new Parser();
$builder = new Builder();
$configuration = $parser->parseFile('default.conf');
/** @var Server $servers[] */
$servers = $configuration->search(function (Node $node) {
return $node instanceof Server;
});
if (count($servers) > 0) {
/** @var Server $server */
foreach ($servers as $server) {
$builder->appendServerNode($server);
}
}
$builder->dumpFile('generated.conf');
```
## TODO
* [ ] Implement comments parsing
## License
The MIT License (MIT)
Copyright (c) 2016 Madkom S.A.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

35
composer.json Normal file
View File

@@ -0,0 +1,35 @@
{
"name": "madkom/nginx-configurator",
"license": "MIT",
"homepage": "http://madkom.pl/",
"minimum-stability": "dev",
"require": {
"madkom/collection": "^1.0",
"ferno/loco": "@dev",
"madkom/uri": "^1.0"
},
"require-dev": {
"phpspec/phpspec": "^2.5",
"phpunit/phpunit": "~4"
},
"repositories": [
{
"type": "vcs",
"url": "git@github.com:madkom/loco.git"
}
],
"autoload": {
"psr-4": {
"Madkom\\NginxConfigurator\\": "src/"
}
},
"authors": [
{
"name": "Michał Brzuchalski",
"email": "m.brzuchalski@madkom.pl"
}
],
"config": {
"bin-dir": "bin/"
}
}

40
examples/build.php Normal file
View File

@@ -0,0 +1,40 @@
<?php
use Madkom\NginxConfigurator\Builder;
use Madkom\NginxConfigurator\Config\Location;
use Madkom\NginxConfigurator\Node\Directive;
use Madkom\NginxConfigurator\Node\Literal;
use Madkom\NginxConfigurator\Node\Param;
require __DIR__ . '/../vendor/autoload.php';
$builder = new Builder();
$server = $builder->addServerNode(80);
$server->append(new Directive('error_log', [new Param('/var/log/nginx/error.log'), new Param('debug')]));
$server->append(new Location(new Param('/test'), null, [
new Directive('error_page', [new Param('401'), new Param('@unauthorized')]),
new Directive('set', [new Param('$auth_user'), new Literal('none')]),
new Directive('auth_request', [new Param('/auth')]),
new Directive('proxy_pass', [new Param('http://test-service')]),
]));
$server->append(new Location(new Param('/auth'), null, [
new Directive('proxy_pass', [new Param('http://auth-service:9999')]),
new Directive('proxy_bind', [new Param('$server_addr')]),
new Directive('proxy_redirect', [new Param('http://$host'), new Param('https://$host')]),
new Directive('proxy_set_header', [new Param('Content-Length'), new Literal("")]),
new Directive('proxy_pass_request_body', [new Param('off')]),
]));
$server->append(new Location(new Param('@unauthorized'), null, [
new Directive('return', [new Param('302'), new Param('/login?backurl=$request_uri')]),
]));
$server->append(new Location(new Param('/login'), null, [
new Directive('expires', [new Param('-1')]),
new Directive('proxy_pass', [new Param('http://identity-provider-service')]),
new Directive('proxy_bind', [new Param('$server_addr')]),
new Directive('proxy_redirect', [new Param('http://$host'), new Param('https://$host')]),
new Directive('proxy_set_header', [new Param('Content-Length'), new Literal("")]),
new Directive('proxy_pass_request_body', [new Param('off')]),
]));
print($builder->dump());

25
examples/default.conf Normal file
View File

@@ -0,0 +1,25 @@
server {
listen 8080;
root /data/www/web;
index index.php index.html index.htm;
location / {
try_files $uri $uri/ /index.php;
}
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/www;
}
# pass the PHP scripts to FastCGI server listening on the php-fpm socket
location ~ \.php$ {
try_files $uri =404;
fastcgi_pass unix:/var/run/php5-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}

54
examples/parse.php Normal file
View File

@@ -0,0 +1,54 @@
<?php
use Madkom\NginxConfigurator\Builder;
use Madkom\NginxConfigurator\Config\Server;
use Madkom\NginxConfigurator\Node\Node;
use Madkom\NginxConfigurator\Parser;
require __DIR__ . '/../vendor/autoload.php';
$config = <<<CONFIG
server {
listen 8080;
root /data/www/web;
index index.php index.html index.htm;
location / {
try_files \$uri \$uri/ /index.php;
}
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/www;
}
# pass the PHP scripts to FastCGI server listening on the php-fpm socket
location ~ \.php$ {
try_files \$uri =404;
fastcgi_pass unix:/var/run/php5-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;
include fastcgi_params;
}
}
CONFIG;
$parser = new Parser();
$defaultConfig = $parser->parse($config);
/** @var Server $defaultServers[] */
$defaultServers = $defaultConfig->search(function (Node $node) {
return $node instanceof Server;
});
$builder = new Builder();
if (count($defaultServers) > 0) {
/** @var Server $defaultServer */
foreach ($defaultServers as $defaultServer) {
$builder->appendServerNode($defaultServer);
}
}

11
phpcs.xml Normal file
View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<ruleset name="Selective Standard">
<description>Project Coding Standard</description>
<file>./src</file>
<rule ref="Generic.Files.LineLength">
<properties>
<property phpcs-only="true" name="lineLimit" value="130"/>
<property phpcbf-only="true" name="lineLimit" value="150"/>
</properties>
</rule>
</ruleset>

5
phpspec.yml Normal file
View File

@@ -0,0 +1,5 @@
suites:
types:
namespace: Madkom\NginxConfigurator
psr4_prefix: Madkom\NginxConfigurator
spec_path: tests

27
phpunit.xml Normal file
View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
backupGlobals="false"
colors="true"
>
<testsuites>
<testsuite name="Application Test Suite">
<directory suffix="Test.php">./tests/phpunit/</directory>
<exclude>./vendor</exclude>
<exclude>./app</exclude>
</testsuite>
</testsuites>
<filter>
<blacklist>
<directory suffix=".php">./vendor</directory>
<directory suffix=".php">./app</directory>
<directory suffix=".php">./bin</directory>
<directory suffix=".php">./tests</directory>
<file>./var/bootstrap.php.cache</file>
</blacklist>
</filter>
<php>
<server name="KERNEL_DIR" value="./app/" />
</php>
</phpunit>

93
src/Builder.php Normal file
View File

@@ -0,0 +1,93 @@
<?php
/**
* Created by PhpStorm.
* User: mbrzuchalski
* Date: 18.04.16
* Time: 11:06
*/
namespace Madkom\NginxConfigurator;
use Madkom\NginxConfigurator\Config\Server;
use Madkom\NginxConfigurator\Config\Upstream;
use Madkom\NginxConfigurator\Node\Directive;
use Madkom\NginxConfigurator\Node\Param;
use Madkom\NginxConfigurator\Node\RootNode;
/**
* Class Builder
* @package Madkom\NginxConfigurator
* @author Michał Brzuchalski <m.brzuchalski@madkom.pl>
*/
class Builder
{
/**
* @var RootNode Holds configuration root node
*/
protected $rootNode;
/**
* Builder constructor.
*/
public function __construct()
{
$this->clear();
}
public function clear()
{
$this->rootNode = new RootNode();
}
/**
* @param int $port
* @return Server
*/
public function addServerNode(int $port) : Server
{
$listenIPv4 = new Directive('listen', [new Param($port)]);
$listenIPv6 = new Directive('listen', [new Param("[::]:{$port}"), new Param('default'), new Param('ipv6only=on')]);
$httpNode = new Server([$listenIPv4, $listenIPv6]);
$this->rootNode->append($httpNode);
return $httpNode;
}
/**
* @param Server $server
* @return Server
*/
public function appendServerNode(Server $server) : Server
{
$this->rootNode->append($server);
return $server;
}
/**
* @param Upstream $upstream
* @return Upstream
*/
public function appendUpstreamNode(Upstream $upstream) : Upstream
{
$this->rootNode->append($upstream);
return $upstream;
}
/**
* @return string
*/
public function dump() : string
{
return (string)$this->rootNode;
}
/**
* @param string $filename
* @return bool
*/
public function dumpFile(string $filename) : bool
{
return file_put_contents($filename, $this->dump());
}
}

28
src/Config/Events.php Normal file
View File

@@ -0,0 +1,28 @@
<?php
/**
* Created by PhpStorm.
* User: mbrzuchalski
* Date: 08.04.16
* Time: 20:55
*/
namespace Madkom\NginxConfigurator\Config;
use Madkom\NginxConfigurator\Node\Context;
use Madkom\NginxConfigurator\Node\Directive;
/**
* Class Events
* @package Madkom\NginxConfigurator\Config
* @author Michał Brzuchalski <m.brzuchalski@madkom.pl>
*/
class Events extends Context
{
/**
* Events constructor.
* @param Directive[] $directives
*/
public function __construct(array $directives = [])
{
parent::__construct('events', $directives);
}
}

28
src/Config/Http.php Normal file
View File

@@ -0,0 +1,28 @@
<?php
/**
* Created by PhpStorm.
* User: mbrzuchalski
* Date: 08.04.16
* Time: 20:57
*/
namespace Madkom\NginxConfigurator\Config;
use Madkom\NginxConfigurator\Node\Context;
use Madkom\NginxConfigurator\Node\Directive;
/**
* Class Http
* @package Madkom\NginxConfigurator\Config
* @author Michał Brzuchalski <m.brzuchalski@madkom.pl>
*/
class Http extends Context
{
/**
* Http constructor.
* @param Directive[] $directives
*/
public function __construct(array $directives = [])
{
parent::__construct('http', $directives);
}
}

71
src/Config/Location.php Normal file
View File

@@ -0,0 +1,71 @@
<?php
/**
* Created by PhpStorm.
* User: mbrzuchalski
* Date: 08.04.16
* Time: 21:01
*/
namespace Madkom\NginxConfigurator\Config;
use Madkom\NginxConfigurator\Node\Context;
use Madkom\NginxConfigurator\Node\Literal;
use Madkom\NginxConfigurator\Node\Param;
/**
* Class Location
* @package Madkom\NginxConfigurator\Config
* @author Michał Brzuchalski <m.brzuchalski@madkom.pl>
*/
class Location extends Context
{
/**
* Holds location match
* @var Param
*/
private $location;
/**
* Holds location match modifier
* @var Param
*/
private $match;
/**
* Location constructor.
* @param Param $location
* @param Param $match
* @param array $directives
*/
public function __construct(Param $location, Param $match = null, array $directives = [])
{
$this->location = $location;
$this->match = $match;
parent::__construct('location', $directives);
}
public function __toString() : string
{
return sprintf(
"{$this->name} %s %s {\n\t%s\n}\n",
$this->match,
$this->location,
implode("\n\t", (array)$this->childNodes->getIterator())
);
}
/**
* @return Param
*/
public function getLocation() : string
{
return (string)$this->location;
}
/**
* @return Param
*/
public function getMatch() : string
{
return (string)$this->match;
}
}

27
src/Config/Server.php Normal file
View File

@@ -0,0 +1,27 @@
<?php
/**
* Created by PhpStorm.
* User: mbrzuchalski
* Date: 08.04.16
* Time: 21:00
*/
namespace Madkom\NginxConfigurator\Config;
use Madkom\NginxConfigurator\Node\Context;
/**
* Class Server
* @package Madkom\NginxConfigurator\Config
* @author Michał Brzuchalski <m.brzuchalski@madkom.pl>
*/
class Server extends Context
{
/**
* Server constructor.
* @param array $directives
*/
public function __construct(array $directives = [])
{
parent::__construct('server', $directives);
}
}

51
src/Config/Upstream.php Normal file
View File

@@ -0,0 +1,51 @@
<?php
/**
* Created by PhpStorm.
* User: mbrzuchalski
* Date: 08.04.16
* Time: 21:07
*/
namespace Madkom\NginxConfigurator\Config;
use Madkom\NginxConfigurator\Node\Context;
use Madkom\NginxConfigurator\Node\Directive;
use Madkom\NginxConfigurator\Node\Param;
/**
* Class Upstream
* @package Madkom\NginxConfigurator\Config
* @author Michał Brzuchalski <m.brzuchalski@madkom.pl>
*/
class Upstream extends Context
{
/**
* Holds upstream name
* @var Param
*/
private $upstream;
/**
* Upstream constructor.
* @param Param $upstream
* @param Directive[] $directives
*/
public function __construct(Param $upstream, array $directives = [])
{
$this->upstream = $upstream;
parent::__construct('upstream', $directives);
}
public function getName() : string
{
return (string)$this->upstream->getValue();
}
public function __toString() : string
{
return sprintf(
"{$this->name} %s {\n\t%s\n}\n",
$this->upstream,
implode("\n\t", (array)$this->childNodes->getIterator())
);
}
}

View File

@@ -0,0 +1,20 @@
<?php
/**
* Created by PhpStorm.
* User: mbrzuchalski
* Date: 10.04.16
* Time: 09:12
*/
namespace Madkom\NginxConfigurator\Exception;
use Exception;
/**
* Class GrammarException
* @package Madkom\NginxConfigurator\Exception
* @author Michał Brzuchalski <m.brzuchalski@madkom.pl>
*/
class GrammarException extends Exception
{
}

View File

@@ -0,0 +1,20 @@
<?php
/**
* Created by PhpStorm.
* User: mbrzuchalski
* Date: 13.04.16
* Time: 09:22
*/
namespace Madkom\NginxConfigurator\Exception;
use Exception;
/**
* Class UnrecognizedContextException
* @package Madkom\NginxConfigurator\Exception
* @author Michał Brzuchalski <m.brzuchalski@madkom.pl>
*/
class UnrecognizedContextException extends Exception
{
}

42
src/Node/Context.php Normal file
View File

@@ -0,0 +1,42 @@
<?php
/**
* Created by PhpStorm.
* User: mbrzuchalski
* Date: 08.04.16
* Time: 20:53
*/
namespace Madkom\NginxConfigurator\Node;
/**
* Class Context
* @package Madkom\NginxConfigurator\Node
* @author Michał Brzuchalski <m.brzuchalski@madkom.pl>
*/
abstract class Context extends Node
{
/**
* Context constructor.
* @param string $name
* @param Directive[] $directives
*/
public function __construct($name, array $directives = [])
{
parent::__construct($name);
foreach ($directives as $directive) {
$this->append($directive);
}
}
public function __toString() : string
{
$childStrings = [];
foreach ($this->childNodes as $childNode) {
$childStrings[] = implode("\n\t", explode("\n", (string)$childNode));
}
return sprintf(
"{$this->name} {\n\t%s\n}\n",
implode("\n\t", $childStrings)
);
}
}

77
src/Node/Directive.php Normal file
View File

@@ -0,0 +1,77 @@
<?php
/**
* Created by PhpStorm.
* User: mbrzuchalski
* Date: 06.04.16
* Time: 13:40
*/
namespace Madkom\NginxConfigurator\Node;
use Madkom\Collection\CustomTypedCollection;
use Traversable;
/**
* Class Directive
* @package Madkom\NginxConfigurator
* @author Michał Brzuchalski <m.brzuchalski@madkom.pl>
*/
class Directive extends Node
{
/**
* Holds directive name
* @var string
*/
protected $name;
/**
* Holds param collection
* @var CustomTypedCollection|Param[]
*/
protected $params;
/**
* Directive constructor.
* @param string $name
* @param array $params
*/
public function __construct(string $name, array $params = [])
{
parent::__construct($name);
$this->params = new class($params) extends CustomTypedCollection {
/**
* Retrieves collection type
* @return string
*/
protected function getType() : string
{
return Param::class;
}
};
}
/**
* Retrieve directive name
* @return string
*/
public function getName() : string
{
return $this->name;
}
/**
* Retrieve params iterator
* @return Traversable|Param[]
*/
public function getParams() : Traversable
{
return $this->params->getIterator();
}
/**
* @return string
*/
public function __toString() : string
{
return sprintf("{$this->name} %s;", implode(' ', (array)$this->params->getIterator()));
}
}

24
src/Node/Literal.php Normal file
View File

@@ -0,0 +1,24 @@
<?php
/**
* Created by PhpStorm.
* User: mbrzuchalski
* Date: 08.04.16
* Time: 10:32
*/
namespace Madkom\NginxConfigurator\Node;
/**
* Class Literal
* @package Madkom\NginxConfigurator
* @author Michał Brzuchalski <m.brzuchalski@madkom.pl>
*/
class Literal extends Param
{
/**
* @return string
*/
public function __toString() : string
{
return '"' . addslashes($this->value) . '"';
}
}

122
src/Node/Node.php Normal file
View File

@@ -0,0 +1,122 @@
<?php
/**
* Created by PhpStorm.
* User: mbrzuchalski
* Date: 06.04.16
* Time: 13:23
*/
namespace Madkom\NginxConfigurator\Node;
use Countable;
use IteratorAggregate;
use Madkom\Collection\CustomTypedCollection;
use Traversable;
/**
* Class Node
* @package Madkom\NginxConfigurator
* @author Michał Brzuchalski <m.brzuchalski@madkom.pl>
*/
abstract class Node implements Countable, IteratorAggregate
{
/**
* Holds parent node
* @var Node
*/
protected $parent;
/**
* Holds node name
* @var string
*/
protected $name = '';
/**
* Holds node children
* @var CustomTypedCollection
*/
protected $childNodes;
/**
* Node constructor.
* @param string $name
*/
public function __construct(string $name)
{
$this->name = $name;
$this->childNodes = new class extends CustomTypedCollection {
/**
* Retrieves collection type
* @return string
*/
protected function getType() : string
{
return Node::class;
}
};
}
/**
* Append new child node
* @param Node $node
* @return bool
*/
public function append(Node $node) : bool
{
$node->parent = $this;
return $this->childNodes->add($node);
}
/**
* Remove child node
* @param Node $node
* @return bool
*/
public function remove(Node $node) : bool
{
return $this->childNodes->remove($node);
}
/**
* Search for specified nodes
* @param callable $checker
* @return CustomTypedCollection
*/
public function search(callable $checker) : CustomTypedCollection
{
return $this->childNodes->filter($checker);
}
/**
* Count elements of an object
* @link http://php.net/manual/en/countable.count.php
* @return int The custom count as an integer.
* </p>
* <p>
* The return protocol is cast to an integer.
* @since 5.1.0
*/
public function count()
{
return count($this->childNodes);
}
/**
* Retrieve an external iterator
* @link http://php.net/manual/en/iteratoraggregate.getiterator.php
* @return Traversable An instance of an object implementing <b>Iterator</b> or
* <b>Traversable</b>
* @since 5.0.0
*/
public function getIterator()
{
return $this->childNodes->getIterator();
}
/**
* @return string
*/
public function __toString() : string
{
return (string)implode("\n", (array)$this->childNodes->getIterator());
}
}

47
src/Node/Param.php Normal file
View File

@@ -0,0 +1,47 @@
<?php
/**
* Created by PhpStorm.
* User: mbrzuchalski
* Date: 08.04.16
* Time: 10:31
*/
namespace Madkom\NginxConfigurator\Node;
/**
* Class Param
* @package Madkom\NginxConfigurator
* @author Michał Brzuchalski <m.brzuchalski@madkom.pl>
*/
class Param
{
/**
* @var string
*/
protected $value;
/**
* Param constructor.
* @param string $value
*/
public function __construct(string $value)
{
$this->value = $value;
}
/**
* Retrieve param value
* @return string
*/
public function getValue() : string
{
return $this->value;
}
/**
* @return string
*/
public function __toString() : string
{
return $this->value;
}
}

28
src/Node/RootNode.php Normal file
View File

@@ -0,0 +1,28 @@
<?php
/**
* Created by PhpStorm.
* User: mbrzuchalski
* Date: 08.04.16
* Time: 21:18
*/
namespace Madkom\NginxConfigurator\Node;
/**
* Class RootNode
* @package Madkom\NginxConfigurator\node
* @author Michał Brzuchalski <m.brzuchalski@madkom.pl>
*/
class RootNode extends Node
{
/**
* RootNode constructor.
* @param Node[] $nodes
*/
public function __construct(array $nodes = [])
{
parent::__construct('');
foreach ($nodes as $node) {
$this->append($node);
}
}
}

220
src/Parser.php Normal file
View File

@@ -0,0 +1,220 @@
<?php
/**
* Created by PhpStorm.
* User: mbrzuchalski
* Date: 06.04.16
* Time: 13:01
*/
namespace Madkom\NginxConfigurator;
use Ferno\Loco\ConcParser;
use Ferno\Loco\Grammar;
use Ferno\Loco\GreedyMultiParser;
use Ferno\Loco\GreedyStarParser;
use Ferno\Loco\LazyAltParser;
use Ferno\Loco\ParseFailureException;
use Ferno\Loco\RegexParser;
use Ferno\Loco\StringParser;
use Madkom\NginxConfigurator\Config\Events;
use Madkom\NginxConfigurator\Config\Http;
use Madkom\NginxConfigurator\Config\Location;
use Madkom\NginxConfigurator\Config\Server;
use Madkom\NginxConfigurator\Config\Upstream;
use Madkom\NginxConfigurator\Exception\GrammarException;
use Madkom\NginxConfigurator\Exception\UnrecognizedContextException;
use Madkom\NginxConfigurator\Node\Context;
use Madkom\NginxConfigurator\Node\Directive;
use Madkom\NginxConfigurator\Node\Literal;
use Madkom\NginxConfigurator\Node\Param;
use Madkom\NginxConfigurator\Node\RootNode;
/**
* Class Parser
* @package Madkom\NginxConfigurator
* @author Michał Brzuchalski <m.brzuchalski@madkom.pl>
*/
class Parser extends Grammar
{
/**
* Holds parsed filename
* @var string
*/
protected $filename;
/**
* Holds parsed string
* @var string
*/
protected $content;
/**
* Parser constructor.
*/
public function __construct()
{
parent::__construct('syntax', [
'syntax' => new GreedyStarParser(new LazyAltParser(['directive', 'section'])),
'sections' => new GreedyMultiParser('section', 0, 2),
'section' => new ConcParser(
[
'section-name',
new LazyAltParser(['space', 'opt-space']),
new LazyAltParser(['params', new LazyAltParser(['space', 'opt-space'])]),
new StringParser('{'),
new LazyAltParser(['space', 'opt-space']),
new GreedyMultiParser(new LazyAltParser(['directive', 'section']), 0, null),
new LazyAltParser(['space', 'opt-space']),
new StringParser('}'),
new LazyAltParser(['space', 'opt-space']),
],
[$this, 'parseSection']
),
'section-name' => new RegexParser('/^[a-z0-9\_]+/i'),
'directives' => new GreedyMultiParser('directive', 0, null),
'directive' => new LazyAltParser([
new ConcParser([
'directive-name',
'semicolon',
new LazyAltParser(['space', 'opt-space']),
], [$this, 'parseDirective']),
new ConcParser([
'directive-name',
'space',
'params',
'semicolon',
new LazyAltParser(['space', 'opt-space']),
], [$this, 'parseDirective'])
]),
'directive-name' => new RegexParser('/^[a-z0-9\_]+/i'),
'params' => new GreedyMultiParser(new ConcParser(['param', 'opt-space'], function ($param, $space) {
return $param;
}), 0, null),
'param' => new LazyAltParser(['literal', 'param-name']),
'param-name' => new RegexParser('/^[^\s\r\n\{\}\;\"\']+/i', function ($match) {
return new Param($match);
}),
'literal' => new LazyAltParser([
new RegexParser('/^"([^"]*)"/', function ($match0, $match1) {
return new Literal($match1);
}),
new RegexParser("/^'([^']*)'/", function ($match0, $match1) {
return new Literal($match1);
})
]),
'semicolon' => new StringParser(';', function () {
return null;
}),
'space' => new GreedyStarParser('whitespace/comment', function () {
return null;
}),
'whitespace/comment' => new LazyAltParser(['whitespace', 'comment'], function () {
return null;
}),
'comment' => new RegexParser("/^#+([^\r\n]*)/", function () {
return null;
}),
'whitespace' => new RegexParser("/^[ \t\r\n]+/"),
'opt-space' => new RegexParser("/^[ \t\r\n]?/"),
'eol' => new LazyAltParser([new StringParser("\r"), new StringParser("\n")], function () {
return null;
})
], function (array $nodes = []) {
return new RootNode($nodes);
});
}
/**
* Parses config file
* @param string $filename
* @return mixed
* @throws ParseFailureException
*/
public function parseFile(string $filename) : RootNode
{
$this->content = null;
$this->filename = $filename;
return $this->parse(file_get_contents($filename));
}
/**
* Parses string
* @param string $string
* @return mixed
* @throws ParseFailureException
*/
public function parse($string) : RootNode
{
$this->content = $string;
$this->filename = null;
return parent::parse($string);
}
/**
* Parses section entries
* @param string $section Section name
* @param null $space0 Ignored
* @param Param[] $params Params collection
* @param null $open Ignored
* @param null $space1 Ignored
* @param Directive[] $directives Directives collection
* @return Context
* @throws GrammarException
* @throws UnrecognizedContextException
*/
protected function parseSection($section, $space0 = null, $params, $open = null, $space1 = null, $directives) : Context
{
switch ($section) {
case 'server':
return new Server($directives);
case 'http':
return new Http($directives);
case 'location':
$modifier = null;
if (sizeof($params) == 2) {
list($modifier, $location) = $params;
} elseif (sizeof($params) == 1) {
$location = $params[0];
} else {
throw new GrammarException(
sprintf(
"Location context missing in %s",
$this->filename ? var_export($this->filename, true) : var_export($this->content, true)
)
);
}
return new Location($location, $modifier, $directives);
case 'events':
return new Events($directives);
case 'upstream':
list($upstream) = $params;
return new Upstream($upstream, $directives);
}
throw new UnrecognizedContextException(
sprintf(
"Unrecognized context: {$section} found in %s",
$this->filename ? var_export($this->filename, true) : var_export($this->content, true)
)
);
}
/**
* Parses directive
* @param string $name
* @param null $space
* @param array $params
* @return Directive
*/
protected function parseDirective(string $name, $space = null, $params = []) : Directive
{
return new Directive($name, is_null($params) ? [] : $params);
}
}

0
tests/coverage/.gitkeep Normal file
View File

187
tests/spec/ParserSpec.php Normal file
View File

@@ -0,0 +1,187 @@
<?php
namespace spec\Madkom\NginxConfigurator;
use Ferno\Loco\ParseFailureException;
use Madkom\NginxConfigurator\Config\Location;
use Madkom\NginxConfigurator\Config\Server;
use Madkom\NginxConfigurator\Node\Context;
use Madkom\NginxConfigurator\Node\Directive;
use Madkom\NginxConfigurator\Node\Node;
use Madkom\NginxConfigurator\Node\RootNode;
use Madkom\NginxConfigurator\Parser;
use PhpSpec\ObjectBehavior;
use PHPUnit_Framework_Assert as Assert;
use Prophecy\Argument;
use Traversable;
/**
* Class ParserSpec
* @package spec\Madkom\NginxConfigurator
* @author Michał Brzuchalski <m.brzuchalski@madkom.pl>
* @mixin Parser
*/
class ParserSpec extends ObjectBehavior
{
function it_is_initializable()
{
$this->shouldHaveType('Madkom\NginxConfigurator\Parser');
}
/**
* @throws ParseFailureException
*/
function it_can_parse_directive()
{
/** @var RootNode $root */
$root = $this->parse(<<<EOF
internal;
EOF
);
$root->shouldReturnAnInstanceOf(RootNode::class);
$directives = $root->search(function (Node $node) {
return $node;
})->getWrappedObject();
/** @var Directive $directive */
foreach ($directives as $directive) {
break;
}
Assert::assertEquals($directive->getName(), 'internal');
Assert::assertInstanceOf(Traversable::class, $directive->getParams());
}
function it_can_parse_multiple_directives_with_params()
{
$root = $this->parse(<<<EOF
internal;
## comment with two hashes
sendfile off; # comment after directive
#comment beetween directives
set \$true 1;
EOF
);
$root->shouldReturnAnInstanceOf(RootNode::class);
$directives = $root->search(function (Node $node) {
return $node;
})->getWrappedObject();
/** @var Directive $directive */
foreach ($directives as $index => $directive) {
switch ($index) {
case 0:
Assert::assertEquals($directive->getName(), 'internal');
Assert::assertInstanceOf(Traversable::class, $directive->getParams());
break;
case 1:
Assert::assertEquals($directive->getName(), 'sendfile');
Assert::assertInstanceOf(Traversable::class, $directive->getParams());
break;
case 2:
Assert::assertEquals($directive->getName(), 'set');
Assert::assertInstanceOf(Traversable::class, $directive->getParams());
break;
}
}
}
function it_can_parse_multiple_directives_with_params_in_context()
{
$root = $this->parse(<<<EOF
server {
internal;
## comment with two hashes
sendfile off; # comment after directive
#comment beetween directives
set \$true 1;
}
EOF
);
$root->shouldReturnAnInstanceOf(RootNode::class);
$contexts = $root->search(function (Node $node) {
return $node;
})->getWrappedObject();
/** @var Context $context */
foreach ($contexts as $index => $context) {
switch ($index) {
case 0:
Assert::assertInstanceOf(Server::class, $context);
/** @var Directive $directive */
foreach ($context as $index => $directive) {
switch ($index) {
case 0:
Assert::assertEquals('internal', $directive->getName());
break;
case 1:
Assert::assertEquals('sendfile', $directive->getName());
break;
case 2:
Assert::assertEquals('set', $directive->getName());
}
}
break;
}
}
}
function it_can_parse_multiple_directives_with_params_in_multiple_contexts()
{
$root = $this->parse(<<<EOF
server {
internal;
## comment with two hashes
sendfile off; # comment after directive
#comment beetween directives
set \$true 1;
location ~ /app {
set \$true 0;
}
}
sendfile off;
events {
sendfile off;
}
EOF
);
$root->shouldReturnAnInstanceOf(RootNode::class);
$contexts = $root->search(function (Node $node) {
return $node;
})->getWrappedObject();
/** @var Context $context */
foreach ($contexts as $index => $context) {
switch ($index) {
case 0:
Assert::assertInstanceOf(Server::class, $context);
/** @var Directive $directive */
foreach ($context as $index => $directive) {
switch ($index) {
case 0:
Assert::assertEquals('internal', $directive->getName());
break;
case 1:
Assert::assertEquals('sendfile', $directive->getName());
break;
case 2:
Assert::assertEquals('set', $directive->getName());
break;
case 3:
Assert::assertInstanceOf(Location::class, $directive);
break;
}
}
break;
case 1:
Assert::assertInstanceOf(Directive::class, $context);
Assert::assertEquals('sendfile', $context->getName());
break;
}
}
}
}