This commit is contained in:
2026-01-07 16:58:35 +08:00
parent e061dbf714
commit f145a98be6
12 changed files with 1467 additions and 1 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
.idea
vendor
composer.lock

26
.travis.yml Normal file
View File

@@ -0,0 +1,26 @@
dist: xenial
language: php
matrix:
fast_finish: true
include:
- php: 8.0
- php: 8.1
- php: 8.2
- php: 8.3
- php: 8.4
cache:
directories:
- $HOME/.composer/cache
install:
- travis_retry composer update --prefer-dist --no-interaction --prefer-stable --no-suggest
script:
- vendor/bin/phpunit --coverage-clover build/logs/coverage.xml
after_script:
- travis_retry wget https://scrutinizer-ci.com/ocular.phar
- php ocular.phar code-coverage:upload --format=php-clover build/logs/coverage.xml

201
LICENSE Normal file
View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -1,2 +1,97 @@
# think-container
# 修复不能在PHP8.5运行的问题
PHP Container & Facade Manager( Support PSR-11)
===============
## 安装
~~~
composer require topthink/think-container
~~~
## 特性
* 支持PSR-11规范
* 支持依赖注入
* 支持Facade门面
* 支持容器对象绑定
* 支持闭包绑定
* 支持接口绑定
## Container
~~~
// 获取容器实例
$container = \think\Container::getInstance();
// 绑定一个类、闭包、实例、接口实现到容器
$container->bind('cache', '\app\common\Cache');
// 判断是否存在对象实例
$container->has('cache');
// 从容器中获取对象的唯一实例
$container->get('cache');
// 从容器中获取对象,没有则自动实例化
$container->make('cache');
// 删除容器中的对象实例
$container->delete('cache');
// 执行某个方法或者闭包 支持依赖注入
$container->invoke($callable, $vars);
// 执行某个类的实例化 支持依赖注入
$container->invokeClass($class, $vars);
// 静态方法获取容器对象实例 不存在则自动实例化
\think\Container::pull('cache');
~~~
对象化操作
~~~
// 获取容器实例
$container = \think\Container::getInstance();
// 绑定一个类、闭包、实例、接口实现到容器
$container->cache = '\app\common\Cache';
// 判断是否存在对象实例
isset($container->cache);
// 从容器中获取对象的唯一实例
$container->cache;
// 删除容器中的对象实例
unset($container->cache);
~~~
## Facade
定义一个`app\facade\App`类之后,即可以静态方式调用`\think\App`类的动态方法
~~~
<?php
namespace think;
class App
{
public function name(){
return 'app';
}
}
~~~
~~~
<?php
namespace app\facade;
use think\Facade;
class App extends Facade
{
/**
* 获取当前Facade对应类名
* @access protected
* @return string
*/
protected static function getFacadeClass()
{
return '\think\App';
}
}
~~~
然后就可以静态方式调用动态方法了
~~~
use app\facade\App;
echo App::name(); // app
~~~

26
composer.json Normal file
View File

@@ -0,0 +1,26 @@
{
"name": "fixtopthink/think-container",
"description": "PHP Container & Facade Manager",
"license": "Apache-2.0",
"authors": [
{
"name": "liu21st",
"email": "liu21st@gmail.com"
}
],
"require": {
"php": ">=8.0",
"psr/container": "^2.0",
"topthink/think-helper":"^3.1"
},
"require-dev": {
"phpunit/phpunit": "^9.5"
},
"autoload": {
"psr-4": {
"think\\": "src"
},
"files": [
]
}
}

25
phpunit.xml.dist Normal file
View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
beStrictAboutTestsThatDoNotTestAnything="false"
bootstrap="tests/bootstrap.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnError="false"
stopOnFailure="false"
verbose="true"
>
<testsuites>
<testsuite name="ThinkPHP Test Suite">
<directory suffix="Test.php">./tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./src</directory>
</whitelist>
</filter>
</phpunit>

564
src/Container.php Normal file
View File

@@ -0,0 +1,564 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare (strict_types = 1);
namespace think;
use ArrayAccess;
use ArrayIterator;
use Closure;
use Countable;
use InvalidArgumentException;
use IteratorAggregate;
use Psr\Container\ContainerInterface;
use ReflectionClass;
use ReflectionException;
use ReflectionFunction;
use ReflectionFunctionAbstract;
use ReflectionMethod;
use ReflectionNamedType;
use ReflectionParameter;
use think\exception\ClassNotFoundException;
use think\exception\FuncNotFoundException;
use think\helper\Str;
use Throwable;
use Traversable;
/**
* 容器管理类 支持PSR-11
*/
class Container implements ContainerInterface, ArrayAccess, IteratorAggregate, Countable
{
/**
* 容器对象实例
* @var Container|Closure
*/
protected static $instance;
/**
* 容器中的对象实例
* @var array
*/
protected $instances = [];
/**
* 容器绑定标识
* @var array
*/
protected $bind = [];
/**
* 容器回调
* @var array
*/
protected $invokeCallback = [];
/**
* 获取当前容器的实例(单例)
* @access public
* @return static
*/
public static function getInstance()
{
if (is_null(static::$instance)) {
static::$instance = new static;
}
if (static::$instance instanceof Closure) {
return (static::$instance)();
}
return static::$instance;
}
/**
* 设置当前容器的实例
* @access public
* @param object|Closure $instance
* @return void
*/
public static function setInstance($instance): void
{
static::$instance = $instance;
}
/**
* 注册一个容器对象回调
*
* @param string|Closure $abstract
* @param Closure|null $callback
* @return void
*/
public function resolving(string|Closure $abstract, ?Closure $callback = null): void
{
if ($abstract instanceof Closure) {
$this->invokeCallback['*'][] = $abstract;
return;
}
$abstract = $this->getAlias($abstract);
$this->invokeCallback[$abstract][] = $callback;
}
/**
* 获取容器中的对象实例 不存在则创建
* @template T
* @param string|class-string<T> $abstract 类名或者标识
* @param array $vars 变量
* @param bool $newInstance 是否每次创建新的实例
* @return T|object
*/
public static function pull(string $abstract, array $vars = [], bool $newInstance = false)
{
return static::getInstance()->make($abstract, $vars, $newInstance);
}
/**
* 获取容器中的对象实例
* @template T
* @param string|class-string<T> $abstract 类名或者标识
* @return T|object
*/
public function get(string $abstract)
{
if ($this->has($abstract)) {
return $this->make($abstract);
}
throw new ClassNotFoundException('class not exists: ' . $abstract, $abstract);
}
/**
* 绑定一个类、闭包、实例、接口实现到容器
* @access public
* @param string|array $abstract 类标识、接口
* @param mixed $concrete 要绑定的类、闭包或者实例
* @return $this
*/
public function bind(string|array $abstract, $concrete = null)
{
if (is_array($abstract)) {
foreach ($abstract as $key => $val) {
$this->bind($key, $val);
}
} elseif ($concrete instanceof Closure) {
$this->bind[$abstract] = $concrete;
} elseif (is_object($concrete)) {
$this->instance($abstract, $concrete);
} else {
$abstract = $this->getAlias($abstract);
if ($abstract != $concrete) {
$this->bind[$abstract] = $concrete;
}
}
return $this;
}
/**
* 根据别名获取真实类名
* @param string $abstract
* @return string
*/
public function getAlias(string $abstract): string
{
if (isset($this->bind[$abstract])) {
$bind = $this->bind[$abstract];
if (is_string($bind)) {
return $this->getAlias($bind);
}
}
return $abstract;
}
/**
* 绑定一个类实例到容器
* @access public
* @param string $abstract 类名或者标识
* @param object $instance 类的实例
* @return $this
*/
public function instance(string $abstract, $instance)
{
$abstract = $this->getAlias($abstract);
$this->instances[$abstract] = $instance;
return $this;
}
/**
* 判断容器中是否存在类及标识
* @access public
* @param string $abstract 类名或者标识
* @return bool
*/
public function bound(string $abstract): bool
{
return isset($this->bind[$abstract]) || isset($this->instances[$abstract]);
}
/**
* 判断容器中是否存在类及标识
* @access public
* @param string $name 类名或者标识
* @return bool
*/
public function has(string $name): bool
{
return $this->bound($name);
}
/**
* 判断容器中是否存在对象实例
* @access public
* @param string $abstract 类名或者标识
* @return bool
*/
public function exists(string $abstract): bool
{
$abstract = $this->getAlias($abstract);
return isset($this->instances[$abstract]);
}
/**
* 创建类的实例 已经存在则直接获取
* @template T
* @param string|class-string<T> $abstract 类名或者标识
* @param array $vars 变量
* @param bool $newInstance 是否每次创建新的实例
* @return T|object
*/
public function make(string $abstract, array $vars = [], bool $newInstance = false)
{
$abstract = $this->getAlias($abstract);
if (isset($this->instances[$abstract]) && !$newInstance) {
return $this->instances[$abstract];
}
if (isset($this->bind[$abstract]) && $this->bind[$abstract] instanceof Closure) {
$object = $this->invokeFunction($this->bind[$abstract], $vars);
} else {
$object = $this->invokeClass($abstract, $vars);
}
$this->invokeAfter($abstract, $object);
if (!$newInstance) {
$this->instances[$abstract] = $object;
}
return $object;
}
/**
* 删除容器中的对象实例
* @access public
* @param string $name 类名或者标识
* @return void
*/
public function delete(string $name)
{
$name = $this->getAlias($name);
if (isset($this->instances[$name])) {
unset($this->instances[$name]);
}
}
/**
* 执行函数或者闭包方法 支持参数调用
* @access public
* @param string|Closure $function 函数或者闭包
* @param array $vars 参数
* @return mixed
*/
public function invokeFunction(string|Closure $function, array $vars = [])
{
try {
$reflect = new ReflectionFunction($function);
} catch (ReflectionException $e) {
throw new FuncNotFoundException("function not exists: {$function}()", $function, $e);
}
$args = $this->bindParams($reflect, $vars);
return $function(...$args);
}
/**
* 调用反射执行类的方法 支持参数绑定
* @access public
* @param mixed $method 方法
* @param array $vars 参数
* @param bool $accessible 设置是否可访问
* @return mixed
*/
public function invokeMethod($method, array $vars = [], bool $accessible = false)
{
if (is_array($method)) {
[$class, $method] = $method;
$class = is_object($class) ? $class : $this->invokeClass($class);
} else {
// 静态方法
[$class, $method] = explode('::', $method);
}
try {
$reflect = new ReflectionMethod($class, $method);
} catch (ReflectionException $e) {
$class = is_object($class) ? $class::class : $class;
throw new FuncNotFoundException('method not exists: ' . $class . '::' . $method . '()', "{$class}::{$method}", $e);
}
$args = $this->bindParams($reflect, $vars);
if ($accessible&&PHP_VERSION_ID < 80100) {
$reflect->setAccessible($accessible);
}
return $reflect->invokeArgs(is_object($class) ? $class : null, $args);
}
/**
* 调用反射执行类的方法 支持参数绑定
* @access public
* @param object $instance 对象实例
* @param mixed $reflect 反射类
* @param array $vars 参数
* @return mixed
*/
public function invokeReflectMethod($instance, $reflect, array $vars = [])
{
$args = $this->bindParams($reflect, $vars);
return $reflect->invokeArgs($instance, $args);
}
/**
* 调用反射执行callable 支持参数绑定
* @access public
* @param mixed $callable
* @param array $vars 参数
* @param bool $accessible 设置是否可访问
* @return mixed
*/
public function invoke($callable, array $vars = [], bool $accessible = false)
{
if ($callable instanceof Closure) {
return $this->invokeFunction($callable, $vars);
} elseif (is_string($callable) && !str_contains($callable, '::')) {
return $this->invokeFunction($callable, $vars);
} else {
return $this->invokeMethod($callable, $vars, $accessible);
}
}
/**
* 调用反射执行类的实例化 支持依赖注入
* @access public
* @param string $class 类名
* @param array $vars 参数
* @return mixed
*/
public function invokeClass(string $class, array $vars = [])
{
try {
$reflect = new ReflectionClass($class);
} catch (ReflectionException $e) {
throw new ClassNotFoundException('class not exists: ' . $class, $class, $e);
}
if ($reflect->hasMethod('__make')) {
$method = $reflect->getMethod('__make');
if ($method->isPublic() && $method->isStatic()) {
$args = $this->bindParams($method, $vars);
return $method->invokeArgs(null, $args);
}
}
$constructor = $reflect->getConstructor();
$args = $constructor ? $this->bindParams($constructor, $vars) : [];
return $reflect->newInstanceArgs($args);
}
/**
* 执行invokeClass回调
* @access protected
* @param string $class 对象类名
* @param object $object 容器对象实例
* @return void
*/
protected function invokeAfter(string $class, $object): void
{
if (isset($this->invokeCallback['*'])) {
foreach ($this->invokeCallback['*'] as $callback) {
$callback($object, $this);
}
}
if (isset($this->invokeCallback[$class])) {
foreach ($this->invokeCallback[$class] as $callback) {
$callback($object, $this);
}
}
}
/**
* 绑定参数
* @access protected
* @param ReflectionFunctionAbstract $reflect 反射类
* @param array $vars 参数
* @return array
*/
protected function bindParams(ReflectionFunctionAbstract $reflect, array $vars = []): array
{
if ($reflect->getNumberOfParameters() == 0) {
return [];
}
// 判断数组类型 数字数组时按顺序绑定参数
$type = array_is_list($vars) ? 1 : 0;
$params = $reflect->getParameters();
$args = [];
foreach ($params as $param) {
$name = $param->getName();
$lowerName = Str::snake($name);
$reflectionType = $param->getType();
if ($param->isVariadic()) {
return array_merge($args, array_values($vars));
} elseif ($reflectionType && $reflectionType instanceof ReflectionNamedType && $reflectionType->isBuiltin() === false) {
$className = $reflectionType->getName();
if ($className == 'self') {
$className = $param->getDeclaringClass()->getName();
}
$args[] = $this->getObjectParam($className, $vars, $param);
} elseif (1 == $type && !empty($vars)) {
$args[] = array_shift($vars);
} elseif (0 == $type && array_key_exists($name, $vars)) {
$args[] = $vars[$name];
} elseif (0 == $type && array_key_exists($lowerName, $vars)) {
$args[] = $vars[$lowerName];
} elseif ($param->isDefaultValueAvailable()) {
$args[] = $param->getDefaultValue();
} else {
throw new InvalidArgumentException('method param miss:' . $name);
}
}
return $args;
}
/**
* 创建工厂对象实例
* @param string $name 工厂类名
* @param string $namespace 默认命名空间
* @param array $args
* @return mixed
* @deprecated
* @access public
*/
public static function factory(string $name, string $namespace = '', ...$args)
{
$class = str_contains($name, '\\') ? $name : $namespace . ucwords($name);
return Container::getInstance()->invokeClass($class, $args);
}
/**
* 获取对象类型的参数值
* @access protected
* @param string $className 类名
* @param array $vars 参数
* @param ReflectionParameter $param
* @return mixed
*/
protected function getObjectParam(string $className, array &$vars, ReflectionParameter $param)
{
$array = $vars;
$value = array_shift($array);
if ($value instanceof $className) {
$result = $value;
array_shift($vars);
} else {
if ($param->isDefaultValueAvailable()) {
$result = $this->bound($className) ? $this->make($className) : $param->getDefaultValue();
} else {
$result = $this->make($className);
}
}
return $result;
}
public function __set($name, $value)
{
$this->bind($name, $value);
}
public function __get($name)
{
return $this->get($name);
}
public function __isset($name): bool
{
return $this->exists($name);
}
public function __unset($name)
{
$this->delete($name);
}
public function offsetExists(mixed $key): bool
{
return $this->exists($key);
}
public function offsetGet(mixed $key): mixed
{
return $this->make($key);
}
public function offsetSet(mixed $key, mixed $value): void
{
$this->bind($key, $value);
}
public function offsetUnset(mixed $key): void
{
$this->delete($key);
}
//Countable
public function count(): int
{
return count($this->instances);
}
//IteratorAggregate
public function getIterator(): Traversable
{
return new ArrayIterator($this->instances);
}
}

99
src/Facade.php Normal file
View File

@@ -0,0 +1,99 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2023 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
namespace think;
/**
* Facade管理类
*/
class Facade
{
/**
* 始终创建新的对象实例
* @var bool
*/
protected static $alwaysNewInstance;
/**
* 创建Facade实例
* @static
* @access protected
* @param string $class 类名或标识
* @param array $args 变量
* @param bool $newInstance 是否每次创建新的实例
* @return object
*/
protected static function createFacade(string $class = '', array $args = [], bool $newInstance = false)
{
$class = $class ?: static::class;
$facadeClass = static::getFacadeClass();
if ($facadeClass) {
$class = $facadeClass;
}
if (static::$alwaysNewInstance) {
$newInstance = true;
}
return Container::getInstance()->make($class, $args, $newInstance);
}
/**
* 获取当前Facade对应类名
* @access protected
* @return string
*/
protected static function getFacadeClass()
{
}
/**
* 带参数实例化当前Facade类
* @access public
* @return object
*/
public static function instance(...$args)
{
if (__CLASS__ != static::class) {
return self::createFacade('', $args);
}
}
/**
* 调用类的实例
* @access public
* @param string $class 类名或者标识
* @param array|true $args 变量
* @param bool $newInstance 是否每次创建新的实例
* @return object
*/
public static function make(string $class, $args = [], $newInstance = false)
{
if (__CLASS__ != static::class) {
return self::__callStatic('make', func_get_args());
}
if (true === $args) {
// 总是创建新的实例化对象
$newInstance = true;
$args = [];
}
return self::createFacade($class, $args, $newInstance);
}
// 调用实际类的方法
public static function __callStatic($method, $params)
{
return call_user_func_array([static::createFacade(), $method], $params);
}
}

View File

@@ -0,0 +1,36 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
namespace think\exception;
use Psr\Container\NotFoundExceptionInterface;
use RuntimeException;
use Throwable;
class ClassNotFoundException extends RuntimeException implements NotFoundExceptionInterface
{
public function __construct(string $message, protected string $class = '', ?Throwable $previous = null)
{
$this->message = $message;
parent::__construct($message, 0, $previous);
}
/**
* 获取类名
* @access public
* @return string
*/
public function getClass()
{
return $this->class;
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace think\exception;
use Psr\Container\NotFoundExceptionInterface;
use RuntimeException;
use Throwable;
class FuncNotFoundException extends RuntimeException implements NotFoundExceptionInterface
{
public function __construct(string $message, protected string $func = '', ?Throwable $previous = null)
{
$this->message = $message;
parent::__construct($message, 0, $previous);
}
/**
* 获取方法名
* @access public
* @return string
*/
public function getFunc()
{
return $this->func;
}
}

361
tests/ContainerTest.php Normal file
View File

@@ -0,0 +1,361 @@
<?php
namespace think\tests;
use Closure;
use Exception;
use InvalidArgumentException;
use PHPUnit\Framework\TestCase;
use ReflectionMethod;
use stdClass;
use think\Container;
use think\exception\ClassNotFoundException;
use think\exception\FuncNotFoundException;
class Taylor
{
public $name;
public function __construct($name)
{
$this->name = $name;
}
public function some(Container $container)
{
}
protected function protectionFun()
{
return true;
}
public static function test(Container $container)
{
return $container;
}
public static function make(self $taylor)
{
return $taylor;
}
public static function __make()
{
return new self('Taylor');
}
}
class SomeClass
{
public $container;
public $count = 0;
public function __construct(Container $container)
{
$this->container = $container;
}
}
class WithDefaultValues
{
public $container;
public function __construct(?Container $container = null)
{
$this->container = $container;
}
/**
* 有默认值且类存在,但无法注入
*/
public static function classExistsButCannotBeInjected(?Closure $closure = null)
{
return $closure;
}
}
class ContainerTest extends TestCase
{
protected function tearDown(): void
{
Container::setInstance(null);
}
public function testClosureResolution()
{
$container = new Container;
Container::setInstance($container);
$container->bind('name', function () {
return 'Taylor';
});
$this->assertEquals('Taylor', $container->make('name'));
$this->assertEquals('Taylor', Container::pull('name'));
}
public function testGet()
{
$container = new Container;
$this->expectException(ClassNotFoundException::class);
$this->expectExceptionMessage('class not exists: name');
$container->get('name');
$container->bind('name', function () {
return 'Taylor';
});
$this->assertSame('Taylor', $container->get('name'));
}
public function testExist()
{
$container = new Container;
$container->bind('name', function () {
return 'Taylor';
});
$this->assertFalse($container->exists("name"));
$container->make('name');
$this->assertTrue($container->exists('name'));
}
public function testInstance()
{
$container = new Container;
$container->bind('name', function () {
return 'Taylor';
});
$this->assertEquals('Taylor', $container->get('name'));
$container->bind('name2', Taylor::class);
$object = new stdClass();
$this->assertFalse($container->exists('name2'));
$container->instance('name2', $object);
$this->assertTrue($container->exists('name2'));
$this->assertTrue($container->exists(Taylor::class));
$this->assertEquals($object, $container->make(Taylor::class));
unset($container->name1);
$this->assertFalse($container->exists('name1'));
$container->delete('name2');
$this->assertFalse($container->exists('name2'));
foreach ($container as $class => $instance) {
}
}
public function testSelf()
{
$container = new Container;
$taylor = new Taylor('test');
$container->invoke([Taylor::class, 'make'], [$taylor]);
}
public function testBind()
{
$container = new Container;
$object = new stdClass();
$container->bind(['name' => Taylor::class]);
$container->bind('name2', $object);
$container->bind('name3', Taylor::class);
$container->name4 = $object;
$container['name5'] = $object;
$this->assertTrue(isset($container->name4));
$this->assertTrue(isset($container['name5']));
$this->assertInstanceOf(Taylor::class, $container->get('name'));
$this->assertSame($object, $container->get('name2'));
$this->assertSame($object, $container->name4);
$this->assertSame($object, $container['name5']);
$this->assertInstanceOf(Taylor::class, $container->get('name3'));
unset($container['name']);
$this->assertFalse(isset($container['name']));
unset($container->name3);
$this->assertFalse(isset($container->name3));
}
public function testAutoConcreteResolution()
{
$container = new Container;
$taylor = $container->make(Taylor::class);
$this->assertInstanceOf(Taylor::class, $taylor);
$this->assertSame('Taylor', $taylor->name);
}
public function testGetAndSetInstance()
{
$this->assertInstanceOf(Container::class, Container::getInstance());
$object = new stdClass();
Container::setInstance($object);
$this->assertSame($object, Container::getInstance());
Container::setInstance(function () {
return $this;
});
$this->assertSame($this, Container::getInstance());
}
public function testResolving()
{
$container = new Container();
$container->bind(Container::class, $container);
$container->resolving(function (SomeClass $taylor, Container $container) {
$taylor->count++;
});
$container->resolving(SomeClass::class, function (SomeClass $taylor, Container $container) {
$taylor->count++;
});
/** @var SomeClass $someClass */
$someClass = $container->make(SomeClass::class);
$this->assertEquals(2, $someClass->count);
}
public function testInvokeFunctionWithoutMethodThrowsException()
{
$this->expectException(FuncNotFoundException::class);
$this->expectExceptionMessage('function not exists: ContainerTestCallStub()');
$container = new Container();
$container->invokeFunction('ContainerTestCallStub', []);
}
public function testInvokeProtectionMethod()
{
$container = new Container();
$this->assertTrue($container->invokeMethod([Taylor::class, 'protectionFun'], [], true));
}
public function testInvoke()
{
$container = new Container();
Container::setInstance($container);
$container->bind(Container::class, $container);
$stub = $this->createMock(Taylor::class);
$stub->expects($this->once())->method('some')->with($container)->will($this->returnSelf());
$container->invokeMethod([$stub, 'some']);
$this->assertEquals('48', $container->invoke('ord', ['0']));
$this->assertSame($container, $container->invoke(Taylor::class . '::test', []));
$this->assertSame($container, $container->invokeMethod(Taylor::class . '::test'));
$reflect = new ReflectionMethod($container, 'exists');
$this->assertTrue($container->invokeReflectMethod($container, $reflect, [Container::class]));
$this->assertSame($container, $container->invoke(function (Container $container) {
return $container;
}));
$this->assertSame($container, $container->invoke(Taylor::class . '::test'));
$object = $container->invokeClass(SomeClass::class);
$this->assertInstanceOf(SomeClass::class, $object);
$this->assertSame($container, $object->container);
$stdClass = new stdClass();
$container->invoke(function (Container $container, stdClass $stdObject, $key1, $lowKey, $key2 = 'default') use ($stdClass) {
$this->assertEquals('value1', $key1);
$this->assertEquals('default', $key2);
$this->assertEquals('value2', $lowKey);
$this->assertSame($stdClass, $stdObject);
return $container;
}, ['some' => $stdClass, 'key1' => 'value1', 'low_key' => 'value2']);
}
public function testInvokeMethodNotExists()
{
$container = $this->resolveContainer();
$this->expectException(FuncNotFoundException::class);
$container->invokeMethod([SomeClass::class, 'any']);
}
public function testInvokeClassNotExists()
{
$container = new Container();
Container::setInstance($container);
$container->bind(Container::class, $container);
$this->expectExceptionObject(new ClassNotFoundException('class not exists: SomeClass'));
$container->invokeClass('SomeClass');
}
public function testInvokeWithDefaultValues()
{
$container = $this->resolveContainer();
$class = $container->invokeClass(WithDefaultValues::class);
$this->assertSame(null, $class->container);
$container->bind(Container::class, $container);
$bound = $container->invokeClass(WithDefaultValues::class);
$this->assertSame($container, $bound->container);
$this->assertSame(null, $container->invokeMethod(WithDefaultValues::class . '::classExistsButCannotBeInjected'));
}
protected function resolveContainer()
{
$container = new Container();
Container::setInstance($container);
return $container;
}
}

3
tests/bootstrap.php Normal file
View File

@@ -0,0 +1,3 @@
<?php
require __DIR__.'/../vendor/autoload.php';