remove parser and add collection package into this library

This commit is contained in:
2022-10-27 19:57:52 +02:00
parent a22d611816
commit affab28c09
14 changed files with 434 additions and 390 deletions

View File

@@ -1,7 +1,7 @@
NGINX Configurator NGINX Configurator
================== ==================
PHP Library for NGINX configuration parser/generator PHP Library for NGINX configuration generator
![PHP 8.1](https://img.shields.io/badge/PHP-8.1-8C9CB6.svg?style=flat) ![PHP 8.1](https://img.shields.io/badge/PHP-8.1-8C9CB6.svg?style=flat)
@@ -27,60 +27,6 @@ This library requires *PHP* in `>=8.1` version.
## Usage ## Usage
Parsing configuration string:
```php
use Envoyr\NginxConfigurator\Builder;
use Envoyr\NginxConfigurator\Config\Server;
use Envoyr\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->append($defaultServer);
}
}
```
Generating configuration string: Generating configuration string:
```php ```php
@@ -167,37 +113,6 @@ server {
} }
``` ```
There are also methods to read and dump file:
```php
use Envoyr\NginxConfigurator\Builder;
use Envoyr\NginxConfigurator\Config\Location;
use Envoyr\NginxConfigurator\Config\Server;
use Envoyr\NginxConfigurator\Node\Directive;
use Envoyr\NginxConfigurator\Node\Literal;
use Envoyr\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->append($server);
}
}
$builder->dumpFile('generated.conf');
```
## TODO ## TODO
* [ ] Implement comments parsing * [ ] Implement comments parsing

View File

@@ -3,10 +3,7 @@
"license": "MIT", "license": "MIT",
"homepage": "https://envoyr.com/", "homepage": "https://envoyr.com/",
"minimum-stability": "dev", "minimum-stability": "dev",
"require": { "require": {},
"envoyr/collection": "dev-main",
"envoyr/loco": "dev-main"
},
"require-dev": {}, "require-dev": {},
"autoload": { "autoload": {
"psr-4": { "psr-4": {

View File

@@ -9,34 +9,22 @@ use Envoyr\NginxConfigurator\Node\Param;
require __DIR__ . '/../vendor/autoload.php'; require __DIR__ . '/../vendor/autoload.php';
$uuid = 'uuid';
$factory = new Factory(); $factory = new Factory();
$builder = new Builder(); $builder = new Builder();
$builder->append(new Directive('proxy_cache_path', [new Param("/data/nginx/cache/{$uuid}"), new Param("keys_zone={$uuid}:10m")]));
$server = $builder->append($factory->createServer(80)); $server = $builder->append($factory->createServer(80));
$server->append(new Directive('error_log', [new Param('/var/log/nginx/error.log'), new Param('debug')])); $server->append(new Directive('error_log', [new Param("/data/nginx/log/{$uuid}.log"), new Param('debug')]));
$server->append(new Location(new Param('/test'), null, [ $server->append(new Directive('proxy_cache', [new Param($uuid)]));
new Directive('error_page', [new Param('401'), new Param('@unauthorized')]), $server->append(new Location(new Param('/'), null, [
new Directive('set', [new Param('$auth_user'), new Literal('none')]), new Directive('error_page', [new Param('401')]),
new Directive('auth_request', [new Param('/auth')]), new Directive('host', [new Param('example.com')]),
new Directive('proxy_pass', [new Param('http://test-service')]), new Directive('proxy_pass', [new Param('https://example.com')]),
])); ]));
$server->append(new Location(new Param('/auth'), null, [ $server->append(new Location(new Param('/.well-known/acme-challenge'), new Param('^~'), [
new Directive('proxy_pass', [new Param('http://auth-service:9999')]), new Directive('proxy_pass', [new Param('https://le.cdn.gd')])
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()); print($builder->dump());

View File

@@ -1,54 +0,0 @@
<?php
use Envoyr\NginxConfigurator\Builder;
use Envoyr\NginxConfigurator\Config\Server;
use Envoyr\NginxConfigurator\Node\Node;
use Envoyr\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->append($defaultServer);
}
}

View File

@@ -8,7 +8,7 @@
namespace Envoyr\NginxConfigurator; namespace Envoyr\NginxConfigurator;
use Countable; use Countable;
use Envoyr\Collection\CustomTypedCollection; use Envoyr\NginxConfigurator\Collection\CustomTypedCollection;
use Envoyr\NginxConfigurator\Node\Node; use Envoyr\NginxConfigurator\Node\Node;
use Envoyr\NginxConfigurator\Node\RootNode; use Envoyr\NginxConfigurator\Node\RootNode;
use Traversable; use Traversable;

View File

@@ -0,0 +1,154 @@
<?php
namespace Envoyr\NginxConfigurator\Collection;
use ArrayIterator;
use Countable;
use IteratorAggregate;
use Serializable;
use Traversable;
/**
* Class Collection
* @package Envoyr\NginxConfigurator
* @author Michał Brzuchalski <m.brzuchalski@madkom.pl>
*/
class Collection implements Countable, IteratorAggregate, Serializable
{
/**
* @var array
*/
protected $elements = [];
/**
* Collection constructor.
* @param array $elements
*/
public function __construct(array $elements = [])
{
foreach ($elements as $element) {
$this->add($element);
}
}
/**
* Add the element to collection
* @param $element
* @return bool
*/
public function add($element) : bool
{
$this->elements[] = $element;
return $this->contains($element);
}
/**
* Removes element and checks if collection not contains it anymore
* @param $element
* @return bool
*/
public function remove($element) : bool
{
if (!$this->contains($element)) {
return false;
}
$index = array_search($element, $this->elements, true);
unset($this->elements[$index]);
return !$this->contains($element);
}
/**
* Return element existence in collection test result
* @param $element
* @return bool
*/
public function contains($element) : bool
{
return in_array($element, $this->elements, true);
}
/**
* Iterates through all elements to exam existence by callback
* @param callable $checker
* @return bool
*/
public function exists(callable $checker)
{
foreach ($this->elements as $element) {
if ($checker($element)) {
return true;
}
}
return false;
}
/**
* Iterates through all elements to exam existence by callback
* @param callable $checker
* @return bool
*/
public function filter(callable $checker)
{
$result = new static();
foreach ($this->elements as $element) {
if ($checker($element)) {
$result->add($element);
}
}
return $result;
}
/**
* String representation of object
* @link http://php.net/manual/en/serializable.serialize.php
* @return string the string representation of the object or null
* @since 5.1.0
*/
public function serialize()
{
return serialize($this->elements);
}
/**
* Constructs the object
* @link http://php.net/manual/en/serializable.unserialize.php
* @param string $serialized <p>
* The string representation of the object.
* </p>
* @return void
* @since 5.1.0
*/
public function unserialize($serialized)
{
$this->elements = unserialize($serialized);
}
/**
* 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->elements);
}
/**
* 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 new ArrayIterator($this->elements);
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace Envoyr\NginxConfigurator\Collection;
use RuntimeException;
use UnexpectedValueException;
/**
* Class ComparableTypedCollection
* @package Envoyr\NginxConfigurator
* @author Michał Brzuchalski <m.brzuchalski@madkom.pl>
*/
abstract class ComparableDistinctTypedCollection extends CustomTypedCollection
{
/**
* Checks two elements are equal
* @param $left
* @param $right
* @return bool
*/
abstract public function compareTo($left, $right) : bool;
/**
* @inheritDoc
*/
public function add($element) : bool
{
if ($this->contains($element)) {
throw new RuntimeException("Given element already exists in collection");
}
return parent::add($element);
}
/**
* @inheritDoc
*/
public function contains($element) : bool
{
if (!$this->isElementValid($element)) {
throw new UnexpectedValueException(
"Unexpected element type, expecting: {$this->getType()}, given: " . get_class($element)
);
}
foreach ($this->elements as $current) {
if ($this->compareTo($current, $element)) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace Envoyr\NginxConfigurator\Collection;
use RuntimeException;
use UnexpectedValueException;
/**
* Class CustomDistinctCollection
* @package Envoyr\NginxConfigurator
* @author Michał Brzuchalski <m.brzuchalski@madkom.pl>
*/
abstract class CustomDistinctCollection extends CustomTypedCollection
{
/**
* @return string
*/
abstract protected function getMethod() : string;
/**
* @inheritDoc
*/
public function add($element) : bool
{
if ($this->contains($element)) {
throw new RuntimeException("Given element already exists in collection");
}
return parent::add($element);
}
/**
* @inheritDoc
*/
public function contains($element) : bool
{
if (!$this->isElementValid($element)) {
throw new UnexpectedValueException(
"Unexpected element type, expecting: {$this->getType()}, given: " . get_class($element)
);
}
$distinct = $element->{$this->getMethod()}();
foreach ($this->elements as $current) {
if ($current->{$this->getMethod()}() == $distinct) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,69 @@
<?php
namespace Envoyr\NginxConfigurator\Collection;
use InvalidArgumentException;
use UnexpectedValueException;
/**
* Class AbstractTypedCollection
* @package Envoyr\NginxConfigurator
* @author Michał Brzuchalski <m.brzuchalski@madkom.pl>
*/
abstract class CustomTypedCollection extends Collection
{
/**
* Retrieves collection type
* @return string
*/
abstract protected function getType() : string;
/**
* AbstractTypedCollection constructor.
* @param array $elements
*/
public function __construct(array $elements = [])
{
if (!class_exists($this->getType())) {
throw new InvalidArgumentException("Expected type should be accessible class name, given: {$this->getType()}");
}
parent::__construct($elements);
}
/**
* @inheritDoc
*/
public function add($element) : bool
{
if (!$this->isElementValid($element)) {
throw new UnexpectedValueException(
"Unexpected element type, expecting: {$this->getType()}, given: " . get_class($element)
);
}
return parent::add($element);
}
/**
* @inheritDoc
*/
public function remove($element) : bool
{
if (!$this->isElementValid($element)) {
throw new UnexpectedValueException(
"Unexpected element type, expecting: {$this->getType()}, given: " . get_class($element)
);
}
return parent::remove($element);
}
/**
* Check is element valid object type
* @param $element
* @return bool
*/
protected function isElementValid($element) : bool
{
return is_a($element, $this->getType()) || is_subclass_of($element, $this->getType());
}
}

View File

@@ -0,0 +1,57 @@
<?php
namespace Envoyr\NginxConfigurator\Collection;
use InvalidArgumentException;
use RuntimeException;
use UnexpectedValueException;
/**
* Class DistinctCollection
* @package Envoyr\NginxConfigurator
* @author Michał Brzuchalski <m.brzuchalski@madkom.pl>
*/
class DistinctCollection extends CustomDistinctCollection
{
/**
* @var string Collection generic type
*/
protected $type;
/**
* @var string Generic type distinguish method name
*/
private $method;
/**
* UniqueGenericCollection constructor.
* @param string $type
* @param string $method
* @param array $elements
*/
public function __construct(string $type, string $method, array $elements = [])
{
if (!method_exists($type, $method) && is_callable($type, $method)) {
throw new InvalidArgumentException(
"Non-existent distinct method name in class {$type}, given: {$method}"
);
}
$this->type = $type;
$this->method = $method;
parent::__construct($elements);
}
/**
* @return string
*/
protected function getType() : string
{
return $this->type;
}
/**
* @inheritDoc
*/
protected function getMethod() : string
{
return $this->method;
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace Envoyr\NginxConfigurator\Collection;
/**
* Class DistinctCollection
* @package Envoyr\NginxConfigurator
* @author Michał Brzuchalski <m.brzuchalski@madkom.pl>
*/
class TypedCollection extends CustomTypedCollection
{
/**
* @var string Collection generic type
*/
protected $type;
/**
* GenericCollection constructor.
* @param string $type
* @param array $elements
*/
public function __construct(string $type, array $elements = [])
{
$this->type = $type;
parent::__construct($elements);
}
/**
* @return string
*/
protected function getType() : string
{
return $this->type;
}
}

View File

@@ -7,7 +7,7 @@
*/ */
namespace Envoyr\NginxConfigurator\Node; namespace Envoyr\NginxConfigurator\Node;
use Envoyr\Collection\CustomTypedCollection; use Envoyr\NginxConfigurator\Collection\CustomTypedCollection;
use Traversable; use Traversable;
/** /**

View File

@@ -9,7 +9,7 @@ namespace Envoyr\NginxConfigurator\Node;
use Countable; use Countable;
use IteratorAggregate; use IteratorAggregate;
use Envoyr\Collection\CustomTypedCollection; use Envoyr\NginxConfigurator\Collection\CustomTypedCollection;
use Traversable; use Traversable;
/** /**

View File

@@ -1,220 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: mbrzuchalski
* Date: 06.04.16
* Time: 13:01
*/
namespace Envoyr\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 Envoyr\NginxConfigurator\Config\Events;
use Envoyr\NginxConfigurator\Config\Http;
use Envoyr\NginxConfigurator\Config\Location;
use Envoyr\NginxConfigurator\Config\Server;
use Envoyr\NginxConfigurator\Config\Upstream;
use Envoyr\NginxConfigurator\Exception\GrammarException;
use Envoyr\NginxConfigurator\Exception\UnrecognizedContextException;
use Envoyr\NginxConfigurator\Node\Context;
use Envoyr\NginxConfigurator\Node\Directive;
use Envoyr\NginxConfigurator\Node\Literal;
use Envoyr\NginxConfigurator\Node\Param;
use Envoyr\NginxConfigurator\Node\RootNode;
/**
* Class Parser
* @package Envoyr\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, $params, $open, $space1, $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);
}
}