tueena / core
tueena 框架的核心包:一个结合了依赖注入框架和应用启动器的框架。
Requires
- php: >=5.3.0
This package is not auto-updated.
Last update: 2024-09-18 10:47:59 UTC
README
tueena 框架是一个依赖注入框架、应用启动器和类加载器的组合。它不会告诉你如何设计架构。你可以用它来编写 MVC 应用、MVP 应用、带有命令的 CLI 应用等等。它用 PHP 编写,并采用 MIT 许可证。
依赖注入 ("DI") 帮助你编写 松耦合 的代码,并编写易于 测试 的代码。tueena 框架本身是通过测试驱动的开发,代码覆盖率达到了 100%(当然,这并不意味着它完全经过测试)。
我专注于 强烈的错误信息,因为我发现,在其他 DI 框架中,错误信息不佳或缺失是最大的问题(尚未尝试过 PHP 中的此类框架)。
安装
需要 PHP >= 4.3.0。
下载代码,克隆存储库或使用 composer(包名为 tueena/core
)。
它是如何工作的?
你编写一个前端控制器,该控制器检索应用程序工厂。你告诉这个工厂创建一个 Application
对象。运行应用程序。要构建应用程序,你必须配置类加载器,你必须定义你的服务(这些都是将来的注入的 PHP 类)和定义一个主函数。这看起来像这样
<?php namespace my\application; use tueena\core\Loader; use tueena\core\services\ServiceDefinitions; use tueena\core\types\Type; use ... $ApplicationFactory = require_once '/path/to/tueena/framework/source/applicationFactory.php'; $Application = $ApplicationFactory( // Configure the class loader. function (Loader $Loader) { $Loader->defineNamespaceDirectory('my\\application', __DIR__); }, // Define your services (IHttpRequest, HttpRequest, IRouter and Router are // not part of the framework). function (ServiceDefinitions $ServiceDefinitions) { $ServiceDefinitions ->add( IdentifyingType::is(Type::fromName('IHttpRequest')), ImplementingType::is(Type::fromName('HttpRequest')) ) ->add( IdentifyingType::is(Type::fromName('IRouter')), ImplementingType::is(Type::fromName('Router')) ) // ... ; }, // Your main function (with any parameters, you need). function (IRouter $Router, Injetor $Injector, ...) { // The code here is just an example. You don't have to write // routers, controllers, response objects. You can do anything // here... $Controller = $Router->getControler(); $methodName = $Router->getControllerMethod(); $InjectionTarget = new InjectionTargetMethod($Controller, $methodName); $Response = $Injector->resolve($InjectionTarget); $Response->send(); } ); $Application->run();
现在,这个文件略微扩展,并带有注释,以了解其工作原理和可能做什么
<?php namespace my\application; // Those three usings are required. use tueena\core\Loader; use tueena\core\services\ServiceDefinitions; use tueena\core\types\Type; use ... // The file applicationFactory.php returns the ApplicationFacory closure. $ApplicationFactory = require_once '/path/to/tueena/framework/source/applicationFactory.php'; // This closure is called with three parameters. Each of them has to be // a closure. The first one is to configure the class loader, the second one // to define your services and the third one is the main function. $Application = $ApplicationFactory( // Tell the "Loader" where to find your classes. // This closure must be defined with one parameter, that will be an // instance of \tueena\core\Loader. function (Loader $Loader) { // The Loader has two public methods, you can use. // defineNamespaceDirectory('foo\\bar', '/my/path') means, that // a class \foo\bar\baz\Qux would be searched in the file // /my/path/baz/Qux.php. // With the addLoader() method, you can define a closure, that will // be called with the name of the class as parameter. Return true, // if you found the file and false, if not. $Loader ->defineNamespaceDirectory('my\\application', __DIR__) ->addLoader(function ($className) { // Include your file and return true on success or // false otherwise. }); // The loaders will be called in the order, they have been defined. }, // Define your services. // The parameter of this closure must be an instance of // \tueena\core\services\ServiceDefinitions. function (ServiceDefinitions $ServiceDefinitions) { // A service has an identifying type and an implementing type. If // the Injector has to resolve a method foo(IBar $Bar, IBaz $Baz), // it injects a service with the identifying type IBar and one with // the indentifying type IBaz. // The identifying type can also be a concrete or abstract class. // The implementing type tells the service factory, which class // is implementing the service. This implementing class has to be an // instanceof the identifying type. $ServiceDefinitions ->add( IdentifyingType::is(Type::fromName('IHttpRequest')), ImplementingType::is(Type::fromName('HttpRequest')) ) ->add( IdentifyingType::is(Type::fromName('IRouter')), ImplementingType::is(Type::fromName('Router')) ) // The identifying type and the implementing type can also be // the same. In this case, you can define the service like this: ->add( IdentifyingType::is(Type::fromName('Users')), ImplementingType::isTheSame() ) // You can define a factory function. In this case, you don't // define the implementing type. ->add( IdentifyingType::is(Type::fromName('Configuration')), FactoryFunction::is(function () { return new Configuration(__DIR__ . '/../config.json'); }) ) // The factory function will also be injected with other // services, if required. ->add( IdentifyingType::is(Type::fromName('Database')), FactoryFunction::is(function (Configuration $Configuration) { return new Database( $Configuration->get('database.host'), $Configuration->get('database.user'), $Configuration->get('database.password') ); }) ) // If no factory function is defined, the service instance is // build by the service factory. If the constructor requires // other services, they are injected automatically in this case. // You cn also define an init function. This is called after a // service has been build (services are build on demand and are // only build once, so you'll get always the same instance of a // service class). ->add( IdentifyingType::is(Type::fromName('Session')), ImplementingType::isTheSame(), InitFunction::is(function (Session $Session) { $Session->start(); }) ) // In the init function you can also use other services. ->add( IdentifyingType::is(Type::fromName('CurrentUserContainer')), ImplementingType::isTheSame(), InitFunction::is(function (CurrentUserContainer $CurrentUserContainer, Session $Session, Users $Users) { if (!$Users->has('currentUserId')) return; $currentUserId = $Session->get('currentUserId'); $CurrentUser = $Users->get(currentUserId); $CurrentUserContainer->setUser($CurrentUser); }) ); }, // Define the main function. // It is called after the services have been defined. Required services // will be injected. So you can define any of your defined services // as parameters. // Two services are defined by the framework: The Loader service (see // above) and the Injector. You can use those as well. function (IRouter $Router, Injetor $Injector) { // The code here is just an example. You don't have to write // routers, controllers, response ojects. You can do anything // here... $Controller = $Router->getControler(); $methodName = $Router->getControllerMethod(); // We want to call the controller now and want the injector to // inject the services into controller, that it needs. This could // be the HttpRequest service, the Session, the Configuration, some // model repositories, possibly a webservice service and so on). // To do this, we have to pass an instance of IInjectionTarget to // the resolve() method of the Injector. $InjectionTarget = new InjectionTargetMethod($Controller, $methodName); $Response = $Injector->resolve($InjectionTarget); // Other implementations of IInjectionTarget are // InjectionTargetStaticMethod, InjectionTargetConstructor, // InjectionTargetInvokeMethod, InjectionTargetClosure and // InjectionTargetFunction. // Finally send the response or load your views, templates, // whatever. $Response->send(); } ); // Now, the application is defined and build. Run it. That's all. $Application->run();
这一切有什么好处?
现在你可以编写很好的代码了。易于测试和松耦合的代码。在一个 JSON API 应用程序中,例如,一个用于注册新用户的控制器方法可能看起来像这样
<?php namespace my\application\controllers; use my\application\lib\http\HttpRequest; use my\application\lib\http\JsonResponseFactory; use my\application\CurrentUserContainer; use my\application\security\SecurityPolice; use my\application\model\users\Users; use my\application\model\users\exceptions\UsernameExists; class User { public function register(IHttpRequest $Request, ISecurityPolicy $SecurityPolicy, IUsers $Users, ICurrentUserContainer $CurrentUserContainer, IJsonResponseFactory $JsonResponseFactory) { $errors = []; $password = $Request->getPostParameter('password'); $passwordErrors = $SecurityPolice->validatePassword($password); if (!empty($passwordErrors)) $errors['password'] = $passwordErrors; // ... do some other validation here ... $username = $Request->getPostParameter('username'); if ($Users->hasUsername($username)) $errors['username'] = ['already-exists']; if (!empty($errors)) return $JsonResponseFactory->createErrorResponse('errors'); try { $NewUser = $Users->add($username, $password, $Request->...); } catch (UsernameExists) { return $JsonResponseFactory->createErrorResponse([ 'username' => ['already-exists'] ]); } $CurrentUserContainer->setUser($NewUser); return $JsonResponseFactory->createSuccessResponse($NewUser->getId()); } }
尽管在这个控制器方法中需要应用程序的许多其他部分,但它完全可测试,并且你不需要传递不透明的注册或容器。实际上,你可以使这个方法成为静态的。或者,例如,你可以将请求、当前用户容器和响应工厂服务放入一个基类 Controller
或 JsonController
中,如果你还有其他类,我也喜欢这种方式编程,这就是我编写这个框架的原因。
请随时联系我,提出问题、解决问题或反馈:bastian.fenske@tueena.org