azjezz/sweet

Sweet ! 一个严格的类型化黑客服务容器和定位器。

维护者

详细信息

github.com/azjezz/sweet

源代码

问题

安装: 1

依赖: 0

建议: 0

安全: 0

星级: 2

关注者: 3

分支: 0

开放问题: 0

语言:Hack

1.21.0 2019-09-09 10:21 UTC

This package is auto-updated.

Last update: 2024-09-19 08:29:59 UTC


README

Sweet, 一个严格的类型化黑客服务容器和定位器 !

Build Status Total Downloads Latest Stable Version License

安装

此包可以使用Composer安装。

$ composer require azjezz/sweet

用法

创建一个容器只是创建一个 ServiceContainer 实例的事情

use namespace Sweet;

$container = new Sweet\ServiceContainer();

// or functionally

$container = Sweet\container();

定义服务

服务是一个对象,作为更大系统的一部分执行某些操作。服务的例子:数据库连接、模板引擎或邮件发送。几乎任何 全局 对象都可以是服务。

服务由返回对象实例的工厂定义

use namespace Sweet;

class SessionStorageFactory implements Sweet\Factory<SessionStorage> {
  public function create(
    Sweet\ServiceContainerInterface $container
  ): SessionStorage {
    return new SessionStorage('SESSION_ID');
  }
}

class SessionFactory implements Sweet\Factory<Session> {
  public function create(
    Sweet\ServiceContainerInterface $container
  ): Session {
    return new Session(
      $container->get(SessionStorage::class)
    );
  }
}

// later :

$container = new Sweet\Container();
$container->add(
  Session::class,
  new SessionFactory(),
);
$container->add(
  SessionStorage::class,
  new SessionStorageFactory(),
);

请注意,工厂可以访问当前容器实例,允许引用其他服务。

您还可以使用 Sweet\factory() 助手从函数创建一个工厂。

$container = new Sweet\Container();

// lambda
$container->add(Session::class, Sweet\factory(($container) ==>
  new Session(
    $container->get(SessionStorage::class)
  )
));

// anonymous function / closure
$container->add(Session::class, Sweet\factory(function($container) {
  return new Session(
    $container->get(SessionStorage::class)
  );
}));

// function
$container->add(Session::class, Sweet\factory(fun('session_factory')));

// static method
$container->add(Session::class, Sweet\factory(class_meth(Factory::class, 'createSession')));

// object method
$container->add(Session::class, Sweet\factory(inst_meth($factory, 'createSession')));

由于对象仅在获取时创建,因此定义的顺序无关紧要。

使用定义的服务也非常简单

// get the session object
$session = $container->get(Session::class);

// the above call is roughly equivalent to the following code:
// $storage = new SessionStorage('SESSION_ID');
// $session = new Session($storage);

共享服务

每次您获取一个服务时,Sweet都会返回该服务的新实例。如果您希望所有调用都返回相同的实例,请将 shared 参数设置为 true 或使用 ->share() 代理。

$container = new Sweet\Container();
$container->add(
  Session::class,
  new SessionFactory(),
  true, // shared
);

/**
 * Proxy to add with shared as true.
 */
$container->share(
  Session::class,
  new SessionFactory(),
);

现在每次调用 $container->get(Session::class); 都将返回会话的相同实例。

定义参数

定义参数可以简化从外部配置容器和存储全局值的过程

newtype SessionCookieName = string;

$container->share(SessionCookieName::class, Sweet\factory(($container) ==> {
  return 'SESSION_ID';
}));

$container->share(SessionStorage::class, Sweet\factory(($container) ==> {
  return new SessionStorage(
    $container->get(SessionCookieName::class)
  );
}));

$container->share(Session::class, Sweet\factory(($container) ==> {
  return new Session(
    $container->get(SessionStorage::class)
  );
}));

$session = $container->get(Session::class);

现在,您可以通过覆盖 SessionCookieName 参数而不是重新定义服务定义来轻松更改cookie名称。

服务提供者

服务提供者可以通过将容器定义组织在一起以及提高大型应用程序的性能(在服务提供者内部注册的定义在获取服务时延迟注册)来提供好处。

要构建服务提供者,只需实现服务提供者接口并定义您要注册的内容即可。

namespace App;

use namespace Sweet;
use namespace HH\Lib\C;

final class SessionServiceProvider extends Sweet\ServiceProvider {
  /**
   * The provided container is a way to let the service
   * container know that a service is provided by this
   * service provider.
   * Every service that is registered via this service
   * provider must have an alias added to this vector
   * or it will be ignored.
   */
  protected Container<string> $provides = vec[
    SessionCookieName::class,
    SessionStorage::class,
    Session::class
  ];

  /**
   * This is where the magic happens, within the method you can
   * access the container and register or retrieve anything
   * that you need to, but remember, every alias registered
   * within this method must be declared in the `$provides` container.
   */
  public function register(
    Sweet\ServiceContainer $container
  ): void {
    $container->share(SessionCookieName::class, Sweet\factory(($container) ==> {
      return 'SESSION_ID';
    }));

    $container->share(SessionStorage::class, Sweet\factory(($container) ==> {
      return new SessionStorage(
        $container->get(SessionCookieName::class)
      );
    }));

    $container->share(Session::class, Sweet\factory(($container) ==> {
      return new Session(
        $container->get(SessionStorage::class)
      );
    }));
  }
}

要将此服务提供者注册到容器中,只需将您的提供者实例传递给 register 方法。

$container = new Sweet\Container();
$container->register(new SessionServiceProvider());

register 方法在请求服务容器的 $provides 容器中的一个别名之前不会调用,因此,当我们想要检索服务提供者提供的项目之一时,它实际上只有在需要时才会注册,这提高了大型应用程序的性能,因为您的依赖关系图会增长。

定义

定义是 ServiceContainer 内部描述您的依赖关系图的方式。每个定义都包含有关如何构建您的服务的信息。

通常,ServiceContainer 将为您处理构建定义所需的一切。当您调用 add 时,会构建一个 Definition 并返回,这意味着任何进一步的交互实际上是与 Definition 而不是 ServiceContainer 进行。

$definition = $container->add(Session::class, $factory);

assert($definition is Sweet\Definition);

如果需要,您还可以扩展定义。

$container->add(Session::class, $factory);

$definition = $container->extend(Session::class);

$definition->inflect(($session) ==> {
  $session->setStorage(
    $container->get(RedisSessionStorage::class)
  );

  return $session;
});

注意:您不能为通过服务提供者注册的服务调用 extend()

也可以手动创建定义并将其传递到容器中。

$definition = new Sweet\Definition($service, $factory, $shared);

$container->addDefinition($definition);

$definition = $container->extend($service);

我们可以告诉定义只解决一次,每次解决时都返回相同的实例。

$container->add($service, $factory)
  ->setShared();

在某些情况下,您可能希望在定义服务之后修改服务定义。您可以使用 inflect 方法来定义在创建服务后立即运行的附加代码。

$definition->inflect(($service) ==> {
  // do something to the service
  return $service;
});

服务定位器

一些服务需要访问多个其他服务。在这些情况下,传统的解决方案是将整个服务容器注入以获取实际所需的服务。然而,这并不可取,因为它赋予了服务对应用程序其他部分的过于宽泛的访问权限,并隐藏了服务的实际依赖关系。

服务定位器 是一种设计模式,它“封装了获取服务的涉及过程 [...] 使用一个称为服务定位器的中央注册表”。这种模式通常被劝阻,但在这些情况下很有用,并且比注入整个服务容器要好得多。

考虑一个 RouteDispatcher 类,它映射路由及其处理程序。这个类一次只调度一个路由处理程序,因此实例化所有这些处理程序是没有用的。

首先,使用 newtype 定义一个服务定位器服务,并将所有请求处理程序添加到其中。

newtype RequestHandlersLocator = Sweet\ServiceLocator;

$container->add(RequestHandlersLocator::class, Sweet\factory(($container) ==> {
  $handlers = vec[
    HomeHandler::class,
    PostHandler::class,
    CommentHandler::class,
    LoginHandler::class,
    RegistrationHandler::class,
    ProfileHandler::class,
    SettingsHandler::class,
  ];
  return new Sweet\ServiceLocator($handlers, $container);
}));

然后,将服务定位器注入到路由调度器的服务定义中。

$container->add(RouteDispatcher::class, Sweet\factory(($container) ==> {
  return new RouteDispatcher(
    $container->get(RequestHandlersLocator::class)
  );
}));

注入的服务定位器是 Sweet\ServiceLocator 的实例。这个类实现了 Sweet\ServiceContainerInterface,该接口包括 has()get() 方法,用于从定位器检查和获取服务。

use namespace HH\Lib\Str;
use type Sweet\ServiceContainerInterface;
use type Nuxed\Contract\Http\Message\ResponseInterface;
use type Nuxed\Contract\Http\Message\ServerRequestInterface;
use type Nuxed\Contract\Http\Server\RequestHandlerInterface;

final class RouteDispatcher {
  public function __construct(
    private ServiceContainerInterface $locator
  ) {}

  public function dispatch<T as RequestHandlerInterface>(
    classname<T> $handler,
    ServerRequestInterface $request,
  ): ResponseInterface {
    if (!$this->locator->has($handler)) {
      throw new NotFoundException(Str\format(
        'Handler (%s) is not registered in the container.'
      ), $handler);
    }

    $handler = $this->locator->get($handler);
    return $handler->handle($request);
  }
}

安全漏洞

如果您在 Sweet 中发现安全漏洞,请通过 azjezz@protonmail.com 向 Saif Eddin Gmati 发送电子邮件。

许可证

Sweet 项目是开源软件,许可协议为 MIT 许可证。