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

这篇具有很好参考价值的文章主要介绍了怎么从0到1实现一个PHP框架?。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

写在前面

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

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

整体思路

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

入口文件

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

<?php

define("FAST_PATH", $_SERVER["DOCUMENT_ROOT"] . DIRECTORY_SEPARATOR . "fast");

// 初始化
include_once FAST_PATH . DIRECTORY_SEPARATOR . "App.php";
(new \fast\App())->init();

应用核心类

应用核心类主要是用来注册类的自动加载、加载环境变量文件、注册错误异常以及注册路由。下面是应用初始化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;
        }
    }

上面初始化的方法中,我们需要先判断框架是否已经初始化,如果已经初始化则不需要再进行操作了。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';
    }

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

加载配置

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

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

<?php

return [
    "Cache" => [
        "default" => "redis",
        "redis" => [
            "master" => [
                "pconnect" => false,
                "host" => "localhost",
                "port" => 6379,
                "timeout" => 0,
            ],
        ],
    ],
    "Log" => [
        "default" => "file",
        "file" => [
            "path" => RUNTIME_PATH
        ],
    ]
];

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

    /**
     * 加载配置
     * @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);
        }
    }

加载环境变量

环境变量文件,我们默认的就是项目根目录的.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);
    }

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

错误和异常

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

    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 = "<b>message</b>:  {$exception->getMessage()}<br/>" .
            "<b>code</b>:  {$exception->getCode()}<br/>" .
            "<b>file</b>:  {$exception->getFile()}<br/>" .
            "<b>line</b>:  {$exception->getLine()}<br/>";

        $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;
            }
        }
    }

路由分发

路由的实现思路是:我们根据请求的地址,截取到请求的路径信息(根据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();
    }

实现缓存

框架中的缓存、日志、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);
    }

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

    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的实现方法和缓存的实现类似,也是通过实现一个适配器,然后通过加载配置中定义的适配器类来加载。

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

设置:

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

获取:

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

实现日志

日志的实现比较简单,主要值实现了日志的写入功能,通过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));
    }
    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));
    }

应用层调用:

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

实现操作数据库

数据库目前只实现了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);
    }

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

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

update:

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

数据验证器

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

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

主要实现代码:

    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;
    }

字符传校验部分代码:

    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;
    }

业务层这样调用:

    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());
    }

实现容器依赖注入

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

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

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

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

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

  1. 根据类名获取目标类(实际是反射类)
$reflection = new \ReflectionClass($className)
  1. 进一步获取目标类的构造方法(实际是构造方法类)
$reflection->getConstructor()
  1. 获取构造方法所需参数类(是一个数组)
$constructorParameters = $constructor->getParameters()
  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()] ?? []);
    }
}

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

创建的完整代码:

    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;
    }
}

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

业务层调用:

    $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();

Group.php:

<?php
namespace app\service;

class Group
{
    public static $a = 0;

    function __construct($a =1)
    {
        static::$a = $a;
    }

    public function getA()
    {
        echo self::$a;
    }
}

User.php:

<?php
namespace app\service;

class User
{
    public function __construct(Group $group)
    {

    }

    function user()
    {

    }
}

UserList.php:

<?php
namespace app\service;

class UserList
{
    public function __construct(User $user)
    {

    }

    public function getUserList()
    {
        echo "this is the user-list";
    }
}

尾声

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

https://github.com/yijiebaiyi/fast_framework

这里有详细的代码示例和完整的实现过程。文章来源地址https://www.toymoban.com/news/detail-705226.html

到了这里,关于怎么从0到1实现一个PHP框架?的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 用php语言写一个chatgpt3.5模型的例子

    当然可以!使用PHP语言调用OpenAI API与ChatGPT-3.5模型进行交互。首先,确保你已经安装了PHP 7.2或更新版本,并具备可用的OpenAI API密钥。 下面是一个基本的PHP示例,展示了如何使用OpenAI API与ChatGPT-3.5模型进行对话: 以上是一个基本的例子,你可以根据自己的需求进行修改和扩展

    2024年02月13日
    浏览(39)
  • 【周末闲谈】“PHP是最好的语言”这个梗是怎么来的?

    个人主页:【😊个人主页】 系列专栏:【❤️周末闲谈】 ✨第一周 二进制VS三进制 ✨第二周 文心一言,模仿还是超越? ✨第二周 畅想AR 突然发现已经很久没有更新周末闲谈这个专栏了,不能在摆烂下去了。ψ(`∇´)ψ “PHP是最好的语言”,经常被用来嘲讽程序员技术较差

    2024年02月08日
    浏览(86)
  • php怎么实现时间差

    php实现时间差的方法:1、通过strtotime函数将两个日期转换为时间戳;2、通过“$enddate-$startdate”公式将两个时间戳相减;3、将时间差“$diff_seconds”除以86400,并使用“floor()”函数向下舍入为最接近的整数即可获得相差天数。 php怎么实现时间差? php求两个给定日期的时间差:

    2024年02月08日
    浏览(87)
  • PHP接口自动化测试框架实现

    我们来看一个简单的PHP实现的超简单的接口。 说明: 首先,它是一个POST接口。它需要两个参数:raid 和 mid。 然后,判断raid 和 mid 是否为空,为空返回:参数错误。 最后,调用 enlist_model 模型,通过  get_enlist_by_raid_mid 方法查询是否为空,如果不为空返回:你已经报过名了。

    2024年02月14日
    浏览(55)
  • 6.php开发-个人博客项目&Tp框架&路由访问&安全写法&历史漏洞

    目录 知识点 php框架——TP URL访问 Index.php-放在控制器目录下 ​编辑 Test.php--要继承一下 带参数的—————— 加入数据库代码 --不过滤 --自己写过滤 --手册(官方)的过滤 用TP框架找漏洞: 如何判断网站是thinkphp? 黑盒: 白盒: php总结 ​ 1-基于TP框架入门安装搭建使用

    2024年01月25日
    浏览(54)
  • 推荐一个日历转换开源工具库,支持C#、Java、PHP等主流的语言

    日历对我们来说,最熟悉的就是阳历和农历,在中国每年都有固定的节日、节气、中国特有传统节日,有些节日是固定的,但是节气这些都需要我们经过一定规则换算出来。 所以,今天给大家推荐一个开源库,它支持阳历、阴历、佛历和道历的日历转换,可以满足我们的所有

    2024年02月06日
    浏览(72)
  • 短视频矩阵系统源码开发搭建技术解析-PHP语言

    一、系统架构 整个短视频矩阵系统大概分为以下几个模块: 1.多平台账号管理 支持抖音、快手、小红书、西瓜、头条视频号等多平台账号管理。 2.视频管理模块 支持视频批量上传、批量剪辑、文字转语音,特效等功能配置 3.推荐算法模块 推荐算法模块主要用来推荐用户感兴

    2024年02月07日
    浏览(67)
  • 第27天:安全开发-PHP应用&TP框架&路由访问&对象操作&内置过滤绕过&核心漏洞

    1.TP框架-开发-配置架构路由MVC模型 参考:https://www.kancloud.cn/manual/thinkphp5_1 配置架构-导入使用 路由访问-URL访问 数据库操作-应用对象 文件上传操作-应用对象 前端页面渲染-MVC模型 1.TP框架-安全-不安全写法版本过滤绕过 1.内置代码写法 不合要求的代码写法-ThinkPHP5-自写 2.框架

    2024年04月25日
    浏览(61)
  • 浅谈PHP框架中类成员方法的类类型形参是怎么利用ReflectionClass反射类自动实例化的(应该是全网首发)

    1. 或许是全网首发,我翻过很多文章,从未有一个博主讲过这个东西,很多博主只讲了IOC、DI和反射机制的常见用法,因类类型形参反射的巧妙用法有相当高的难度和学习盲区,所以从未有人讲过类类型的形参它怎么就被自动实例化的。 2. 在Laravel框架,或者是其它框架中,类

    2024年02月06日
    浏览(48)
  • HTML+PHP+MYSQL实现一个简单的留言板

    提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加 大家好,下面将为大家展示 基于HTML PHP MYSQL的留言板的设计与实现过程,适合初学者点击观看,以下我将会以笔者自称! 提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档 提示:

    2024年02月04日
    浏览(51)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包