• 怎么从0到1实现一个PHP框架?


    写在前面

    本人开发的框架在2021年年初开发完成,后面没有再做过任何维护和修改。是仅供大家参考交流的学习项目,请勿使用在生产环境,也勿用作商业用途。

    框架地址:
    https://github.com/yijiebaiyi/fast_framework

    整体思路

    开发一款web框架,首先要考虑这个框架的整体运行架构,然后具体到那些功能的扩展。那么我开发框架的时候想的是,精简为主,实用为主。主要功能需要包括入口文件、路由解析、异常处理、日志记录、ORM、缓存、类依赖注入。

    入口文件

    入口文件需要定义全局变量,主要是核心框架文件的所在路径,然后,通过include_once引入框架核心类文件,初始化框架进行初始化操作。

    init();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    应用核心类

    应用核心类主要是用来注册类的自动加载、加载环境变量文件、注册错误异常以及注册路由。下面是应用初始化init方法。

        public function init()
        {
            if (false === $this->isInit) {
                define("DOCUMENT_ROOT", $_SERVER["DOCUMENT_ROOT"]);
                define("ROOT_PATH", $_SERVER["DOCUMENT_ROOT"]);
                define("RUNTIME_PATH", $_SERVER["DOCUMENT_ROOT"] . DIRECTORY_SEPARATOR . "runtime");
                define("APP_PATH", $_SERVER["DOCUMENT_ROOT"]);
    
                // 注册自动加载
                require_once FAST_PATH . DIRECTORY_SEPARATOR . "Autoload.php";
                (new Autoload())->init();
    
                // 注册配置
                (new Config())->init();
    
                // 加载env
                (new Env())->init();
    
                // 注册错误和异常
                (new Exception())->init();
                (new Error())->init();
                (new Shutdown())->init();
    
                // 检验运行环境
                $this->validateEnv();
    
                // 注册路由
                (new Route())->init();
    
                $this->isInit = true;
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    上面初始化的方法中,我们需要先判断框架是否已经初始化,如果已经初始化则不需要再进行操作了。init方法中所涉及到的类都在框架核心文件根目录下面,需要注意的是,一定要先注册自动加载,不然使用new 关键字生成对象就会报错。下面是自动加载类的自动加载方法。

        public function init()
        {
            if (false === $this->isInit) {
                spl_autoload_register(array($this, 'autoload'));
    
                $this->isInit = true;
            }
        }
    
        /**
         * @var array 类加载次
         */
        private static array $loadedClassNum = [];
    
        /**
         * 自动加载
         * @param $name
         * @throws Exception
         */
        public static function autoload($name): void
        {
            if (trim($name) == '') {
                throw new Exception("No class for loading");
            }
    
            $file = self::formatClassName($name);
    
            if (isset(self::$loadedClassNum[$file])) {
                self::$loadedClassNum[$file]++;
                return;
            }
            if (!$file || !is_file($file)) {
                return;
            }
            // 导入文件
            include $file;
    
            if (empty(self::$loadedClassNum[$file])) {
                self::$loadedClassNum[$file] = 1;
            }
        }
    
        /**
         * 返回全路径
         * @param $className
         * @return string
         */
        private static function formatClassName($className): string
        {
            return $_SERVER['DOCUMENT_ROOT'] . DIRECTORY_SEPARATOR . $className . '.php';
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    使用PHP提供的spl_autoload_register自动加载器函数,注册autoload方法实现自动加载,可以看到我们自动加载的类必须都在项目根目录下才可以实现。这是一个简单的约定。

    加载配置

    我们知道php使用include 导入文件是可以获取到文件的返回值的(如果有的话),所以使用php文件返回一个数组来实现项目的配置文件,框架里面支持默认的config.php文件,以及额外用户可以自定义的配置:extra.php。这个也是我们约定好的。

    配置文件示例代码config.php:

     [
            "default" => "redis",
            "redis" => [
                "master" => [
                    "pconnect" => false,
                    "host" => "localhost",
                    "port" => 6379,
                    "timeout" => 0,
                ],
            ],
        ],
        "Log" => [
            "default" => "file",
            "file" => [
                "path" => RUNTIME_PATH
            ],
        ]
    ];
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    引入配置文件的关键代码:

        /**
         * 加载配置
         * @param $filename
         */
        private static function addConfig($filename): void
        {
            $configArr = include_once($filename);
            if (is_array($configArr)) {
                self::$configs = Arr::arrayMergeRecursiveUnique(self::$configs, $configArr);
            }
        }
    
        /**
         * 导入配置
         * @param $paths
         */
        private static function importConfig($paths): void
        {
            foreach ($paths as $path) {
                self::addConfig($path);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    加载环境变量

    环境变量文件,我们默认的就是项目根目录的.env文件。.env文件配置项是标准的*.ini类型配置文件的书写方式,且.env文件里面的配置项不区分大小写,小写配置项最终会被转化成大写。.env文件的加载使用php的函数parse_ini_file来实现:

        /**
         * 加载环境变量定义文件
         * @param string $file 环境变量定义文件
         * @return void
         */
        public static function load(string $file): void
        {
            $env = parse_ini_file($file, true) ?: [];
            static::set($env);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    框架支持环境变量的写入、读取和检测。

    错误和异常

    异常信息抓取到之后,我们将他格式化处理,主要记录异常码、异常文件和所在行号。然后将异常写入日志。(注意,如果是生产模式,需要关闭错误显示)

        public static function handler($exception)
        {
            // 设置http状态码,发送header
            if (in_array($exception->getCode(), array_keys(Http::$httpStatus))) {
                self::$httpCode = $exception->getCode();
            } else {
                self::$httpCode = 500;
            }
            Http::sendHeader(self::$httpCode);
    
            // 异常信息格式化输出
            $echoExceptionString = "message:  {$exception->getMessage()}
    " . "code: {$exception->getCode()}
    " . "file: {$exception->getFile()}
    " . "line: {$exception->getLine()}
    "; $serverVarDump = Str::dump(false, $_SERVER); $postVarDump = Str::dump(false, $_POST); $filesVarDump = Str::dump(false, $_FILES); $cookieVarDump = Str::dump(false, $_COOKIE); $logExceptionString = "message: {$exception->getMessage()}" . PHP_EOL . "code: {$exception->getCode()}" . PHP_EOL . "file: {$exception->getFile()}" . PHP_EOL . "line: {$exception->getLine()}" . PHP_EOL . "\$_SERVER: {$serverVarDump}" . PHP_EOL . "\$_POST: {$postVarDump}" . PHP_EOL . "\$_COOKIE: {$cookieVarDump}" . PHP_EOL . "\$_FILES: {$filesVarDump}"; Log::write($logExceptionString, Log::ERROR); // debug模式将错误输出 if (static::isDebugging()) { if (self::$isJson) { echo Json::encode(["message" => $exception->getMessage(), "code" => 0]); App::_end(); } else { echo $echoExceptionString; } } }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    路由分发

    路由的实现思路是:我们根据请求的地址,截取到请求的路径信息(根据PHP全局变量$_SERVER[‘PATH_INFO’]获取),根据路径信息的格式,定位到某个控制器类的某个方法,然后将其触发。实现代码:

        public function distribute()
        {
            // 解析path_info
            if (isset($_SERVER['PATH_INFO'])) {
                $url = explode('/', trim($_SERVER['PATH_INFO'], "/"));
                if (count($url) < 3) {
                    $url = array_pad($url, 3, "index");
                }
            } else {
                $url = array_pad([], 3, "index");
            }
    
            // 获取类名和方法名
            $className = self::formatClassName($url);
            $actionName = self::formatActionName($url);
    
            if (!class_exists($className)) {
                throw new Exception("the controller is not exist: {$className}", 404);
            }
    
            $class = new $className();
    
            if (!is_callable([$class, $actionName])) {
                throw new Exception("the action is not exist: {$className} -> {$actionName}", 404);
            }
    
            if (!$class instanceof Controller) {
                throw new Exception("the controller not belongs to fast\\Controller: {$className}", 403);
            }
    
            // 将请求分发
            $class->$actionName();
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    实现缓存

    框架中的缓存、日志、ORM都是使用适配器模式。即定义一个抽象类,抽象类中定义若干抽象方法。这样的话,继承了抽象类的方法必须要实现这些抽象方法。我们就可以通过统一的入口去根据配置去调用对应的适配器类了。

    其中缓存适配了Redis、Memcache以及Memcached三种。开发者可以在config.php配置文件中自行配置。

    缓存主要实现了将数据写入缓存和获取缓存数据两个方法,我们以redis为例,redis缓存主要是使用redis字符串存储结构,使用set和get方法来实现。

        public function get($key, &$time = null, &$expire = null)
        {
            $_key = $this->makeKey($key);
            $res = $this->slaveObj->get($_key);
            if (is_null($res) || false === $res) {
                return null;
            }
    
            $res = unserialize($res);
            if ($res && isset($res['value'])) {
                $time = $res['time'];
                $expire = $res['expire'];
                return $res['value'];
            }
    
            return null;
        }
    
        public function set($key, $value = null, $expire = 3600): bool
        {
            return $this->masterObj->set($this->makeKey($key), serialize($this->makeValue($value, $expire)), $expire);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    前面的代码只是适配器的实现,那么我们怎么调用适配器类中的方法呢。我这边想到的是,在框架核心代码根目录创建一个缓存文件类,实现一个单例,通过配置来读取我们要使用什么类型的缓存(即使用哪个适配器类),配置中配置项是缓存适配器类的类名称,读取到了我们就加载他。具体实现代码:

        public static function instance($type = "default"): CacheDriver
        {
            if ($type === "default") {
                $_type = Config::get("Cache.default");
            } else {
                $_type = $type;
            }
    
            if (!$_type) {
                throw new Exception("The type can not be set to empty!");
            }
    
            if (!isset(self::$_instance[$_type])) {
                $conf = Config::get("Cache.{$_type}");
    
                if (empty($conf)) {
                    throw new Exception("The '{$_type}' type cache config does not exists!");
                }
    
                $class = self::getNamespace() . "\\" . ucfirst($_type);
                $obj = new $class();
    
                if (!$obj instanceof CacheDriver) {
                    throw new Exception("The '{$class}' not instanceof CacheDriver!");
                }
    
                $obj->init($conf);
                self::$_instance[$_type] = $obj;
    
            } else {
                $obj = self::$_instance[$_type];
            }
    
            return $obj;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    注:日志以及ORM的实现方法和缓存的实现类似,也是通过实现一个适配器,然后通过加载配置中定义的适配器类来加载。

    实现完了之后我们测试一下:

    设置:

            $cacheObj = Cache::instance('redis');
            $setRes = $cacheObj->setModuleName("user")->set(["id" => 1], ["name" => "ZhangSan"], 1000);
            if ($setRes) {
                echo "设置成功";
            } else {
                echo "设置失败";
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    获取:

            $cacheObj = Cache::instance('redis');
            $res = $cacheObj->setModuleName("user")->get(["id" => 1], $time, $expire);
            var_dump($res, $time, $expire);
    
    • 1
    • 2
    • 3

    实现日志

    日志的实现比较简单,主要值实现了日志的写入功能,通过php函数file_put_contents实现写入文件。当然也可以使用别的方法来实现。
    相关代码:

    public function write(string $message, string $type)
        {
            if (empty($message)) {
                trigger_error('$message dose not empty! ');
    
                return false;
            }
    
            if (empty($type)) {
                trigger_error('$type dose not empty! ');
    
                return false;
            }
    
            $path = APP_PATH . DIRECTORY_SEPARATOR . 'runtime' . DIRECTORY_SEPARATOR . 'logs' . DIRECTORY_SEPARATOR . $type . '/' . date('Ym/d') . '.log';
    
            $mark = "\n\n===========================================================================\n";
            $mark .= 'time:' . date('Y/m/d H:i:s') . "\n";
    
            return \fast\util\File::write($mark . $message, $path, (FILE_APPEND | LOCK_EX));
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
        public static function write($content, $path, $flags = 0)
        {
            $path = trim($path);
            if (empty($path)) {
                trigger_error('$path must to be set!');
    
                return false;
            }
    
            $dir = dirname($path);
            if (!self::exists($dir)) {
                if (false == self::mkdir($dir)) {
                    trigger_error('filesystem is not writable: ' . $dir);
    
                    return false;
                }
            }
            $path = str_replace("//", "/", $path);
    
            return file_put_contents($path, $content, ((empty($flags)) ? (LOCK_EX) : $flags));
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    应用层调用:

    Log::write("这是一条info类型的log", Log::INFO);
    
    • 1

    实现操作数据库

    数据库目前只实现了Mysql,如果需要支持别的数据库,只需要新增适配器即可。区别于缓存的实现,数据库使用接口interface作为适配器的约定。

    mysql的实现主要依赖mysqli库,它对mysql库做了优化,防注入更完善一些。CURD的具体实现思路是,先获取要处理的数据,最终拼接成sql来执行。

    注:链式调用通过方法返回$this来实现

    简单看一下select查询的实现:

        public function select()
        {
            $this->checkMysqlOperate("table_empty");
            empty($this->_fields) && $this->_fields = "*";
    
            $sql = "SELECT {$this->_fields} FROM {$this->_table}";
            !empty($this->_where) && $sql .= " WHERE {$this->_where}";
            !empty($this->_order) && $sql .= " ORDER BY {$this->_order}";
            !empty($this->_group) && $sql .= " GROUP BY {$this->_group}";
            !empty($this->_limit) && $sql .= " LIMIT {$this->_offset}, {$this->_limit}";
    
            $this->_sql = $sql;
            $mysqliResult = mysqli_query($this->_connection, $this->_sql);
            if (false === $mysqliResult) {
                $this->_error = mysqli_error($this->_connection);
                return false;
            }
            return mysqli_fetch_all($mysqliResult, MYSQLI_ASSOC);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    我们在应用层调用一下select:

      $dbInstance = Db::getInstance();
      $result = $dbInstance->table('student')->where('SId in (01, 02, 13)')->order("SId DESC")->select();
    
    • 1
    • 2

    update:

      $dbInstance = Db::getInstance();
      $dbInstance->table('student');
      $dbInstance->where(['Sid' => '01']);
      $result = $dbInstance->update($data);
    
    • 1
    • 2
    • 3
    • 4

    数据验证器

    数据验证器主要是用来验证数据是否符合我们的规范,可以用来验证表单数据,也可以用来验证业务数据。

    主要实现是列举所有的验证规则依次校验,主要有这些规则校验:必传校验、类型校验、字符校验、数字校验、正则校验。

    主要实现代码:

        public function check(array $data, array $rules): self
        {
            foreach ($rules as $rule => $message) {
                $dataRule = explode(".", $rule);
                if (count($dataRule) < 2) {
                    continue;
                }
    
                // 必传校验
                if ($dataRule[1] == "required" && !isset($data[$dataRule[0]])) {
                    array_push($this->errors, $message);
                    continue;
                }
    
                if (!isset($data[$dataRule[0]])) {
                    continue;
                }
    
                // 类型校验
                if (in_array($dataRule[1], $this->typeCheckName)) {
                    if (false === self::typeCheck(strval($dataRule[1]), $data[$dataRule[0]])) {
                        array_push($this->errors, $message);
                        continue;
                    }
                }
    
                // 字符校验
                if (in_array($dataRule[1], $this->stringCheckName) && isset($dataRule[2])) {
                    if (false === self::stringCheck(strval($dataRule[1]), $dataRule[2], $data[$dataRule[0]])) {
                        array_push($this->errors, $message);
                        continue;
                    }
                }
    
                // 数字校验
                if (in_array($dataRule[1], $this->operatorCheckName) && isset($dataRule[2])) {
                    if (false === self::operatorCheck(strval($dataRule[1]), $dataRule[2], $data[$dataRule[0]])) {
                        array_push($this->errors, $message);
                        continue;
                    }
                }
    
                // 正则校验
                if (in_array($dataRule[1], array_keys($this->pregCheckRules))) {
                    if (false === self::pregCheck(strval($dataRule[1]), $data[$dataRule[0]])) {
                        array_push($this->errors, $message);
                        continue;
                    }
                }
            }
            return $this;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    字符传校验部分代码:

        public function stringCheck(string $rule, $value, $dataValue): bool
        {
            $flag = true;
            switch ($rule) {
                case "max":
                    strlen($dataValue) > $value && $flag = false;
                    break;
                case "min":
                    strlen($dataValue) < $value && $flag = false;
                    break;
                case "length":
                    strlen($dataValue) != $value && $flag = false;
                    break;
                case "in":
                    $value = explode(",", $value);
                    !in_array($dataValue, $value) && $flag = false;
                    break;
                case "notIn":
                    $value = explode(",", $value);
                    in_array($dataValue, $value) && $flag = false;
                    break;
            }
            return $flag;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    业务层这样调用:

        public function testValidate()
        {
            $validate = new ValidateData();
            $data = [
                "age" => 17,
                "weight" => "50公斤",
                "name" => "ZhangSan",
                "country" => "这里是中国abc",
                "sex" => "未知",
                "mobile" => "11098186452",
            ];
    
            $rules = [
                "age.required" => "请输入年龄",
                "email.required" => "请输入邮箱",
                "age.gt.18" => "年龄必须大于18",
                "weight.float" => "体重必须为浮点数",
                "name.max.6" => "姓名最大长度为6",
                "country.alphaNum" => "国家必须为数字或者字母",
                "sex.in.男,女" => "性别必须是男或者女",
                "mobile.mobile" => "手机号码不合法",
            ];
            $validate->check($data, $rules);
    
            var_dump($validate->getErrors());
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    实现容器依赖注入

    首先我们先了解概念。框架中的容器指的是什么?什么是依赖注入?

    容器(当前所指)是一个用于管理和存储应用程序中各种对象的工具。它允许你注册、创建和解析对象,以及管理它们之间的依赖关系。当前框架中的容器通常使用关联数组来存储对象和服务。

    依赖注入是一种设计模式,它允许你将一个对象的依赖关系传递给它,而不是在对象内部直接创建或管理依赖关系。
    这可以使代码更加可测试、可维护和可扩展,因为它将对象的依赖性解耦,并使它们更容易替换和修改。
    依赖注入通常通过构造函数注入、方法注入或属性注入来实现。
    在当前框架中,依赖注入和容器一起使用,容器负责实例化和解析对象,并自动注入它们的依赖关系。

    那么如何实现呢?通过php的反射,来获取类的相关信息来解决依赖。

    我们从容器中拿一个服务对象,如果没有拿到,则需要创建。创建的时候通过下面几步我们来解决依赖。

    1. 根据类名获取目标类(实际是反射类)
    $reflection = new \ReflectionClass($className)
    
    • 1
    1. 进一步获取目标类的构造方法(实际是构造方法类)
    $reflection->getConstructor()
    
    • 1
    1. 获取构造方法所需参数类(是一个数组)
    $constructorParameters = $constructor->getParameters()
    
    • 1
    1. 循环所需参数,如果参数没有默认值,则是一个服务对象,我们继续从容器中获取,直到解决所有的依赖。
    foreach ($constructorParameters as $param) {
        if (version_compare(PHP_VERSION, '5.6.0', '>=') && $param->isVariadic()) {
            break;
        } elseif ($param->isDefaultValueAvailable()) {
            $dependencies[] = $param->getDefaultValue();
        } else {
            $c = $param->getClass();
            $dependencies[] = $this->get($c->getName(), $this->_params[$c->getName()] ?? []);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    注:请避免出现循环嵌套,否则会出现未知问题

    创建的完整代码:

        public function build(string $className, array $params = []): ?object
        {
            if (isset($this->_reflections[$className])) {
                $reflection = $this->_reflections[$className];
            } else {
                try {
                    $reflection = new \ReflectionClass($className);
                } catch (ReflectionException $exception) {
                    throw new Exception("Failed to reflect class " . $className . ", error: " . $exception->getMessage());
                }
                $this->_reflections[$className] = $reflection;
            }
    
            if (!$reflection->isInstantiable()) {
                throw new Exception("Is not instantiable:" . $reflection->name);
            }
    
            $dependencies = [];
            $constructor = $reflection->getConstructor();
            if ($constructor !== null) {
                $constructorParameters = $constructor->getParameters();
                foreach ($constructorParameters as $param) {
                    if (version_compare(PHP_VERSION, '5.6.0', '>=') && $param->isVariadic()) {
                        break;
                    } elseif ($param->isDefaultValueAvailable()) {
                        $dependencies[] = $param->getDefaultValue();
                    } else {
                        $c = $param->getClass();
                        $dependencies[] = $this->get($c->getName(), $this->_params[$c->getName()] ?? []);
                    }
                }
            }
    
            $this->_dependencies[$className] = Arr::arrayMergeBase($dependencies, $params);
            $object = $reflection->newInstanceArgs($this->_dependencies[$className]);
            $this->_objects[$className] = $object;
            return $object;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    解决完依赖,我们就把改服务存入容器中。

    业务层调用:

        $container = new Container();
        $container->set("app\service\Group", [123]);
        $container->set("app\service\User");
        $container->set("app\service\UserList");
        $group = $container->get("app\service\Group");
        $userList = $container->get("app\service\UserList");
        $group->getA();
        $userList->getUserList();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    Group.php:

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    User.php:

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    UserList.php:

尾声

至此,这款简易的php框架的实现过程就介绍完了。更多详细的内容请异步:

https://github.com/yijiebaiyi/fast_framework

这里有详细的代码示例和完整的实现过程。

  • 相关阅读:
    SSM之Spring注解式缓存Redis
    VTK——angleWidget的3D转换
    2023年浙大MBA项目的后备考阶段三大策略:你永远要相信光的力量
    Delphi XE E2251 Ambiguous overloaded call to ‘StrPas‘错误处理
    C++内存管理:其六、静态allocator的实现
    记录一个删库跑路的技巧(如何快速删除数据库下面的所有表)
    安装HookZz 在 Monkey 中安装 HookZz
    springboot vue mysql的在线竞拍拍卖系统
    深入思考redis面经
    new map日常使用
  • 原文地址:https://blog.csdn.net/qq_39545346/article/details/132641542