/micro

Micro PHP library

Primary LanguagePHPMIT LicenseMIT

Micro (Yet another PHP library)

...but no shit

Build Status Scrutinizer Code Quality Latest Stable Version GitHub release GitHub license

Description

Micro provides minimalistic core features to write a new Application. Instead providing a rich featured fatty library it only provides a couple of namespaces. It comes with a logger (and multiple adapters), configuration parser, HTTP routing/response, Authentication (and multiple adapters) and some wrapper around databases and ldap.

  • \Micro\Auth
  • \Micro\Config
  • \Micro\Http
  • \Micro\Log

Requirements

The library is only >= PHP7.1 compatible.

Download

The package is available at packagist: https://packagist.org/packages/gyselroth/micro

To install the package via composer execute:

composer require gyselroth/micro

Configuration (\Micro\Config)

Read

Simply read an xml configuration and initialize your configuration:

$config = new \Micro\Config(new \Micro\Config\Xml($path));
var_dump($config->myconfigentry);
string(1) "1"

And your actual XML configuration would look like:

<config version="1.0">
  <production>
    <myconfigentry>1</myconfigentry>
  </production>
</config>

Every configuration got configuration environment, if you only have one, just stick with as your first node within . See environments for further information.

Merge

Merge multiple configuration files:

$config1 = new \Micro\Config\Xml($path);
$config2 = new \Micro\Config\Xml($path);
$config1->merge($config2);
$config = new \Micro\Config($config1);

Environments

You can request a custom configuration environment:

$config = new \Micro\Config(new \Micro\Config\Xml($path, 'development'));
var_dump($config->myconfigentry);
string(1) "2"

While your XML configuration would look like:

<config version="1.0">
  <production>
    <myconfigentry>1</myconfigentry>
  </production>
  <development>
    <myconfigentry>2</myconfigentry>
  </development>
</config>

Inheritance

The configuration parser supports inheritance. A simple example would be to just inherit one environment from another:

<config version="1.0">
  <production>
    <a>a</a>
  </production>
  <development inherits="production">
  </development>
</config>
$config = new \Micro\Config(new \Micro\Config\Xml($path, 'development'));
var_dump($config->a);
string(1) "a"

You can also inherit single elements (recursively) within one environment and overwrite elements which were inherit in the first place:

<config version="1.0">
  <production>
    <a>a</a>
    <b inherits="a"/>
    <c>
        <child>c</child>
    </c>
    <d inherits="c">
        <child2>d</child2>
    </d>
  </development>
</config>
$config = new \Micro\Config(new \Micro\Config\Xml($path));
var_dump($config->a);
string(1) "a"

var_dump($config->b);
string(1) "a"

var_dump($config->c->child);
string(1) "c"

var_dump($config->d->child);
var_dump($config->d->child2);
string(1) "c"
string(1) "d"

It possible as well to access any elements (node path is separated with a "."):

<config version="1.0">
  <production>
    <a>
        <child>
            <subchild>a</subchild>
        </child>
    </a>
    <b inherits="c">
        <child inherits="a.child">
        <child2 inherits="a.child.subchild">
    </b>
  </development>
</config>
$config = new \Micro\Config(new \Micro\Config\Xml($path));
var_dump($config->a->child->subchild);
string(1) "a"

var_dump($config->b->child->subchild);
var_dump($config->b->child2);
string(1) "a"
string(1) "a"

XML Attributes

There is no more a difference between XML attributes and XML nodes. \Micro\Config\Xml parses both equally. So you can decide or switch on the fly whether a name/value should be an attribute or a node.

<config version="1.0">
  <production>
    <a enabled="1"/>
  </development>
</config>

Meaning the above configuration gets parsed as the same Config object as the following:

<config version="1.0">
  <production>
    <a>
      <enabled>1</enabled>
    </a>
  </development>
</config>

Logger (\Micro\Log)

Description

\Micro\Log is a PSR-3 compatible logger with multiple log adapters.

Initialize

$logger = new Logger(Iterable $options);
$logger->info(string $message, array $context);

Configuration

$logger = new Logger([
  'adapter_name' => [
    'class'  => '\Micro\Log\Adapter\File',
    'config' => [
      'file'        => '/path/to/file',
      'date_format' => 'Y-d-m H:i:s', //http://php.net/manual/en/function.date.php
      'format'      => '{date} {level} {message} {context.category}',
      'level'       => 7 //PSR-3 log levels 1-7
    ],
  ],
  'adapter2_name' => []
]);

Of course you can initialize the logger with a configuration object as well (any any other iterable objects):

<log>
  <adapter_name enabled="1" class="\Micro\Log\Adapter\File">
    <config>
      <file>/path/to/file</file>
      <date_format>Y-d-m H:i:s</date_format>
      <format>{date} {level} {message} {context.category}</format>
      <level>7</level>
    </config
  </adapter_name>
</log>
$config = new \Micro\Config(new \Micro\Config\Xml($path));
$logger = new Logger($config);

Format

The message formate is configured in each adapter separately. Available variables are:

  • {message} - The message iteself
  • {date} - The current timestamp formatted with the configured date_format option
  • {level} - The log level, the configured number will be replaced with a string, for exampe 7 => debug
  • {context.} - You can acces each context option and include them in the message format. For example you have a context ['category' => 'router'] then you can configure {context.category} to include this context value within your message.

Log adapters

  • \Micro\Log\Adapter\File
  • \Micro\Log\Adapter\Blackhole
  • \Micro\Log\Adapter\Stdout
  • \Micro\Log\Adapter\Syslog

You can always create your own log adapter using \Micro\Log\Adapter\AdapterInterface.

HTTP (\Micro\Http)

Initialize router

The http router requires an array with http headers, usually this is $_SERVER and a PSR-3 compatible logger.

$router = new \Micro\Http\Router(array $server, \Psr\Log\LoggerInterface $logger)

Adding routes

$router = (new \Micro\Http\Router($_SERVER, $logger))
  ->clearRoutingTable()
  ->addRoute(new \Micro\Http\Router\Route('/api/v1/user', 'MyApp\Rest\v1\User'))
  ->addRoute(new \Micro\Http\Router\Route('/api/v1/user/{uid:#([0-9a-z]{24})#}', 'MyApp\Rest\v1\User'))
  ->addRoute(new \Micro\Http\Router\Route('/api/v1$', 'MyApp\Rest\v1\Rest'))
  ->addRoute(new \Micro\Http\Router\Route('/api/v1', 'MyApp\Rest\v1\Rest'))
  ->addRoute(new \Micro\Http\Router\Route('/api$', 'MyApp\Rest\v1\Rest'));
  ->run(array $controller_params);

The router tries to map a request to the first matching route in his routing table. The request gets mappend to a class and method. Optional parameters/query string gets automatically submitted to the final controller class.

Given the routing table above and the following final controller class:

namespace MyApp\Rest\v1;

class User
{
    /**
     * GET http://localhost/api/v1/user/540f1fc9a641e6eb708b4618/attributes
     * GET http://localhost/api/v1/user/attributes?uid=540f1fc9a641e6eb708b4618
     */
    public function getAttributes(string $uid=null): \Micro\Http\Response
    {

    }

    /**
     * GET http://localhost/api/v1/user/540f1fc9a641e6eb708b4618
     * GET http://localhost/api/v1/user?uid=540f1fc9a641e6eb708b4618
     */
    public function get(string $uid=null): \Micro\Http\Response
    {

    }

    /**
     * POST http://localhost/api/v1/user/540f1fc9a641e6eb708b4618/password / POST body password=1234
     * POST http://localhost/api/v1/user/password?uid=540f1fc9a641e6eb708b4618 / POST body password=1234
     * POST http://localhost/api/v1/user/password / POST body password=1234, uid=540f1fc9a641e6eb708b4618
     */
    public function postPassword(string $uid, string $password): \Micro\Http\Response
    {

    }

    /**
     * DELETE http://localhost/api/v1/user/540f1fc9a641e6eb708b4618/mail
     * DELETE http://localhost/api/v1/user/mail?uid=540f1fc9a641e6eb708b4618
     */
    public function deleteMail(string $uid=null): \Micro\Http\Response
    {

    }

    /**
     * DELETE http://localhost/api/v1/540f1fc9a641e6eb708b4618/mail
     * DELETE http://localhost/api/v1/user?uid=540f1fc9a641e6eb708b4618
     */
    public function delete(string $uid=null): \Micro\Http\Response
    {

    }

    /**
     * HEAD http://localhost/api/v1/user/540f1fc9a641e6eb708b4618
     * HEAD http://localhost/api/v1/user?uid=540f1fc9a641e6eb708b4618
     */
    public function headExists(string $uid=null): \Micro\Http\Response
    {

    }
}

Response

Each endpoint needs to return a Response object to the router.

/**
 * HEAD http://localhost/api/v1/user/540f1fc9a641e6eb708b4618
 * HEAD http://localhost/api/v1/user?uid=540f1fc9a641e6eb708b4618
 */
public function headExists(string $uid=null): \Micro\Http\Response
{
  if(true) {
    return (new \Micro\Http\Response())->setCode(200)->setBody('user does exists');
  } else {
    return (new \Micro\Http\Response())->setCode(404);  
  }
}