tueena/core

tueena 框架的核心包:一个结合了依赖注入框架和应用启动器的框架。

安装: 9

依赖者: 0

建议者: 0

安全: 0

星标: 0

关注者: 1

分支: 0

开放问题: 0

类型:tueena-framework-package

dev-master 2015-10-24 22:46 UTC

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

尽管在这个控制器方法中需要应用程序的许多其他部分,但它完全可测试,并且你不需要传递不透明的注册或容器。实际上,你可以使这个方法成为静态的。或者,例如,你可以将请求、当前用户容器和响应工厂服务放入一个基类 ControllerJsonController 中,如果你还有其他类,我也喜欢这种方式编程,这就是我编写这个框架的原因。

请随时联系我,提出问题、解决问题或反馈:bastian.fenske@tueena.org