minimal / framework
The Minimal Framework.
Installs: 144
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 1
Forks: 0
Open Issues: 0
Language:CSS
Type:project
Requires
- php: >=7.0.0
- minimal/assets: v0.1.4
- minimal/benchmark: v0.1.2
- minimal/cli: v0.2.0
- minimal/database: v0.1.8
- minimal/html: v0.2.0
- minimal/minimal: v0.5.0
- minimal/paths: v0.1.7
- minimal/presenters: v0.1.1
- minimal/translation: v0.1.3
- minimal/views: v0.1.4
Requires (Dev)
- phpunit/phpunit: 4.*
- symfony/var-dumper: 3.3.6
- dev-master
- v0.10.0
- v0.9.12
- v0.9.11
- v0.9.10
- v0.9.9
- v0.9.8
- v0.9.7
- v0.9.6
- v0.9.5
- v0.9.4
- v0.9.3
- v0.9.2
- v0.9.1
- v0.9.0
- v0.8.3
- v0.8.2
- v0.8.1
- v0.8.0
- v0.7.16
- v0.7.15
- v0.7.14
- v0.7.13
- v0.7.12
- v0.7.11
- v0.7.10
- v0.7.9
- v0.7.8
- v0.7.7
- v0.7.6
- v0.7.5
- v0.7.4
- v0.7.3
- v0.7.2
- v0.7.1
- v0.7.0
- v0.6.1
- v0.6.0
- v0.5.4
- v0.5.3
- v0.5.2
- v0.5.1
- v0.5.0
- v0.4.10
- v0.4.9
- v0.4.8
- v0.4.7
- v0.4.6
- v0.4.5
- v0.4.4
- v0.4.3
- v0.4.2
- v0.4.1
- v0.4.0
- v0.3.9
- v0.3.8
- v0.3.7
- v0.3.6
- v0.3.5
- v0.3.4
- v0.3.3
- v0.3.2
- v0.3.1
- v0.3.0
- v0.2.3
- v0.2.2
- v0.2.1
- v0.2.0
- v0.1.13
- v0.1.12
- v0.1.11
- v0.1.10
- v0.1.9
- v0.1.8
- v0.1.7
- v0.1.6
- v0.1.5
- v0.1.4
- v0.1.3
- v0.1.2
- v0.1.1
- v0.1.0
This package is auto-updated.
Last update: 2024-10-30 01:59:29 UTC
README
Please acknowledge that this is solely a Tech/Skill Demo. It is not intended for use in actual projects, although it is fully functional and tested under PHP 7.
Minimal is a MVC web application framework for PHP.
App::dispatch(function () { DB::connections(Config::database()); Router::get('space-game/(:num)/(:num)', function ($characterId, $levelId) { return [ Character::with('sprite', 'trait')->getById($characterId)->toArray(), LevelSpec::with('sprite', 'entity.trait')->getById($levelId)->toArray() ]; }); }
The code snippet demonstrates a monolithic approach to the framework, employing facades to define a single-function web application endpoint. There is much more than static classes under the hood! It is an efficient solution for developing small REST APIs. The framework automatically converts ORM returned data (models implementing the JsonableInterface) into JSON, streamlining the API response process. Conversely, the framework accommodates alternative setup familiar in other frameworks, supporting a traditional modular MVC architecture for more complex, larger-scale projects.
...and if you're not happy with the Router (or any other component), you could do:
App::bind(RouterInterface::class, MyCustomRouter::class);
...yes, SOLID principles.
Quickstart example | Routing | Dependency Injection | Providers | Middlewares | Controllers | Views | Assets | CLI
Introduction
Key features:
- Build MVC-, REST-, CLI-APIs and apps and query databases with a ORM
- Take advantage of inversion of control and facades
- Easy install via command line and works out of the box
- No dependencies to third party libraries (except in development mode: PHPUnit, Symfony VarDumper)
- Most of the core components work standalone
- Plain PHP in the views/templates
Requirements
- PHP version 7.x
- composer
Install
With the default directory structure:
$ composer create-project minimal/framework
Then point your server's document root to the public directory.
If you use the PHP-builtin webserver then do:
$ cd public
$ php -S 0.0.0.0:8000 server.php
Vendor libraries only:
$ composer require minimal/framework
Minimal installation for code style like in the introduction above:
$ composer require minimal/minimal
Usage
Quickstart example | Routing | Dependency Injection | Providers | Middlewares | Controllers | Views | Assets | CLI
Quickstart example
App::dispatch(function () { // Register additional services App::register(['Demo' => DemoServiceProvider::class]); // Respond on GET request Router::get('/', function () { return 'Hello from Minimal!'; }); // Respond on GET request with uri paramters Router::get('hello/(:any)/(:num)', function ($any, $num) { return 'Hello ' . $any . ' ' . $num ; }); // Respond on POST request Router::post('/', function () { return Request::post(); }); // Respond with HTTP location Router::get('redirection', function () { Response::redirect('/'); }); // Respond with a view Router::get('view', function () { return View::render('fancy-html', ['param' => 'value']); }); // Test the database connection Router::get('database', function () { DB::connections(Config::database()); return 'Successfully connected to database'; }); // Route group Router::group([ 'uriPrefix' => 'route-groups', 'namespace' => 'App\\Demo\\Base\\Controllers\\', 'middlewares' => [ 'App\\Demo\\Base\\Middlewares\\CheckPermission', 'App\\Demo\\Base\\Middlewares\\ReportAccess', ] ], function () { // Responds to GET route-groups/controller-action/with/middlewares' Router::get('controller-action/with/middlewares', [ 'middlewares' => ['App\\Demo\\Base\\Middlewares\\Cache' => [10]], 'controller' => 'YourController', 'action' => 'timeConsumingAction' ]); // Do database stuff Router::get('users', function () { // Connect to database DB::connections(Config::database()); // Truncate tables Role::instance()->truncate(); User::instance()->truncate(); // Create 2 new roles Role::create([['name' => 'admin'], ['name' => 'member']]); // Get all the roles $roles = Role::all(); // Create a user $user = User::create(['username' => 'john']); // Assign all roles to this user $user->roles()->attach($roles); // Get the first username 'john' with his roles return $user->with('roles')->where(['username', 'john'])->first(); }); // ... subgroups are possible ... }); });
Quickstart example | Routing | Dependency Injection | Providers | Middlewares | Controllers | Views | Assets | CLI
Routing
Direct output:
Router::get('hello/(:any)/(:any)', function($firstname, $lastname) { return 'Hello ' . ucfirst($firstname) . ' ' . ucfirst($lastname); }); // (:segment) match anything between two slashes // (:any) match anything until next wildcard or end of uri // (:num) match integer only
http://localhost/hello/julien/duseyau -> Hello Julien Duseyau
// Router::get() responds to GET requests // Router::post() responds to POST requests // Router::put() ...you get it // Router::patch() // Router::delete() // 1st parameter: string uri pattern // 2nd parameter: a closure with return sends a response to the client, a array // of key/value pairs sets the attributes of the route object, which are: // 'controller': the controller class to load, // 'action':, the method to execute // 'uriPrefix': a string that prefixes the uri pattern // 'middlewares': a multidimensional array of middleware with optional params // 'params': array of values that will be injected to the method
Using controllers
Router::get(hello/(:any)/(:any)', 'App\\Demo\\Base\\Controllers\\YourController@yourMethod');
or
Router::get(hello/(:any)/(:any), [ 'controller' => YourController::class, 'action' => 'yourMethod' ]);
class App\Demo\Base\Controllers\YourController { public function yourMethod($name, $lastname) { return 'Hello ' . ucfirst($name) . ' ' . ucfirst($lastname); } }
http://localhost/hello/julien/duseyau -> Hello Julien Duseyau
Route groups
Router::group([ // Prefixes all urls in the group with 'auth/' 'uriPrefix' => 'auth', // Define the class namespace for all routes in this group // Will be prefixed to the controllers 'namespace' => 'App\\Demo\\Auth\\Controllers\\' ], function () { // GET request: 'auth/login' // Controller 'App\\Demo\\Auth\\Controllers\AuthController Router::get('login', [ 'controller' => 'AuthController', 'action' => 'loginForm' // Show the login form ]); // POST request: 'auth/login' // Controller 'App\\Demo\\Auth\\Controllers\AuthController Router::post('login', [ 'controller' => 'AuthController', 'action' => 'login' // Login the user ]); /** * Subgroup with middlewares */ Router::group([ // Middlewares apply to all route in this (sub)group 'middlewares' => [ // Check if the client is authorised to access these routes 'App\\Demo\\Auth\\Middlewares\\CheckPermission', // Log or send a access report 'App\\Demo\\Auth\\Middlewares\\ReportAccess', ] ], function () { // No access to these routes if middleware CheckPermission fails // GET request: 'auth/users' // Controller 'App\\Demo\\Auth\\Controllers\UserController Router::get('users', [ 'controller' => 'UserController', 'action' => 'list' // Show a list of users ]); // etc... }); });
File download
Router::get('download/pdf', function () { Response::header('Content-Type: application/pdf'); Response::header('Content-Disposition: attachment; filename="downloaded.pdf"'); readfile('sample.pdf'); });
Single route execution from anywhere
$widget = App::execute('route/of/widget')
Quickstart example | Routing | Dependency Injection | Providers | Middlewares | Controllers | Views | Assets | CLI
Dependency injection
Binding a interface to a implementation is straight forward:
App::bind([ 'App\\InterfaceA' => App\ClassA::class, 'App\\InterfaceB' => App\ClassB::class, 'App\\InterfaceC' => App\ClassC::class ]);
or in config/bindings.php
return [ 'App\\InterfaceA' => \App\ClassA::class, 'App\\InterfaceB' => \App\ClassB::class, 'App\\InterfaceC' => \App\ClassC::class ];
class ClassA {} class ClassB {} class ClassC { public function __construct(InterfaceB $classB) {} } class MyClass { public function __construct(InterfaceA $classA, InterfaceC $classC) {} }
$MyClass = App::make(MyClass::class);
Quickstart example | Routing | Dependency Injection | Providers | Middlewares | Controllers | Views | Assets | CLI
Providers
Providers are service providers
App::register([ 'MyService' => \App\MyService::class, 'App\MyClass' => \App\MyClass::class, 'MyOtherClassA' => \App\MyOtherClassAFactory::class, 'any-key-name-will-do' => \App\MyOtherClassB::class, ]);
or in config/providers.php
return [ 'MyService' => \App\MyServiceProvider::class, 'App\\MyClass' => \App\MyClass::class, 'MyOtherClassA' => \App\MyOtherClassA::class, 'any-key-name-will-do' => \App\MyOtherClassB::class, ];
class MyServiceProvider extends AbstractProvider { /** * This is what happens when we call App::resolve('MyService') */ public function resolve() { // Do something before the class is instantiated $time = time(); $settings = Config::item('settings'); // return new instance return App::make(MyService::class, [$time, $settings]); // ... or make singleton and resolve dependencies return $this->singleton('MySingleton', App::make(App\\MyService::class, [ App::resolve('App\\MyOtherClassA'), App::resolve('App\\MyOtherClassB'), $time, $settings ]); } /** * Optional: Register additional config if needed */ public function config(): array { return [ 'key' => 'value' ]; } /** * Optional: Register additional bindings if needed */ public function bindings(): array { return [ 'SomeInterface' => SomeClass::class ]; } /** * Optional: Register additional services if needed */ public function providers(): array { return [ 'SomeService' => SomeServiceProvider:class ]; } /** * Optional: Register additional event subscribers */ public function subscribers(): array { return [ 'event.name' => EventSubscriber::Class ]; } /** * Optional: Register additional routes */ public function routes() { Router::get('my-service', MySerciceController::class . '@myControllerMethod') } }
$myService = App::resolve('MyService');
Quickstart example | Routing | Dependency Injection | Providers | Middlewares | Controllers | Views | Assets | CLI
Middlewares
// in config/routes.php Router::get('users', [ 'controller' => 'UsersController', 'action' => 'list', 'middlewares' => [ // Check if the client is authorized to access this route 'App\\Middlewares\\checkPermission', // Send a email to the administrator 'App\\Middlewares\\ReportAccess', // Cache for x seconds 'App\\Middlewares\\Cache' => [(1*1*10)] ] ]);
// in app/Middlewares/CheckPermission.php class CheckPermission implements MiddlewareInterface { ... // Inject what you want, instance is created through // IOC::make() which injects any dependencies public function __construct( RequestInterface $request, ResponseInterface $response, RouteInterface $route ) { $this->request = $request; $this->response = $response; $this->route = $route; } // Executed before dispatch public function before() { // If not authorised... // ... send appropriate response ... $this->response->addHeader(); $this->response->setContent(); $this->response->send()->exit(); // ... or redirect to login page $this->response->redirect('login'); // ... or set error and cancel dispatch $this->request->setError(); return false; } }
// in app/Middlewares/Cache.php class Cache implements MiddlewareInterface { ... // Executed before dispatch public function before() { // return cached contents } // Executed after dispatch public function after() { // delete old cache // create new cache } }
Standalone example
$result = Middleware::dispatch(function() { return 'the task, for example FrontController::dispatch(Router::route())'; }, [ 'App\\Middlewares\\checkPermission', 'App\\Middlewares\\ReportAccess', 'App\\Middlewares\\Cache' => [(1*1*10)] ]);
Quickstart example | Routing | Dependency Injection | Providers | Middlewares | Controllers | Views | Assets | CLI
Controllers
The controllers specified in the routes are instantiated through Provider->make() (e.g. App::make()), which will always look for a singleton first, then search the service container for a provider or factory or else just create a instance and inject dependencies. Which means there is nothing to do to make this controller with concrete dependencies work:
class MyController { public function __construct(MyModelA $myModelA, MyModelB $myModelB) { $this->modelA = $myModelA; $this->modelB = $myModelB; } }
In order to use interfaces, bindings have to be registered. See also config/bindings.php
App::bind(MyModelInterface::class, MyModel::class);
class MyController { public function __construct(MyModelInterface $myModel) { $this->model = $myModel; } }
For a more control register a factory. See also config/providers.php
App::register(MyController::class, MyControllerFactory::class);
class MyControllerFactory extends AbstractProvider { public function resolve() { return new MyController('value1', 'value2'); } }
class MyController { public function __construct($optionA, $optionB) { // $optionA is 'value1', $optionB is 'value2' } }
Quickstart example | Routing | Dependency Injection | Providers | Middlewares | Controllers | Views | Assets | CLI
Views
// The base directory to start from View::setBase('../resources/views/'); // The theme directory in base directory, is optional and can be ingored View::setTheme('my-theme'); // The layout file without '.php' from the base/theme directory View::setLayout('layouts/my-layout'); // Set variables for the view View::set('viewValue1', 'someValue1') // By default variables are only accessible in the current view // To share a variable $title across all layout and views View::share('title', 'My title'); // Render a view without layout View::view('pages/my-view', [ 'viewValue2' => 'someValue2' // Same as View::set() ]); // Render a view with layout, but in case of ajax only the view View::render('pages/my-view', [ 'viewValue2' => 'someValue2' // Same as View::set() ]);
<!-- resources/views/my-theme/layouts/my-layout.php --> <!DOCTYPE html> <html> <head> <title><?=$title?></title> </head> <body> <?= self::view() ?> </body> </html>
<!-- resources/views/my-theme/main/my-view.php --> <p><?= $viewValue1 ?></p> <p><?= $viewValue2 ?></p>
Result:
<!DOCTYPE html> <html> <head> <title>My title</title> </head> <body> <p>someValue1</p> <p>someValue2</p> </body> </html>
Where to do these View calls? Anywhere is fine. But one place could be:
class BaseController { public function __construct() { View::setBase(__DIR__'/../views/'); View::setTheme('my-theme'); View::setLayout('layouts/my-layout'); Assets::setBase(__DIR__'/../assets'); Assets::setTheme('my-theme'); } }
then
class MyController extends BaseController { private $user; public function __construct(UserInterface $user) { parent::__construct(); $this->user = $user; } public function myAction() { View::render('my-view', ['user' => $this->user->find(1)]); } }
Quickstart example | Routing | Dependency Injection | Providers | Middlewares | Controllers | Views | Assets | CLI
Assets
// The base directory to start from Assets::setBase('../app/Pages/resources/assets'); // The theme directory in base directory, is optional and can be ingored Assets::setTheme('my-theme'); // Directory for css (default 'css') Assets::setCssDir('css'); // Directory for js (default 'js') Assets::setJsDir('js'); // Register css files Assets::addCss(['normalize.css', 'main.css']); //Register js files with keyword Assets::addJs(['vendor/modernizr-2.8.3.min.js'], 'top'); // Register more js files with another keyword Assets::addJs(['plugins.js', 'main.js'], 'bottom'); // Js from CDN Assets::addExternalJs(['https://code.jquery.com/jquery-3.1.0.min.js'], 'bottom'); // Add inline javascript Assets::addInlineScripts('jQueryFallback', function () use ($view) { return View::render('scripts/jquery-fallback', [], true); });
<!-- resources/views/my-theme/layouts/my-layout.php --> <html> <head> <title><?=$title?></title> <?= Assets::getCss() ?> <?= Assets::getJs('top') ?> </head> <body> <div class="content"> ... </div> <?= Assets::getExternalJs('bottom') ?> <?= Assets::getInlineScripts('jQueryFallback') ?> <?= Assets::getJs('bottom') ?> <?= Assets::getInlineScripts() ?> </body> </html>
Outputs:
<html> <head> <title>My title</title> <link rel="stylesheet" href="assets/my-theme/css/normalize.css"> <link rel="stylesheet" href="assets/my-theme/css/main.css"> <script src="assets/my-theme/js/vendor/modernizr-2.8.3.min.js" ></script> </head> <body> <div class="content"> ... </div> <script src="https://code.jquery.com/jquery-3.1.0.min.js" ></script> <script>window.jQuery || document.write('...blablabla...')</script> <script src="assets/my-theme/js/plugins.js" ></script> <script src="assets/my-theme/js/main.js" ></script> </body> </html>
Quickstart example | Routing | Dependency Injection | Providers | Middlewares | Controllers | Views | Assets | CLI
CLI
List all registered routes
$ php minimal routes ----------------------------------------------------------------------------------------------------------------------------------------------------------- | Type | Pattern | Action | Middlewares | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | GET | / | <= Closure() | | | GET | /hello/(:any)/(:any) | <= Closure() | | | GET | /welcome/(:any)/(:any) | App\Controllers\YourController@yourMethod | | | GET | /auth/login | App\Controllers\AuthController@loginForm | | | POST | /auth/login | App\Controllers\AuthController@login | | | GET | /auth/logout | App\Controllers\AuthController@logout | | | GET | /auth/users | App\Controllers\UserController@list | App\Middlewares\CheckPermission, App\Middlewares\ReportAccess | | GET | /auth/users/create | App\Controllers\UserController@createForm | App\Middlewares\CheckPermission, App\Middlewares\ReportAccess | | GET | /auth/users/edit/(:num) | App\Controllers\UserController@editForm | App\Middlewares\CheckPermission, App\Middlewares\ReportAccess | | GET | /download/pdf | <= Closure() | | | GET | /huge/data/table | App\Controllers\YourController@timeConsumingAction | App\Middlewares\Cache(10) | | GET | /pages/(:any) | App\Pages\Controllers\PagesController@getStaticPage | App\Middlewares\Cache(10) | | GET | /pages/info | App\Pages\Controllers\PagesController@info | App\Middlewares\Cache(10) | | GET | /assets/(:any) | App\Assets\Controllers\AssetsController@getAsset | | -----------------------------------------------------------------------------------------------------------------------------------------------------------
List all registered modules
$ php minimal modules --------------------------------------------------------------------------------------------------------------------------------------------------------- | Name | Path | Config | Routes | Providers | Bindings | --------------------------------------------------------------------------------------------------------------------------------------------------------- | Pages | app/Pages/ | app/Pages/Config/config.php | app/Pages/Config/routes.php | app/Pages/Config/providers.php | app/Pages/Config/bindings.php | | Assets | app/Assets/ | app/Assets/Config/config.php | app/Assets/Config/routes.php | app/Assets/Config/providers.php | app/Assets/Config/bindings.php | ---------------------------------------------------------------------------------------------------------------------------------------------------------
List all registered bindings
$ php minimal bindings
List all registered providers
$ php minimal providers
List all events and subscribers
$ php minimal events
List all registered config
$ php minimal config
Quickstart example | Routing | Dependency Injection | Providers | Middlewares | Controllers | Views | Assets | CLI
Components
Minimal requires at least these packages:
- judus/minimal-collections - a simple iterator
- judus/minimal-config - handles a multidimensional array
- judus/minimal-controllers - the frontcontroller
- judus/minimal-http - request and response objects
- judus/minimal-middlewares - a unconventional middleware implementation
- judus/minimal-minimal - the application object that binds all together
- judus/minimal-provider - service provider and dependency injector
- judus/minimal-routing - the router
These packages are also included but are not necessary:
- judus/minimal-assets - register css and js during runtime, dump html link and script tags
- judus/minimal-benchmark - this "benchmarking" class is only used in the demo
- judus/minimal-cli - a command line interface, which will be completely redone asap
- judus/minimal-database - a pdo connector, a mysql query builder and a ORM
- judus/minimal-event - a simple event dispatcher
- judus/minimal-html - for now just a html table class
- judus/minimal-log - a simple logger
- judus/minimal-paths - might help creating paths and urls
- judus/minimal-translation - translations in a pretty printed json file
- judus/minimal-views - simple php views and layouts
License
The Minimal framework is open-sourced software licensed under the MIT license
Quickstart example | Routing | Dependency Injection | Providers | Middlewares | Controllers | Views | Assets | CLI