2017 © Pedro Peláez
 

library app

A Nano-Framework for PHP

image

hiraeth/app

A Nano-Framework for PHP

  • Sunday, July 2, 2017
  • by mattsah
  • Repository
  • 1 Watchers
  • 7 Stars
  • 15 Installations
  • CSS
  • 0 Dependents
  • 0 Suggesters
  • 0 Forks
  • 0 Open issues
  • 12 Versions
  • 67 % Grown

The README.md

Hiraeth

Hiraeth (pronounced [hiraɪ̯θ]) is a Welsh word for which there is no direct English translation. The online Welsh-English dictionary of the University of Wales, Lampeter likens it to homesickness tinged with grief or sadness over the lost or departed. -- Wikipedia, (*1)

A Nano-Framework for PHP

Hiraeth is the product of 10+ years of PHP framework development. Over those 10+ years, the PHP ecosystem has grown and developed to include some truly great libraries and components. At the beginning of that journey, developers who were concerned with ease of use, stability, or flexibility encountered many barriers in the course of application or website development., (*2)

These barriers no longer exist. Hiraeth is designed to let you use and re-use the packages you love without locking you in., (*3)

Getting Started

Installing:, (*4)

composer create-project hiraeth/app hiraeth

Running the Development Server:, (*5)

php bin/server

You can now visit Hiraeth at http://localhost:8080. If you need to adjust any of the server settings, check out the config/app.jin file and look for:, (*6)

[server]
php = php
host = localhost
port = 8080
docroot = public

Hello World!

Let's create a new file in public/hello.php. Begin by placing the following code at the top:, (*7)

<?php

for (
    $root_path  = __DIR__;
    $root_path != '/' && !is_file($root_path . DIRECTORY_SEPARATOR . 'composer.json');
    $root_path  = realpath($root_path . DIRECTORY_SEPARATOR . '..')
);

These first 7 lines simply track backwards until the composer.json file is discovered. This is the "root" of our Hiraeth installation which will be contained in the $root_path variable. From there, we include the composer autoloader and instantiate our application:, (*8)

$loader  = require $root_path . '/vendor/autoload.php';
$hiraeth = new Hiraeth\Application($root_path, $loader);

Lastly, we execute the application by providing our post-boot logic:, (*9)

exit($hiraeth->run(function() {
    echo 'Hello World!';
}));

Save the file and visit http://localhost:8080/hello.php to see your message. Note: The post-boot logic must be in the form of a Closure because it will be bound to the scope of our Hiraeth\Application., (*10)

Configuration

Configuration files in Hiraeth are Jsonified Ini Notation or "JIN" files. Put simply, the structure of these files is exactly like an INI file, but the values are fully qualified JSON. Let's create a test file at config/text.jin., (*11)

subject = "World!"

[hello]
translations = [
    "Hello",
    "Hola",
    "Bonjour",
    "Ciao"
]

Getting Data

Returning to our public/hello.php file, we can use our config to make things a bit more interesting:, (*12)

exit($hiraeth->run(function() {
    $subject      = $this->config->get('test', 'subject', 'World!');
    $translations = $this->config->get('test', 'hello.translations', array());

    foreach ($translations as $translation) {
        echo $translation . ' ' . $subject . '<br />';
    }
}));

The get() method on a Hiraeth\Configuration takes 3 arguments:, (*13)

  1. The file path (minus the .jin extension) relative to the configuration root.
  2. The data path. Each path element is separated by a . in the path string.
  3. A default value in the event that the requisite configuration value is not set.

It is also possible to get data across multiple configuration files by replacing the file path with a *:, (*14)

exit($hiraeth->run(function() {
    $subject      = $this->config->get('test', 'subject', 'World!');
    $translations = $this->config->get('*', 'hello.translations', array());

    foreach ($translations as $path => $values) {
        foreach ($values as $translation) {
            echo $translation . ' ' . $subject . '<br />';
        }
    }
}));

Note: That when retrieving values across multiple configurations, the values are not merged. Getting from mulitple configurations will always return an array whose keys are the configuration path and whose values are the requested data., (*15)

Application

In addition to the run() method which bootstraps the system and then executes your post-boot logic, the Hireath\Application provides a number of methods for working with the application environment. For example, you can retrieve environment settings:, (*16)

exit($hiraeth->run(function() {
    echo 'The DEBUG environment setting is set to ' . $this->getEnvironment('DEBUG', 0);
}));

The getEnvironment() method is a simple way to get an environment setting based on name, and if it's not set, to enable the return of a default. There's no special magic, here. It's just a wrapper around getenv(). To simplify environment configuration, however, Hiraeth does support the use of a .env file., (*17)

It is also possible to easily check for files/directories relative to the document root and get their full path:, (*18)

exit($hiraeth->run(function() {
    if ($this->hasFile('README.md')) {
        echo file_get_contents($this->getFile('README.md'));
    }
}));

The above would echo the contents of this file (assuming it's not deleted). It is also possible to create an empty file and the necessary directory structure, by providing TRUE as the second argument to getFile(). If the file does not exist, Hiraeth will attempt to make it., (*19)

exit($hiraeth->run(function() {
    $cache_path = 'writable/cache/nonexisting/path/mytool.cache';

    if ($this->hasFile($file)) {
        $data = require $this->getFile($cache_path);
    } else {
        $cache = $this->getFile($cache_path, TRUE);

        //
        // Do work to generated complex $data
        //

        file_put_contents($cache, $data);
    }

    //
    // Use $data
    //
}));

Similar to hasFile() and getFile() methods are the hasDirectory() and getDirectory() methods. The only difference being that getDirectory() does not require any arguments and if no relative path is supplied it will return the full path of the application directory:, (*20)

exit($hiraeth->run(function() {
    echo 'Hiraeth is installed to ' . $this->getDirectory();
}));

Dependency Injection

Dependency injection is an important feature in Hiraeth. Although it is possible to configure dependency injection directly (Hiraeth\Broker is an alias for the auryn dependency injector), Hiraeth is designed to encapsulate dependency configuration. There are two forms of encapsulation depending on the complexity in setting up the dependency:, (*21)

  • Hiraeth\Delegate
  • Hiraeth\Provider

Simple Dependencies

For a simple dependency that requires no additional configuration or setup during instantiation, it is possible to inject this without any encapsulation:, (*22)

exit($hiraeth->run(function(Parsedown $parsedown) {
    $parsedown->text(file_get_contents($this->getFile('README.md')));
}));

Delegates

Delegates are basically factories for the dependency injector which provide some meta-information about what class is constructed, what interfaces it can serve, and ultimately, an instance to be injected., (*23)

Let's create a simple delegate for monolog as an example. The Hiraeth\Delegate interface requires the implementation of 3 methods:, (*24)

getClass()

The getClass() method is responsible for returning the class for which the delegate operates. That is, the class which the delegate is responsible for building. In our example, this is quite simple:, (*25)

static public function getClass()
{
    return 'Monolog\Logger';
}
getInterfaces()

If the dependency the delegate is responsible for constructing should be used in an instance where an interface is typehinted, then we can return an array of those interfaces. In our case, if the broker encounters any dependency for the Psr\Log\LoggerInterface interface, we will want it to use this logger., (*26)

static public function getInterfaces()
{
    return [
        'Psr\Log\LoggerInterface'
    ];
}

Note that both getClass() and getInterfaces() are static methods -- this is so that our delegate can be configured without instantiating any of the additional dependencies it may need to construct the object., (*27)

__invoke(Hiraeth\Broker $broker)

Last, but not least is the __invoke() method which takes a single argument which is the broker itself and is responsible for building the class:, (*28)

public function __invoke(Hiraeth\Broker $broker)
{
    $app      = $broker->make('Hiraeth\Application');
    $config   = $broker->make('Hiraeth\Config');
    $handlers = $config->get('monolog', 'handlers', array());
    $logger   = new Monolog\Logger('app');

    if (isset($handlers['file'])) {
        $logger->pushHandler(new Monolog\Handler\RotatingFileHandler(
            $app->getFile($handlers['file'])
        ));
    }

    $broker->share($logger);

    return $logger;
}

In the example above, we use the $broker to additional make our share $app an $config instance. However, an alternative approach to this is to implement a __construct() method which will also be dependency injected. This has benefits both with regards to testing and also with regards making our dependencies clearer for code helpers and documentation generator:, (*29)

public function __construct(Hiraeth\Application $app, Hiraeth\Configuration $config)
{
    $this->app    = $app;
    $this->config = $config;
}

Now, instead of using the $broker, we can rewrite our __invoke() to use the contructor injected dependencies., (*30)

public function __invoke(Hiraeth\Broker $broker)
{
    $logger   = new Monolog\Logger('app');
    $handlers = $this->config->get('monolog', 'handlers', array());

    if (isset($handlers['file'])) {
        $logger->pushHandler(new Monolog\Handler\RotatingFileHandler(
            $this->app->getFile($handlers['file'])
        ));
    }

    $broker->share($logger);

    return $logger;
}

The final result can be seen in the hiraeth/monolog package in the LoggerDelegate class., (*31)

Registering Delegates

In order to register a delegate for use by the system, you will need to add it to a delegates list in an [application] section of a configuration. You can create a separate config file so that it can simply be copied to the configuration directory, for example monolog.jin or you could add it directly to the app.jin list of delegates:, (*32)

[application]
delegates = [
    "Hiraeth\\Monolog\\LoggerDelegate"
]

In this case, we'll add it to a separate monolog.jin file since we also use that file to configure the handlers in our __invoke method:, (*33)

$handlers = $this->config->get('monolog', 'handlers', array());

Right now our delegate only supports a single handler (namely a file handler), so let's make sure we add that information as well:, (*34)

[handlers]
file = writable/logs/app.log

Using a Delegate

Once our delegate is created and registered with the system, anywhere we have dependency injection in use, we can get our logger:, (*35)

exit($hiraeth->run(function(Psr\Log\LoggerInterface $logger) {
    $logger->error('You forgot to write an application');
}));

Providers

Providers are similar to delegates in that:, (*36)

  1. They are distinct classes which are designed to configure or add dependencies to instantiated dependencies.
  2. They can request additional dependencies through the __construct() method.
  3. They define a number of interfaces, for which they provide configuration or dependency information.

However, where delegates are responsible for constructing a concrete class, a provider is only responsible for configuration or setter-style dependency injection interfaces. So, imagine if you will that you have a number of classes which all have optional caching ability. In order to enable this caching ability, you create an interface:, (*37)

interface Cacheable
{
    public function setCache(Cache $cache);
}

Rather than create delegates for every class which might use caching (especially if there's no other need for a delegate), it is possible to simply create a provider:, (*38)

namespace Hiraeth\Relay;

use Hiraeth;

/**
 *
 */
class CacheProvider implements Hiraeth\Provider
{
    /**
     * The cache instance to provide
     *
     * @access protected
     * @var Cache A cache instance
     */
    protected $cache = NULL;

    /**
     * Get the interfaces for which the provider operates.
     *
     * @access public
     * @return array A list of interfaces for which the provider operates
     */
    static public function getInterfaces()
    {
        return [
            'Cacheable'
        ];
    }


    /**
     * Construct the provider with requisite dependencies.
     *
     * @access public
     * @param Cache $cache A cache instance for the provider to provide
     * @return void
     */
    public function __construct(Cache $cache)
    {
        $this->cache = $cache;
    }


    /**
     * Prepare the instance.
     *
     * @access public
     * @return Object The prepared instance
     */
    public function __invoke($cacheable_instance, Hiraeth\Broker $broker)
    {
        $cacheable_instance->setCache($this->cache);

        return $cacheable_instance;
    }
}

While providers are extremely useful especially for the injection of commonly used objects, it is important to remember that delegates can also prepare instances in this way before returning them. Creating an interface and a provider in order to extend a concrete implementation may not be the best option, (*39)

The Versions

02/07 2017

dev-master

9999999-dev

A Nano-Framework for PHP

  Sources   Download

MIT

The Requires

 

by Matthew J. Sahagian

02/07 2017

1.1.5

1.1.5.0

A Nano-Framework for PHP

  Sources   Download

MIT

The Requires

 

by Matthew J. Sahagian

02/07 2017

1.1.4

1.1.4.0

A Nano-Framework for PHP

  Sources   Download

MIT

The Requires

 

by Matthew J. Sahagian

12/06 2017

1.1.3

1.1.3.0

A Nano-Framework for PHP

  Sources   Download

MIT

The Requires

 

by Matthew J. Sahagian

12/06 2017

1.1.2

1.1.2.0

A Nano-Framework for PHP

  Sources   Download

MIT

The Requires

 

by Matthew J. Sahagian

12/06 2017

1.1.1

1.1.1.0

A Nano-Framework for PHP

  Sources   Download

MIT

The Requires

 

by Matthew J. Sahagian

10/06 2017

1.1.0

1.1.0.0

A Nano-Framework for PHP

  Sources   Download

MIT

The Requires

 

by Matthew J. Sahagian

04/06 2017

1.0.4

1.0.4.0

A Nano-Framework for PHP

  Sources   Download

MIT

The Requires

 

by Matthew J. Sahagian

04/06 2017

1.0.3

1.0.3.0

A Nano-Framework for PHP

  Sources   Download

MIT

The Requires

 

by Matthew J. Sahagian

30/05 2017

1.0.2

1.0.2.0

A Nano-Framework for PHP

  Sources   Download

MIT

The Requires

 

by Matthew J. Sahagian

30/05 2017

1.0.1

1.0.1.0

A Nano-Framework for PHP

  Sources   Download

MIT

The Requires

 

by Matthew J. Sahagian

28/05 2017

1.0.0

1.0.0.0

A Nano-Framework for PHP

  Sources   Download

MIT

The Requires

 

by Matthew J. Sahagian