brtiver blog

mainly about tips of web application tech in English from Japan.

Karen -- PSR-7 micro framework with PHP7

github.com

Karen is a simple PSR-7 micro framework with PHP7. This framework provide these names of blocks and simple Controller class:

  • container
  • middleware
  • routes
  • response
  • run

You have only to write a code in your way with PSR-7 objects.

Karen uses following components by default :

  • PSR-7 Request, Response
    • zendframework/zend-diactoros
  • middleware
    • relay/relay
  • container
    • pimple/pimple
  • router
    • aura/router
    • nikic/fast-route
  • twig/twig

Requirements

  • PHP 7.0 or later.

Install

php -r "eval('?>'.file_get_contents('https://getcomposer.org/installer'));"
php composer.phar create-project brtriver/karen ./your_app

Demo

cd your_app
make server

and open http://localhost:8888/hello/karen_girls in your browser. If try FastRoute version, open http://localhost:8888/karen2/hello/karen_girls

Usage

see web/index.php.

<?php
require __DIR__ . '/../vendor/autoload.php';

$app = new class extends Karen\Framework\Karen {
        public function action($map)
        {
            // hello name controller sample.
            $map->get('hello', '/hello/{name}', function($args, $controller) {
                $name = $args['name']?? 'karen';
                return $controller->render('[Karen] Hello, ' . $name);
            })->tokens(['name' => '.*']);

            // with twig
            $map->get('render_with_twig', '/template/{name}', function($args, $controller) {
                return $controller->renderWithT('demo.html', ['name' => $args['name']]);
            });

            return $map;
        }
    };

$app->run();
$app->sendResponse();

You have to write your logic of a controller with anonymous function:

$map->get('hello', '/hello/{name}', function($args, $controller) {
    $name = $args['name']?? 'karen';
    return $controller->render('Hello, ' . $name);
})->tokens(['name' => '.*']);

If you write your application class and write logic there instead of anonymous class, it is to be a simple one:

<?php
$app = new YourFramework(); // YourFramework class extends Application class and implement your logic.
$app->run();
$app->sendResponse();

$args is arguments from routing path, and $controller is a instance of Karen\Controller class.

Karen\Controller has a render method. this is equal to $controller->response->getBody()->write($output).

Extends Controller

For example, you want to render without a template engine:

$c['controller'] = new Controller();

And if you want to use a template engine like Twig, you have only to write with anonymous class and trait:

$c['controller'] = function($c) {
    $controller = new class extends Controller{
            use Templatable;
        };
    $controller->setTemplate($c['template']);

    return $controller;
};

Create your own framework

This microframework application is a simple template method pattern.

    public function run()
    {
        $this->container();
        $this->middleware();
        $this->route();
        $this->response();
    }

this abstract application knows about Request, Response, MiddlewareBulder(relay), so you have to write your application logic in methods(container, middleware, route and response methods) of your extends application class. these methods are executed by run() method.

method

container

create your container and set necessary object in your container:

    public function container()
    {
        $this->c = new Container();
        $this->c['template'] = function($c) {
            $loader = new \Twig_Loader_Filesystem( __DIR__ . '/../../templates');
            return new \Twig_Environment($loader, array(
                'cache' => '/tmp/',
            ));
        };
    }

Karen use pimple, but you can change it.

middleware (option)

Middleware method is to add your middleware through $this->addQueue() method as needed. Karen use (Relay)http://relayphp.com/, so you have to pass callabe following signature:

use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\RequestInterface as Request;

function (
    Request $request,   // the request
    Response $response, // the response
    callable $next      // the next middleware
) {
    // ...
}

This is a sample to change status by query parameter(status):

    public function middleware()
    {
        // if open http://localhost:8888/hello/ssss?status=404, response status is set to 404
        $this->addQueue('changeStatus', function (Request $request, Response $response, callable $next) {
            $response = $next($request, $response);
            $status = $request->getQueryParams()['status']?? null;
            if ($status) {
                $response = $response->withStatus((int)$status);
            }

            return $response;
        });
    }

route

Route method is to define you application routes and controller logics:

    public function route()
    {
        $map = $this->c['router']->getMap();
        // define routes at an action method in an extended class
        $map->get('hello', '/hello/{name}', function($args, $controller) {
            $name = $args['name']?? 'karen';
                return $controller->render('[Karen] Hello, ' . $name);
            })->tokens(['name' => '.*']);
        $this->route = $this->c['router']->getMatcher()->match($this->request);;
    }

or if you use anonymous class, you can your logic to this class:

  • route method:
    public function route()
    {
        $map = $this->c['router']->getMap();
        // define routes at an action method in an extended class
        $map = $this->action($map);
        $this->route = $this->c['router']->getMatcher()->match($this->request);;
    }
  • anonymous class:
$app = new class extends Karen\Framework\Karen {
        public function action($map)
        {
            // hello name controller sample.
            $map->get('hello', '/hello/{name}', function($args, $controller) {
                $name = $args['name']?? 'karen';
                return $controller->render('[Karen] Hello, ' . $name);
            })->tokens(['name' => '.*']);

            return $map;
        }
    };

If you change Aura.Route to another, read src/Framework/Karen2.php

response

In response method, you have to pass your controller callable handler to $this->addQueue('action', $handler, $args). it is different how to get $handler, because it depends on your selected router library.

    public function response(){
        if (!$this->route) {
            $response =$this->response->withStatus(404);
            $response->getBody()->write('not found');
            return;
        }

        // parse args
        $args = [];
        foreach ((array)$this->route->attributes as $key => $val) {
            $args[$key] = $val;
        }
        // add route action to the queue of Midlleware
        $this->addQueue('action', $this->c['controller']->actionQueue($this->route->handler, $args));
    }
  • FastRoute version (Karen2.php)
    public function response(){
        switch ($this->route[0]) {
            case \FastRoute\Dispatcher::NOT_FOUND:
                echo "Not Found\n";
                break;
            case \FastRoute\Dispatcher::FOUND:
                $handler = $this->route[1];
                $args = $this->route[2];
                $this->addQueue('action', $this->c['controller']->actionQueue($handler, $args));
                break;
            default:
                throw new \LogicException('Should not reach this point');
        }
    }

After execute this response method, queue in middleware are executed automatically.

If you want to use JsonResponse:

$map->get('json', '/json/{name}', function($args, $controller) {
    return new \Zend\Diactoros\Response\JsonResponse(['name' => $args['name']]);
});

Your Own Framework

Within this pattern, you have only to implement your framework logic in these methods.

$app = new YourApplication(); // extends Karen\Application
$app->run();
$app->sendResponse();

For example, Karen2 is a sample microframework with FastRoute instead of Aura.Router but you have only to call same methods. see code web/karen2/index.php and src/Framework/Karen2.php

require __DIR__ . '/../../vendor/autoload.php';

$app = new class extends Karen\Framework\Karen2 {
        public function handlers()
        {
            return function(FastRoute\RouteCollector $r) {
                $r->addRoute('GET', '/karen2/hello/{name}', function($args, $controller){
                    return $controller->render('[Karen2] Hello ' . $args['name']);
                });
            };
        }
    };

$app->run();
$app->sendResponse();

Feel free to create your own framework for PHP7.

License

Karen is licensed under the MIT license.

Anonymous controller and trait for Slim3 with PHP7

We can use anonymous classes in PHP7. So I use anonymous classes instead of anonymous functions. Slim3 now supports PSR-7 interfaces for its Request and Response objects. And Slim3 uses extends objects, and use it in your anonymous functions.

But we use Request and Response object directly, so I try to use anonymous classes to write controller action logic and it is to be readability.

code

https://github.com/brtriver/slim3-controller

install

You can install this classes via composer:

composer require brtriver/slim3-controller

Usage

you can read example code.

sample code is blow:

<?php
use Brtriver\Controller\Controller;

$app->get('/hello/{name}', new class($app) extends Controller {

        public function action($args)
        {
            return $this->render($this->container['greet'] . $args['name']);
        }
});

You have to write your controller logic in action method, this action method call when this route is matched. You don't need to use Request and Response objects directly but access with Controller class.

The more you write your routes and similar controller logics with anonymous functions, The more redundancy your application is. and If you define classes with each routes, there are many classes in your application.... then you maybe decide to use full stack frameworks instead of micro frameworks.

So I think it is a one solution to use anonymous classes instead of function.

  • Controller class has these methods and class properties.
    • methods:
      • action($args): you have to write your controller logic in this method.
      • render($output): helper method to render the $output with 200 status
      • render404($output): helper method to render the $output with 404 status
    • properties:
      • $this->request
      • $this->response
      • $this->app
      • $this->container

If use Template engine (PHP or Twig etc...), a renderWithT method in Templatable trait is available:

<?php
use Brtriver\Controller\Controller;
use Brtriver\Controller\Templatable;
$app->get('/hello/{name}', new class($app) extends Controller {
        use Templatable;

        public function action($args)
        {
            return $this->renderWithT('web.html', ['name' => $args['name']]);
        }
});

If use JSON response, a renderWithJson method in JsonResponse trait is available:

<?php
use Brtriver\Controller\Controller;
use Brtriver\Controller\JsonResponse;
$app->get('/json/{name}', new class($app) extends Controller {
        use JsonResponse;

        public function action($args)
        {
            return $this->renderWithJson(['name' => $args['name']]);
        }
});

Simple Database Migration Tool, Dbup

Dbup is a very simple migration tool with UP command

I published a new database migration tool for PHP, Dbup.

github: https://github.com/brtriver/dbup

f:id:brtRiver:20130502021745p:plain

 

How simple?

  • You have only to download dbup.phar.
  • Dbup has only up command. Dbup does not have down command.
  • Dbup use just a plain old sql, so you don't have to learn ORM nor DSL. You write sql file and just call up command.
  • Dbup use just PDO class to migrate.
  • Dbup doesn't need the table of the migration status in a database to migrate.

 Show status

f:id:brtRiver:20130502022424p:plain

 

 Why only up command?

If in a production env, after you added a new column and users insert new data in it, you cannot down migration safely. because new data may be lost.

If you use Dbup and want to do down a database schema, then you have only to create new sql file to execute as a new up sql file.

 

Simple BASIC Authorization controller provider of Silex

Silex is the best web application framework for developers, who want to lean Symfony Components and PHP 5.3 (closure, name space..etc). Silex allow you to create new controllers with "mount" method which adds a controllers collection with "ControllerProvider".

Install

Then I tried to create new controllers for BASIC Authorization which is simple and reuseable. It is very easy to set up this controller provider, you have only to get code from gist in "src/Silex/Provider" directory.

$ curl -O https://raw.github.com/gist/1740012/6d3ab4f0c35b257ac1319a3c6ee2b05e0901a34b/BasicAuthControllerProvider.php

Set up

This provider require UrlGeneratorServiceProvider and SessionServiceProvider, which are default providers Silex has. Then you have to register these providers before in index.php like below:

BasicAuth requires a name "home" of URL which is used after redirecting, of course you can change the name.

Then, add one line which calls the mount method in index.php. It is too simple:

$app->mount('/auth', new Silex\Provider\BasicAuthControllerProvider());

After adding, if you access to "http://example.com/", this application redirects you to "/auth/login" and require your name and password. If you want to logout, you have to access to '/auth/logout'. At Default, name is "demo" and password is "123456", so after you submit them, you can access to "home" page. If you want to remove Basic Authorization, you have only to comment out this mount line.

Customize

If you want to customize the setting about name, password and url to be redirect, just define like below:

Code

compile twig.phar

It is very popular to use Twig on developing with PHP. But it is not easy to deploy my application with Twig because there are a lot of files to deploy. I want to deploy it easily.

So I tried to make twig.phar, which is PHP Archive.

The phar extension provides a way to put entire PHP applications into a single file called a "phar" (PHP Archive) for easy distribution and installation.

It is similar to "jar" (JAVA Archive) in Java.

via: about phar in PHP documentation

This article is a memo for PHP developers who are like me.

compile.php

As first, let's get Twig sources from git.

$ git clone https://github.com/fabpot/Twig.git

It is "compiler.php", which compile twig.phar file.


If it occurs error when you run this script, you have to set "phar.readonly" to "Off" in php.ini.

Sample code with twig.phar

Then, you have only to call with "phar://" like below:

As the result, you have only to deal with this twig.phar file. It is very simple.

Zend Framework page-a-day Calendar 2012 in Japanese

A Happy New Year everyone from Japan!!

In Japan, a new year 2012 has already came.

Zend Framework Page A Day Calendar 2012

We can find articles of Zend Framework in Japanese fewer than the number of other frameworks, I think. Because CakePHP and CodeIgniter are more popular than ZF. in 2011, Japanese Zend Framework Page A Day Calendar is about to start.

ref: Japanese Zend Framework Page A Day Calendar

ZF users will write small tips of Zend Framework (zf), which has PHP framework component libraries of PHP, page a day: DB, Debug, UnitTest, Zend_Service*, History, Architecture and Documentations..

You can read the article of the first day, which is about Zend Framework and History of Web Framework here (Japanese only). It's very interesting. I am looking forward to reading them:)

Japanese Symfony Advent Calender 2011

In this year, there were many advent calenders which were writen about web tech in Japanese. Of course, there was Symfony Advent Calender which was planed by @koyhoge.

You can read all articles at this page (sorry Japanese only).

In Japan, there are a lot of PHP web developers which use frameworks: Symfony, CakePHP, CodeIgniter, ZendFramework..and so on. After releasing Symfony2, They are interested in Symfony2 than before.

I introduced about each calender title :)