怎么从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());
    }
相关推荐
洛卡卡了1 小时前
从单层到 MVC,再到 DDD:架构演进的思考与实践
架构·mvc
乌恩大侠2 小时前
O-RAN Fronthual CU/Sync/Mgmt 平面和协议栈
5g·平面·fpga开发·架构
木宛哥9 小时前
代码背后的智慧:20条编程感悟
java·后端·架构
58沈剑16 小时前
80后聊架构:架构设计中两个重要指标,延时与吞吐量(Latency vs Throughput) | 架构师之路...
架构
想进大厂的小王18 小时前
项目架构介绍以及Spring cloud、redis、mq 等组件的基本认识
redis·分布式·后端·spring cloud·微服务·架构
阿伟*rui19 小时前
认识微服务,微服务的拆分,服务治理(nacos注册中心,远程调用)
微服务·架构·firefox
ZHOU西口20 小时前
微服务实战系列之玩转Docker(十八)
分布式·docker·云原生·架构·数据安全·etcd·rbac
deephub1 天前
Tokenformer:基于参数标记化的高效可扩展Transformer架构
人工智能·python·深度学习·架构·transformer
架构师那点事儿1 天前
golang 用unsafe 无所畏惧,但使用不得到会panic
架构·go·掘金技术征文
W Y1 天前
【架构-37】Spark和Flink
架构·flink·spark