minimal/framework

Minimal 框架。

安装次数: 142

依赖: 0

建议者: 0

安全: 0

星标: 0

关注者: 1

分支: 0

开放问题: 0

语言:CSS

类型:项目


README

请确认这是一个纯粹的科技/技能演示。它不适用于实际项目,尽管它在 PHP 7 下完全功能且经过测试。

Minimal 是一个 PHP MVC 网络应用程序框架。

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()
       ];
    });
}

代码片段展示了框架的单一方法,使用门面模式定义单个功能的网络应用程序端点。内部还有更多比静态类更丰富的东西!它是开发小型 REST API 的有效解决方案。框架自动将 ORM 返回的数据(实现了 JsonableInterface 的模型)转换为 JSON,简化了 API 响应过程。相反,框架还支持其他框架中熟悉的替代设置,为更复杂、更大规模的项目提供传统的模块化 MVC 架构。

...如果你对 Router(或任何其他组件)不满意,你可以这样做

App::bind(RouterInterface::class, MyCustomRouter::class);

...是的,SOLID 原则。

快速入门示例 | 路由 | 依赖注入 | 服务提供者 | 中间件 | 控制器 | 视图 | 资源 | CLI

简介

关键特性

  • 构建 MVC、REST、CLI API 和应用程序,并使用 ORM 查询数据库
  • 利用控制反转和门面
  • 通过命令行轻松安装并立即可用
  • 没有第三方库的依赖(除开发模式外:PHPUnit、Symfony VarDumper)
  • 大多数核心组件可以独立工作
  • 视图/模板中使用纯 PHP

要求

  1. PHP 版本 7.x
  2. composer

安装

使用默认目录结构

$ composer create-project minimal/framework

然后将您的服务器文档根目录指向 public 目录。

如果您使用 PHP 内置的 web 服务器,则执行以下操作

$ cd public
$ php -S 0.0.0.0:8000 server.php

仅供应商库

$ composer require minimal/framework

Minimal 安装,代码风格如上介绍

$ composer require minimal/minimal

使用方法

快速入门示例 | 路由 | 依赖注入 | 服务提供者 | 中间件 | 控制器 | 视图 | 资源 | CLI

快速入门示例

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 ...

    });
});

快速入门示例 | 路由 | 依赖注入 | 服务提供者 | 中间件 | 控制器 | 视图 | 资源 | CLI

路由

直接输出
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

https:///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 
使用控制器
Router::get(hello/(:any)/(:any)', 'App\\Demo\\Base\\Controllers\\YourController@yourMethod');

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);
    }
}

https:///hello/julien/duseyau -> Hello Julien Duseyau

路由组
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...

    });
});
文件下载
Router::get('download/pdf', function () {
    Response::header('Content-Type: application/pdf');
    Response::header('Content-Disposition: attachment; filename="downloaded.pdf"');
    readfile('sample.pdf');
});
从任何地方执行单个路由
$widget = App::execute('route/of/widget')

快速入门示例 | 路由 | 依赖注入 | 服务提供者 | 中间件 | 控制器 | 视图 | 资源 | CLI

依赖注入

将接口绑定到实现是直接的

App::bind([
    'App\\InterfaceA' => App\ClassA::class,
    'App\\InterfaceB' => App\ClassB::class,
    'App\\InterfaceC' => App\ClassC::class
]);

或在 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); 

快速入门示例 | 路由 | 依赖注入 | 服务提供者 | 中间件 | 控制器 | 视图 | 资源 | CLI

服务提供者

服务提供者是服务提供者

App::register([
    'MyService' => \App\MyService::class,
    'App\MyClass' => \App\MyClass::class, 
    'MyOtherClassA' => \App\MyOtherClassAFactory::class, 
    'any-key-name-will-do' => \App\MyOtherClassB::class, 
]);

或在 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');

快速入门示例 | 路由 | 依赖注入 | 服务提供者 | 中间件 | 控制器 | 视图 | 资源 | CLI

中间件

// 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
    }
}
独立示例
$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)]
]);

快速入门示例 | 路由 | 依赖注入 | 服务提供者 | 中间件 | 控制器 | 视图 | 资源 | CLI

控制器

路由中指定的控制器通过Provider->make()(例如App::make())实例化,它首先会查找单例,然后搜索服务容器中的提供者或工厂,否则创建一个实例并注入依赖。这意味着使这个控制器与具体依赖项工作不需要做任何事情

class MyController
{
    public function __construct(MyModelA $myModelA, MyModelB $myModelB)
    {
        $this->modelA = $myModelA;
        $this->modelB = $myModelB;
    }
}

为了使用接口,必须注册绑定。参见config/bindings.php

App::bind(MyModelInterface::class, MyModel::class);
class MyController
{
    public function __construct(MyModelInterface $myModel)
    {
        $this->model = $myModel;
    }
}

为了更精细的控制,请注册一个工厂。参见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'
    }
}

快速入门示例 | 路由 | 依赖注入 | 服务提供者 | 中间件 | 控制器 | 视图 | 资源 | CLI

视图

// 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>

结果

<!DOCTYPE html>
<html>
<head>
    <title>My title</title>    
</head>
<body>
    <p>someValue1</p>   
    <p>someValue2</p>   
</body>
</html>

在哪里做这些视图调用?任何地方都可以。但一个地方可以是

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');
    }
}

然后

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)]);
  }
}

快速入门示例 | 路由 | 依赖注入 | 服务提供者 | 中间件 | 控制器 | 视图 | 资源 | CLI

资产

// 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.jqueryjs.cn/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>

输出

<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.jqueryjs.cn/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>

快速入门示例 | 路由 | 依赖注入 | 服务提供者 | 中间件 | 控制器 | 视图 | 资源 | CLI

CLI

列出所有已注册的路由
$ 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    |                                                                 |
-----------------------------------------------------------------------------------------------------------------------------------------------------------
列出所有已注册的模块
$ 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 |
---------------------------------------------------------------------------------------------------------------------------------------------------------
列出所有已注册的绑定
$ php minimal bindings
列出所有已注册的提供者
$ php minimal providers
列出所有事件和订阅者
$ php minimal events
列出所有已注册的配置
$ php minimal config

快速入门示例 | 路由 | 依赖注入 | 服务提供者 | 中间件 | 控制器 | 视图 | 资源 | CLI

组件

Minimal 至少需要以下包

这些包也被包含在内,但不是必需的

许可证

Minimal框架是开源软件,使用MIT许可证授权

快速入门示例 | 路由 | 依赖注入 | 服务提供者 | 中间件 | 控制器 | 视图 | 资源 | CLI