Developed by GianArb and a little community
I'm Gianluca Arbezzano work in Corley SRL and I am passionate developer
Doctrine Maintainer and Open source contributor
In computer programming, a software framework is an abstraction in which software providing generic functionality can be selectively changed by additional user-written code, thus providing application-specific software
cit. Wikipedia
Build YOUR own framework, now the PHP ecosystem is awesome!
There are projects for all but we choose for you a lot of them
This is a penny-classic-app
Directory Structure
├── app
│ ├── Controller
│ ├── Form
│ ├── Entity
│ ├── ...
│ └── view
├── bower.json
├── composer.json
├── config
│ └── di.php
├── Gruntfile.js
├── package.json
├── vendor
└── public
└── index.php
bootstrap
// public/index.php
use GianArb\Penny\App;
use Zend\Diactoros\Response\SapiEmitter;
chdir(dirname(__DIR__));
require 'vendor/autoload.php';
$app = new App();
$emitter = new SapiEmitter();
$emitter->emit($app->run());
config
// config/di.php
return [
'template' => \DI\object(\League\Plates\Engine::class)
->constructor('./app/view/'),
'router' => function () {
return \FastRoute\simpleDispatcher(function (\FastRoute\RouteCollector $r) {
$r->addRoute('GET', '/', ['ClassicApp\Controller\IndexController', 'index']);
});
},
];
controller
namespace ClassicApp\Controller;
class IndexController
{
/**
* @Inject("di")
*/
private $di;
public function index(Request $request, Response $response)
{
$response->getBody()->write(' a beautiful job');
return $response;
}
}
POPO - Plain old PHP Object
You can test application flow
use GianArb\Penny\App;
use GianArb\Penny\Config\Loader;
use FastRoute;
class IndexControllerTest extends PHPUnit_Framework_TestCase
{
private $app;
protected function setUp()
{
$config = Loader::load();
$config['router'] = FastRoute\simpleDispatcher(function (FastRoute\RouteCollector $r) {
$r->addRoute('GET', '/', ['TestApp\Controller\Index', 'index']);
});
$this->app = new App(App::buildContainer($config));
}
public function testIndex()
{
// mock request & response and used services
$this->app->run($request, $response);
}
}
Easy to understand
Easy to test
You can test single controller lightly
use Zend\Diactoros\Request;
use Zend\Diactoros\Response;
use Zend\Diactoros\Uri;
public function testIndex()
{
$request = (new Request())
->withUri(new Uri('/'))
->withMethod('GET');
$response = new Response();
$controller = new IndexController();
// mock controller dependencies
$response = $controller->run($request, $response);
$this->assertEquals(200, $response->getStatusCode());
}
public function setUp()
{
$config = Loader::load();
$config['router'] = FastRoute\simpleDispatcher(function (FastRoute\RouteCollector $r) {
$r->addRoute('GET', '/', ['TestApp\Controller\Index', 'diTest']);
});
$this->container = App::buildContainer($config);
}
public function testInjectionHttpFlow()
{
$this->container->set('troyan', 'call me');
$app = new App($this->container);
$request = (new Request())
->withUri(new Uri('/'))
->withMethod('GET');
$response = new Response();
$response = $app->run($request, $response);
$this->assertEquals(200, $response->getStatusCode());
$this->assertEquals('call me', $response->getBody()->__toString());
}
You can increase the application flow with listener
Controller matched is a listener with priority 0
Penny triggers two events controller.method and controller.method_error to catch errors
Use dependency injection to attach your listeners
// config/di.php
return [
'event_manager' => \DI\decorate(function($eventManager, $container) {
$eventManager->attach(
"ERROR_DISPATCH",
[
$container->get(\ClassicApp\EventListener\DispatcherExceptionListener::class),
"onError"
]
);
$eventManager->attach("*_error", [
$container->get(\ClassicApp\EventListener\ExceptionListener::class),
"onError"
]);
return $eventManager;
}),
\ClassicApp\EventListener\ExceptionListener::class
=> \DI\object(\ClassicApp\EventListener\ExceptionListener::class)
->constructor(\DI\get("template")),
\ClassicApp\EventListener\DispatcherExceptionListener::class
=> \DI\object(\ClassicApp\EventListener\DispatcherExceptionListener::class)
->constructor(\DI\get("template")),
...
];
class ExceptionListener
{
private $template;
public function __construct($template)
{
$this->template = $template;
}
public function onError($event)
{
$e = $event->getException();
$response = $event->getResponse()->withStatus($e->getCode());
$event->setResponse($response);
$response->getBody()->write($this->template->render("error/50x", [
'title' => $e->getMessage(),
'exception' => $e,
]));
}
}
// config/di.php
return [
'router' => function () {
return \FastRoute\simpleDispatcher(function (\FastRoute\RouteCollector $r) {
$r->addRoute('GET', '/', ['ClassicApp\Controller\Index', 'dashboard']);
});
},
'event_manager' => \DI\decorate(function($eventManager, $container) {
$eventManager->attach("index.dashboard", [
$container->get(\ClassicApp\EventListener\AuthListener::class), "check"
], 100);
$eventManager->attach("index.dashboard", [
$container->get(\ClassicApp\EventListener\LogListener::class), "info"
], 1000);
return $eventManager;
}),
\ClassicApp\EventListener\LogListener::class
=> \DI\object(\ClassicApp\EventListener\LogListener::class)
->constructor(\DI\get("monolog")),
\ClassicApp\EventListener\AuthListener::class
=> \DI\object(\ClassicApp\EventListener\AuthListener::class)
->constructor(\DI\get("auth_service")),
...
];
namespace \ClassicApp\EventListener;
class AuthListener
{
public function check($event)
{
$request = $event->getRequest();
if($this->authService->isAuthorized($request) {
return $event;
}
$event->stopPropagation();
}
}
I'm not interested to help you to switch between libraries
We are not crazy, monday plates, sunday zend\view is not our use case
We are very happy to see your implementation, psr-7 and container-interop are examples of interoperability
use GianArb\Penny\App;
use GianArb\Penny\Config\Loader;
use FastRoute;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
$config = Loader::load();
$config['router'] = FastRoute\simpleDispatcher(function (FastRoute\RouteCollector $r) {
$r->addRoute('GET', '/', [get_class($this), 'index']);
});
$config['dispatcher'] = \Di\object('GianArb\PennyTest\Utils\FastSymfonyDispatcher')
->constructor(\Di\get("router"));
$this->app = new App(App::buildContainer($config));
$this->app->getContainer()
->get("event_manager")
->attach("symfonykerneltest.index_error",
function ($e) use (&$requestTest, &$responseTest) {
$requestTest = $e->getRequest();
$responseTest = $e->getResponse();
});
$request = Request::create("/", "GET");
$response = new Response();
$this->app->run($request, $response);
$this->assertSame($request, $requestTest);
$this->assertSame($response, $responseTest);
Easy to debug with docker and zray
Thanks github.com/fntlnz