Karen -- PSR-7 micro framework with PHP7
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
- methods:
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
How simple?
- You have only to download
dbup.phar
. - Dbup has only
up
command. Dbup does not havedown
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
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 :)
- Day1 @brtriver History of Symfony
- Day2@koyhoge Twig filter which can Entity::find
- Day3@hidenorigoto Read test code of Symfony2
- Day4@taka512 Change the output of Symfony2 commands
- Day5@fivestr How to use Symfony Form Component
- Day6@uechoco Let's Change the output encoding
- Day7@madapaja How fast is Symfony2 in PHP5.4?
- Day8@yuchimiri 3 Steps for creating custom validator in Symfony2
- Day9@Kiske Full use Twig
- Day10@ooharabucyou Doctrine2 Timezone
- Day11@iteman Improvement domain models to use Dependency Injection Container
- Day12@77web Customize the error page of Silex
- Day13@bakorer Setting Symfony2 environment of staging in PHPFog
- Day14@ganchiku reviews of Symfony2 Cook Book
- Day15@makotokaga introduction frameworks (like Symfony2) for PHP beginers
- Day16@hidenorigoto Replace test codes of Symfony by Phake
- Day17@hidenorigoto Debug setting when the app is redirected by Symfony2
- Day18@co3k Secure developments with Symfony2
- Day19@77web Migration by symfony1.4.x + sfDoctrinePlugin2011 day 19
- Day20@fivestr Explanation about Request Class of Smfony2
- Day21@ryster Symfony2 beginner made the bundle
- Day22@yando Run Symfony2 on Windows Azure + PHP
- Day23@okapon_pon Japanese translation about SonataAdminBundle
- Day24@hidenorigoto Management of Backward Compatibility in Symfony2 with @api annotation