Learn Bone Framework

It's easy to get up and running with a new project


 

Installation

There are two ways to get started, if you already have a LAMP stack set up, then you can install using Composer. However, Bone Framework comes with a Docker LAMP stack out of the box, so it's even easier to get up and running.

...via Composer

First make sure you have Composer! Then install Bone.

composer create-project delboy1978uk/boneframework your/path/here
 

or if you haven't installed composer globally ...

php composer.phar create-project delboy1978uk/boneframework your/path/here

Docker Dev Box

To install Bone via Docker, instead use Git to clone the project

git clone https://github.com/delboy1978uk/boneframework yourProjectName

Bone comes with a docker-compose.yml in the project, so you can instantly get a dev server running if you use Docker (Tested using a default VirtualBox VM). Just add this to your hosts file
awesome.scot 192.168.99.100

docker-machine start
eval $(docker-machine env)
cd /path/to/project
docker-compose up

Then you can access the site at https://awesome.scot in your browser.

Config

You can drop in any number of .php files into the config/ folder. Make sure they return an array with the config. You can override configuration based on environment var APPLICATION_ENV, so for instance if the environment was production it would load the additional config within the production subdirectory. There are default configs for optional packages such as bone-mail which have been added for convenience.

The configurations you will probably want to take a look at first are the following:

  • site.php Site information such as domain name, site name, site logo, etc.
  • packages.php For adding packages to your application
  • bone-db.php PDO connection information, also used by the bone-doctrine package.
  • views.php For overriding vendor package view files
  • middleware.php For adding site wide global middleware
  • bone-firewall.php For disabling vendor routes or adding middleware

Packages

Bone Framework is truly modular, and all Package classes implement Barnacle\RegistrationInterface (Barnacle is a PSR-11 wrapper for Pimple DIC). There are various interfaces you can make the package implement in order to give your package more functionality.

  • AssetRegistrationInterface For vendor package asset folders
  • CommandRegistrationInterface To add bone CLI commands
  • EntityRegistrationInterface To set up bone-doctrine database entity folders
  • I18nRegistrationInterface To set up translation directories
  • MiddlewareRegistrationInterface To add middleware to to the container
  • RouterConfigInterface To set up routes to controller methods
  • ViewRegistrationInterface To set up view folders

Third party packages (or your own packagist packages) will reside in the vendor/ folder, rather than in src/. If these packages have front end assets such as images or CSS or JavaScript, you must deply those assets using the bone assets:deploy command.

Ready to rock Bone Framework packages

Several Packages are available already for installation via composer to Bone Framework

Routing

Bone's router is a fork of league/route.

Routes are defined in your package class by implementing Bone\Router\RouterConfigInterface

Variable parts of the route are surrounded in curly braces.

/**
 * @param Container $c
 * @param Router $router
 * @return Router
 */
public function addRoutes(Container $c, Router $router): Router
{
    $router->map('GET', '/learn', [IndexController::class, 'learn']);
    $router->map('GET', '/with/{customVariable}', [
        IndexController::class, 'somewhere'
    ]);
    $router->map('GET', '/with/{id:number}', [
        IndexController::class, 'somewhereElse'
    ]);
    $router->map('POST', '/with/{name:word}', [
        IndexController::class, 'somewhereElse'
    ]);

    return $router;
}

Variables can be fetched in your controller by calling

$request->getAttribute('yourVariable')

Middleware

Bone Framework can use PSR-15 middleware to create a layered application.

Middleware
 

As an example, bone-i18n uses middleware to take in the request, check for a URL starting with a supported locale (e.g. /en_GB/user/login), set the locale so that translations can take place in the correct language, and remove that part of the URL before passing the new request (e.g. with URL now set to /user/login) to the next middleware in the stack. The end of the middleware chain (Application in the above image) is your controller method defined in the packages router setup.

Middleware can be added on a global level by adding to config/middleware.php, or can be added to an individual route or a group of routes in the package route setup, like this:

/**
 * @param Container $c
 * @param Router $router
 * @return Router
 */
public function addRoutes(Container $c, Router $router): Router
{
    $awesomeMiddleware = new AwesomeMiddleware();
    $router->map('GET', '/learn', [IndexController::class, 'learn'])
           ->middlewares([$awesomeMiddleware]);

    return $router;
}

Controllers

Controllers can be as simple or as complex as you need. If a controller has dependencies, you should create a factory in the Packages addToContainer() method. See src/App/AppPackage for an example, a controller which extends Bone\Controller\Controller, a class providing a translator, view engine, and site config object.

Bone also provides some interfaces and convenience getter and setter traits:

  • I18nAwareInterface use HasTranslatorTrait
  • LoggerAwareInterface use HasLoggerTrait
  • SessionAwareInterface use HasSessionTrait
  • SiteConfigAwareInterface use HasSiteConfigTrait
  • ViewAwareInterface use HasViewTrait

All of these interfaces can be automatically configured in the dependency injection container by passing your controller into Bone\Controller\Init in your package class, like so:

/**
 * @param Container $c
 */
public function addToContainer(Container $c)
{
    $c[IndexController::class] = $c->factory(function (Container $c) {
        $controller = new IndexController();

        return Init::controller($controller, $c);
    });
}

Controller methods called by the router should look like a PSR-15 RequestHandlerInterface, except the method name doesn't obviously need to be called "handle".

/**
 * @param ServerRequestInterface $request
 * @param array $args
 * @return ResponseInterface
 */
public function indexAction(ServerRequestInterface $request) : ResponseInterface
{
    $body = $this->view->render('app::index');

    return new HtmlResponse($body);
}

Bone Framework uses Diactoros for its ServerRequest and Response objects.

Views

View scripts are based on PlatesPHP. Packages which have routes with view files should implement Bone\View\ViewRegistrationInterface

<?php

class SomePackage implements RegistrationInterface, ViewRegistrationInterface
{
    // other code here ...

    /**
     * @return array
     */
    public function addViews(): array
    {
        return [
            'app' => __DIR__ . '/View/index',
            'error' => __DIR__ . '/View/error',
            'layouts' => __DIR__ . '/View/layouts',
        ];
    }


    /**
     * @param Container $c
     * @return array
     */
    public function addViewExtensions(Container $c): array
    {
        $someExtension = new SomeViewExtension();

        return [$someExtension];
    }
}

To render a view in your controller, call the following code, passing in the variables of your choice as an array. You can echo the var in the view by using the key name, i.e. in the example the key 'name' is $name in the view file.

$body = $this->view->render('app::index', [
    'name' => $someNameVariable,
    'date' => date('d/m/Y'),
]);
                

There are also view extensions included, a paginator and a Bootstrap alert box. In your controller, you can set an array, the last item of the array being the alert class (such as alert-danger etc.)

$body = $this->view->render('app::index', [
    'message' => ['Successfully added record to DB', 'success'],
]);
                

In the view file, you can call the following:

<?= $this->alert($message) ?>
 

@todo Documentation for the paginator will be added once it has been refactored to work as a middleware component. Watch this space.

Internationalisation

Bone uses Laminas i18n to support translation into different locales.
Translation files (gettext .po and .mo) should be placed in data/translations, under a subdirectory of the locale, e.g. data/translations/en_GB/en_GB.po.
You can set the default locale and an array of supported locales in config/bone-i18n.php.

<?php

return [
    'i18n' => [
        'translations_dir' => 'data/translations',
        'type' => \Laminas\I18n\Translator\Loader\Gettext::class,
        'default_locale' => 'en_PI',
        'supported_locales' => ['en_PI', 'en_GB', 'nl_BE', 'fr_BE'],
    ]
                    ];

To add internationalisation to a package, make your Package class implement
Bone\I18n\I18nRegistrationInterface

<?php

class SomePackage implements RegistrationInterface, I18nRegistrationInterface
{
    // other code here ...

    /**
     * @return string
     */
    public function getTranslationsDirectory(): string
    {
        return 'path/to/translations';
    }
}

If you make your controller extend Bone\Controller\Controller , you can quickly inject the translator into your controller by passing it into Bone\Controller\Init in your package's addToContainer() method:

/**
 * @param Container $c
 */
public function addToContainer(Container $c)
{
    $c[IndexController::class] = $c->factory(function (Container $c) {
        $controller = new IndexController();

        // adds i18n, view engine, site config info,
        // session manager, and logger to your controller
        return Init::controller($controller, $c);
    });
}

You can perform a translation in your controller like so:

$translator = $this->getTranslator();
$translatedText = $translator->translate('welcome');

You can perform a translation in your view like so:

<?= $this->t('welcome') ?>

Finally, you can use the i18n view helper to prepend your routes in the desired language

<a href="<?= $this->l() ?>/some/link">Some Link</a>

For more information, see Laminas i18n.

We recommend PoEdit to manage your translations.
Configure it to look for t and translate in order for it to detect translation functions in your code base.

Logs

Bone uses monolog/monolog, and logs can be found in data/logs. Currently we only support writing to files, but you can add as many channels as you like:

<<?php

return [
    'log' => [
        'channels' => [
            'default' => 'data/logs/default_log',
        ],
    ],
];

To use the logger in a controller:

$this->getLog()->debug($message) // or error(), etc, see PSR-3

Command Line Interface

Bone Framework comes with a CLI command vendor/bin/bone, which is powered by the Symfony Console component. We recommend (for your convenience) to add the following to your ~/.bash_profile

export PATH=$PATH:bin:vendor/bin
 

This allows you to simply call bone without the vendor path.

The bone CLI command

To register your own commands to bone, simply make your package class implement
Bone\Console\CommandRegistrationInterface.

<?php

class SomePackage implements RegistrationInterface, CommandRegistrationInterface
{
    // other code here ...

    /**
     * @param Container $container
     * @return array
     */
    public function registerConsoleCommands(Container $container): array
    {
        // Create your command(s) first, then return in an array
        $someDependency = $c->get(SomeDependency::class);
        $swashbucklingCommand = new SwashbucklingCommand($someDependency);

        return [$swashbucklingCommand];
    }
}