怎么从0到1开发一个PHP框架-2?

写在前面

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

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

实现缓存

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

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

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

PHP 复制代码
    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);
    }

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

PHP 复制代码
    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;
    }

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

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

设置:

PHP 复制代码
        $cacheObj = Cache::instance('redis');
        $setRes = $cacheObj->setModuleName("user")->set(["id" => 1], ["name" => "ZhangSan"], 1000);
        if ($setRes) {
            echo "设置成功";
        } else {
            echo "设置失败";
        }

获取:

PHP 复制代码
        $cacheObj = Cache::instance('redis');
        $res = $cacheObj->setModuleName("user")->get(["id" => 1], $time, $expire);
        var_dump($res, $time, $expire);

实现日志

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

PHP 复制代码
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));
    }
PHP 复制代码
    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));
    }

应用层调用:

PHP 复制代码
Log::write("这是一条info类型的log", Log::INFO);

实现操作数据库

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

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

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

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

PHP 复制代码
    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);
    }

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

PHP 复制代码
  $dbInstance = Db::getInstance();
  $result = $dbInstance->table('student')->where('SId in (01, 02, 13)')->order("SId DESC")->select();

update:

PHP 复制代码
  $dbInstance = Db::getInstance();
  $dbInstance->table('student');
  $dbInstance->where(['Sid' => '01']);
  $result = $dbInstance->update($data);

数据验证器

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

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

主要实现代码:

PHP 复制代码
    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;
    }

字符传校验部分代码:

PHP 复制代码
    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;
    }

业务层这样调用:

PHP 复制代码
    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());
    }
相关推荐
Lee川14 小时前
深度拆解:基于面向对象思维的“就地编辑”组件全模块解析
javascript·架构
勤劳打代码14 小时前
Flutter 架构日记 — 状态管理
flutter·架构·前端框架
子兮曰19 小时前
后端字段又改了?我撸了一个 BFF 数据适配器,从此再也不怕接口“屎山”!
前端·javascript·架构
卓卓不是桌桌1 天前
如何优雅地处理 iframe 跨域通信?这是我的开源方案
javascript·架构
Qlly1 天前
DDD 架构为什么适合 MCP Server 开发?
人工智能·后端·架构
用户881586910912 天前
AI Agent 协作系统架构设计与实践
架构
鹏北海2 天前
Qiankun 微前端实战踩坑历程
前端·架构
货拉拉技术2 天前
货拉拉海豚平台-大模型推理加速工程化实践
人工智能·后端·架构
RoyLin2 天前
libkrun 深度解析:架构设计、模块实现与 Windows WHPX 后端
架构
CoovallyAIHub3 天前
实时视觉AI智能体框架来了!Vision Agents 狂揽7K Star,延迟低至30ms,YOLO+Gemini实时联动!
算法·架构·github