首页
归档
朋友
关于我
留言
【Wiki知识库】
Search
1
虚拟机无法ping不通百度,并无法访问浏览器
4,451 阅读
2
mysql使用or条件使索引失效
3,347 阅读
3
mysql如何在一对多查询时选取时间最近的一条记录
2,699 阅读
4
根据MySQL获取当天,昨天,本周,本月,上周,上月,本月的起始时间
2,163 阅读
5
git常用命令大全
1,564 阅读
PHP
面向对象
设计模式
知识汇总
常用函数
PHP框架知识
数据库
MySQL
服务器
Docker
虚拟机
Nginx
缓存相关
Redis
前端
中间件
RabbitMQ
网络编程
HTTP相关
Swoole
Workerman
工具软件
Git
Typecho
杂乱无章
面试指南
PHP相关
MySQL面试汇总
中间件相关
开发技巧 | 优化
Search
标签搜索
php
mysql
代码片段
linux
Thinkphp
Redis
nginx
mysql优化
docker
面试指南
面向对象
git
Laravel框架
http协议
RabbitMQ
Redis性能优化
设计模式
linux命令
编译安装
PhpSpreadsheet
黎明强
累计撰写
70
篇文章
累计收到
58
条评论
首页
栏目
PHP
面向对象
设计模式
知识汇总
常用函数
PHP框架知识
数据库
MySQL
服务器
Docker
虚拟机
Nginx
缓存相关
Redis
前端
中间件
RabbitMQ
网络编程
HTTP相关
Swoole
Workerman
工具软件
Git
Typecho
杂乱无章
面试指南
PHP相关
MySQL面试汇总
中间件相关
开发技巧 | 优化
页面
归档
朋友
关于我
留言
搜索到
26
篇与
PHP
的结果
2022-11-08
项目代码if else太多,使用策略+工厂模式来解决
简述策略模式,又称为政策模式,属于行为型的设计模式。定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。本模式使得算法可独立于使用它的客户而变化 。业务场景假设有这样的业务场景,拉取不同部门的数据,根据不同的角色类型获取不同的Json内容数据,多数小伙伴会写出如下代码:if(type=="1"){ //按照1格式解析 }else if(type=="2"){ //按2格式解析 }else{ //按照默认格式解析 } 这个代码可能会存在哪些 问题呢 ?如果分支变多,这里的代码就会变得 臃肿,难以维护,可读性低 。如果你需要接入一种新的解析类型,那只能在 原有代码上修改 。说得专业一点的话,就是以上代码,违背了面向对象编程的开闭原则以及 单一原则 。开闭原则 (对于扩展是开放的,但是对于修改是封闭的):增加或者删除某个逻辑,都需要修改到原来代码单一原则 (规定一个类应该只有一个发生变化的原因):修改任何类型的分支逻辑代码,都需要改动当前类的代码。如果你的代码就是:有多个if...else等条件分支,并且每个条件分支,可以封装起来替换的,我们就可以使用策略模式来优化。策略模式使用一个接口或者抽象类,里面两个方法(一个方法匹配类型,一个可替换的逻辑实现方法)不同策略的差异化实现(就是说,不同策略的实现类)使用策略模式一个接口,两个方法不同策略的差异化实现示例封装接口类、策略实现类<?php //统一实现接口 interface Strategy { function send(); } //实现 class ServiceFollowLog implements Strategy { public function send() { // TODO: Implement get() method. echo "记录网络客服的日志"; } } class SaleFollowLog implements Strategy{ public function send() { // TODO: Implement get() method. echo "记录业务员日志"; } } // 工厂类 class Factory{ public $products=array(); public function get($type){ return $this->products[$type]; } public function register($type){ $class=ucfirst($type); $this->products[$type]=new $class; } } 应用层(接口):class user { // public $types=array("sms","email"); public $types= ["3"=> "ServiceFollowLog" , "4"=> "SaleFollowLog"]; public $factory=null; public function __construct(){ //先生成出所有策略的对象 $this->factory=new Factory(); foreach(array_values($this->types) as $t){ $this->factory->register($t); } } //-----新的代码示例:改造策略模式----------- public function newDoAction(){ //根据传递的type参数 , 选择使用哪一个策略 $post="3"; $notice=$this->factory->get($this->types[$post]); $notice->send(); } //---------旧的代码示例------------ public function OldDoAction(){ $type = "sms"; if($type == "sms"){ echo "sms"; }elseif ($type =="email"){ echo "email"; }elseif ($type == "qq"){ echo "qq"; } } } 调用输出// 输出 $u = new user(); $u->newDoAction();每个类都是一个策略,如果有新的策略,增只需要添加一个策略类并把类注册进来。保证了每个类的职责单一性
2022年11月08日
84 阅读
0 评论
0 点赞
2022-10-15
ThinkPHP5源码分析(1) : 类的自动加载
前文Composer 下载ThinkPHP5.1的源码,每个框架它都必须都有一个“类的自动加载”机制 ,我们都知道PHP引入文件是需要require 、 include 才能使用别的类文件中的方法。比如我需要写一个公共文件 model.phpinclude "model.php" include "model2.php" class User { //code.. }但是!如何当公共类库文件很多的时候,每次都需要手动引入,就显得非常麻烦,不利于/不方便维护管理。所以PHP引入了一个spl_autoload_register() 的类自动加载,TP框架就是借助了 spl_autoload_register() 来完成类的自动加载。TP5框架入口加载机制学习框架源码的第一步,先找到入口文件 public/index.php ,然后一步步跟进流程,看下代码执行的过程。第一行:定义命名空间就不赘述了。。第二行:去加载基础文件 base.php ,是位于上一层目录的 think目录下打开Base.php ,第16行就会去加载 Loader.php 文件 (就是TP5自动加载的类库) ,Loader.php 是TP5封装的底层基础类库。再引用了核心文件 Loader.php 调用类库的 Loader::register() 的方法发现都用了spl_autoload_register()的系统函数,其他框架也是同理。 都是会在框架的第一步“入门文件”就进行类的自动加载机制,针对底层进行封装。TP5中的Loader::register() 其实做了2件事:内部自定义一个autoload() 去进行框架的底层深度封装为了支持 Composer 自动加载 安装第三方的类库插件(Vendor),都是遵循PSR-4的风格统一,那么加载composer类的 autoload_static.php 后,再去加载对应的插件类库。分析Loader::register的执行流程如下截图:分析TP5执行 Loader::register() 的注册自动加载方法 ,( 是调用不存在的类的时候才会执行spl_autoload_register())运用3元运算符,如果有参数就执行其他自定义类,没就 加载本类的autoload() 方法调用 self::gerRootPath() 获取项目的根目录这里就是走 Composer的加载机制,然后组织一个Vendor的决定路径 ,self::$composerPath判断是否有Vendor的目录,再判断是否有auto_static的文件 ,有就加载composer目录下的auto_static.php分析Composer自动加载——类文件在逻辑往下走,判断是否有Vendor的目录,再判断是否有auto_static的文件 ,有就加载composer目录下的auto_static.phpauto_static.php发现定义了2个属性分别是: $prefixLengthsPsr4 、$prefixDirsPsr4 ,这是什么意思呢?定义数组,有个key和value,比如 $prefixLengthsPsr4 (PSR4的长度)t key代表一个命名空间 ,代表think\composer\ 命名空间 ,首字母代表类,把某些类放进去来,15 代表字符的长度。a key也是代表一个命名空间,代表:app ,用首字母代表,把某些类放进去, 4是这个字符的长度。2个斜线是转义字符,比如 think\\composer\\ 等同于 think\composer\$prefixDirsPsr4 把每个命名空间的类对应的目录列出来比如think\composer 这个命名空间 在src下app\\ 这个命名空间就在根目录的application比如通过 Compsoe r安装 swoole、workermam、phpexcel 就把相应的规则追加数组上 填进来。总结: composer的统一加载机制的地方,每次composer新类库的时候,都会往这个属性追加 命名空间 以及类库的目录路径 。分析Composer自动加载——属性赋值回到Loader::register()方法。// Composer自动加载支持 if (is_dir(self::$composerPath)) { if (is_file(self::$composerPath . 'autoload_static.php')) { require self::$composerPath . 'autoload_static.php'; $declaredClass = get_declared_classes(); $composerClass = array_pop($declaredClass); foreach (['prefixLengthsPsr4', 'prefixDirsPsr4', 'fallbackDirsPsr4', 'prefixesPsr0', 'fallbackDirsPsr0', 'classMap', 'files'] as $attr) { if (property_exists($composerClass, $attr)) { self::${$attr} = $composerClass::${$attr}; } } } else { self::registerComposerLoader(self::$composerPath); } } // 注册命名空间定义 self::addNamespace([ 'think' => __DIR__, 'traits' => dirname(__DIR__) . DIRECTORY_SEPARATOR . 'traits', ]);再返回 Loader::register() 方法接着放下分析:如果存在Vendor目录,再判断是否有 composer_static.php ,有就执行如下:执行了 get_declared_classes 这个系统函数,返回由当前脚本中已定义类的名字组成的数组。我们打印这个get_declared_classes得到目前所有加载的类,最后require加载的是 composer类。所以,我们上面的 array_pop 弹出最后一个元素,赋值后的 $composerClass ,就是composer的类。这个命令空间那么长的其实就是 composer目录下的autoload_static.php再继续往下执行property_exists : 检查对象或类是否具有该属性我们知道autoload_static.php 存在有prefixLengthsPsr4$prefixDirsPsr4$classMap等一众的方法,composer的 stacis.php这些属性以及值 ,再赋值作为Loader类的某个属性再处理。上面的写法等同 :self::prefixLengthsPsr4 self::prefixDirsPsr4打印这些属性返回:为什么这样去做?后续要集合多个属性再Map,加载文件的时候用这些属性。总结: 把composere下的auto_static的PSR4的属性以及值赋值到Loader的PSR4属性中分析Composer自动加载——注册命名空间会调用当前Loader::addNamespace() 方法 ,把当前think 跟traits 核心注册进行。// 注册命名空间定义 self::addNamespace([ 'think' => __DIR__, 'traits' => dirname(__DIR__) . DIRECTORY_SEPARATOR . 'traits', ]);进去 addNamespace() 方法,我们打印这个 $namespace 参数。// 注册命名空间 public static function addNamespace($namespace, $path = '') { echo "<pre>"; print_r($namespace);exit; if (is_array($namespace)) { foreach ($namespace as $prefix => $paths) { self::addPsr4($prefix . '\\', rtrim($paths, DIRECTORY_SEPARATOR), true); } } else { self::addPsr4($namespace . '\\', rtrim($path, DIRECTORY_SEPARATOR), true); } }再接下来,无论是不是数组,都再把命名空间拼接组装好,再调用self::addPsr4()查看 addPsr4() 方法注册的时候走的是“296” 行 。因为之前 self::$prefixDirsPsr4 里的属性只是composer的autp_static.php 上的,只有 think\ 跟 app\ ,并没有think 跟traits 这2个属性。public static $prefixDirsPsr4 = array ( 'think\\composer\\' => array ( 0 => __DIR__ . '/..' . '/topthink/think-installer/src', ), 'app\\' => array ( 0 => __DIR__ . '/../..' . '/application', ), );这样就完成 think、traits下的命名空间注册到Loader类的PSR4属性中。加载类库映射文件再往下执行我们在根目录的runtime 并没有classmap.php的文件 ,我们可以通过命令生成这个文件php think optimize:autoload这时候runtime下就有classmap.php ,就是存放一些命名空间映射的文件<?php /** * 类库映射 */ return [ 'app\\index\\controller\\Index' => '/Users/limingqiang/project_php/localhost_pro/tp51_source_code/application/' . 'index/controller/Index.php', 'think\\App' => '/Users/limingqiang/project_php/localhost_pro/tp51_source_code/thinkphp/library/' . '/think/App.php', 'think\\Build' => '/Users/limingqiang/project_php/localhost_pro/tp51_source_code/thinkphp/library/' . '/think/Build.php', 'think\\Cache' => '/Users/limingqiang/project_php/localhost_pro/tp51_source_code/thinkphp/library/' . '/think/Cache.php', 'think\\Collection' => '/Users/limingqiang/project_php/localhost_pro/tp51_source_code/thinkphp/library/' . '/think/Collection.php', 'think\\Config' => '/Users/limingqiang/project_php/localhost_pro/tp51_source_code/thinkphp/library/' . '/think/Config.php', 'think\\Console' => '/Users/limingqiang/project_php/localhost_pro/tp51_source_code/thinkphp/library/' . '/think/Console.php', 'think\\Container' => '/Users/limingqiang/project_php/localhost_pro/tp51_source_code/thinkphp/library/' . '/think/Container.php', //code..... ]进入这个 self::addClassMap() 方法,看到把当前classmap.php的数组内容赋值到一个 Loader类的$classMap 属性中。总结: 把tp5的核心的 think 和 traits 命名空间注册到PSR4里,方便后续调用,实在属性多个数组统一自动加载。自动加载extend目录再往下执行进去 self::addAutoLoadDir() 方法: // 注册自动加载类库目录 public static function addAutoLoadDir($path) { self::$fallbackDirsPsr4[] = $path; }把当前extend的目录也加载到 Loader类 $fallbackDirsPsr4 的属性中。总结prefixDirsPsr4 (对应类的目录)prefixLengthsPsr4 (对应类的命名长度)fallbackDirsPsr4 (对应extend的目录)我们拿到很多变量成员、命令空间、目录路径等都赋值到 Loader类的多个PSR4的属性中。后续再拿这些属性做自动加载配置。
2022年10月15日
62 阅读
1 评论
0 点赞
2022-04-18
整理PHP7版本特性
PHP7版本特性详解原文作者:Zhengkx转自链接:https://learnku.com/articles/35800版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请保留以上作者信息和原文链接。php7.0官网手册地址 : http://php.net/manual/zh/migration70.new-features.phpphp7.1官网手册地址:http://php.net/manual/zh/migration71.new-features.phpphp7.4官网手册地址: http://php.net/manual/zh/migration74.new-features.phpPHP7.0特性标量类型声明标量 类型声明 有两种模式:强制(默认) 和 严格模式 。现在可以使用:字符串、整数、浮点数、布尔值。她们扩充了 PHP5 中引入的其他类型:类名、接口、数组和回调类型。<?php function sumOfInts(int ...$ints) { return array_sum($ints); } var_dump(sumOfInts(2, '3', 4.1)); ### 输出 int(9)返回值类型声明PHP7 增加了对返回类型声明的支持。指明了函数返回值的类型。<?php function arraysSum(array ...$arrays): array { return array_map(function (array $array): int { return array_sum($array); }, $arrays); } print_r(arraysSum([1, 2, 3], [4, 5, 6])); ### 输出 Array ( [0] => 6 [1] => 15 )null 合并运算符日常使用中存在大量同时使用三元表达式和 isset() 的情况,PHP7 添加了 null 合并运算符(??) 。如果变量存在且值不为 NULL ,它就会返回自身的值,否则返回他的第二个操作数<?php $username = $_GET['user'] ?? 'nobody'; echo $username; ##### 输出 nobody太空船操作符(组合比较符)太空船操作符用于比较两个表达式。当 $a 小于、等于或者大于 $b 时他分别返回 -1、0 或 1。<?php # 整数 echo 1 <=> 2; // -1 echo 1 <=> 1; // 0 echo 2 <=> 1; // 1 # 浮点数 echo 1.5 <=> 2.5; // -1 echo 1.5 <=> 1.5; // 0 echo 2.5 <=> 1.5; // 1 # 整数 echo "a" <=> "b"; // -1 echo "a" <=> "a"; // 0 echo "b" <=> "a"; // 1通过 define () 定义常量数组Array 类型的常量现在可以通过 define() 来定义。在 PHP 5.6 中仅能通过 const 定义<?php define('ANIMALS', [ 'dog', 'cat', 'bird' ]); echo ANIMALS[2]; ### 输出 birdClosure::call()Closure::call() 有着更好的性能,剪短干练的暂时绑定一个方法到对象上闭包并调用它。<?php class A { private $x = 1; } // PHP 7 之前版本的代码 $getXCB = function () { return $this->x; }; $getX = $getXCB->bindTo(new A, 'A'); // 中间层闭包 // PHP 7+ $getX = function() { return $this->x; }; echo $getx->call(new A);为 unserialize () 提供过滤这个特性旨在提供更安全的方式解包不可靠的数据。他通过白名单的方式来防止潜在的代码注入。预期预期 是向后兼用并增强之前的 assert() 的方法。它使得在生产环境中启用断言为零成本,并提供断言失败时抛出特定异常的能力。<?php ini_set('assert.exception', 1); class CustomError extends AssertionError() {} assert(false, new CustonError('some error message'));命名空间分组 (Group use declarations)在 PHP 7 之前,开发者经常这么做:use Universe\Saiyan; use Universe\SuperSaiyan;从同一个 namespace 导入的类、函数和常量现在可以通过单个 use 语句一次性导入。<?php use some\namespace\{ClassA, ClassB, ClassC as C}; use function some\namespace\{fn_a, fn_b, fn_c}; use const some\namespace\{ConstA, ConstB, ConstC};函数和常量也是一样的。如果它们属于同一命名空间,则可以对它们进行分组。整数除法函数 intdiv ()新加的函数 intdiv() 用来进行整数的除法运算<?php var_dump(intdiv(10, 3)); ### 输出 int(3)会话选项session_start() 可以接受一个 array 作为参数,用来覆盖 php.ini 文件中设置的会话配置选项。CSPRNG Functions新加入两个跨平台的函数:random_bytes() 和 random_int() 用来产生高安全级别的随机字符串和随机整数。<?php $bty = random_bytes(4); var_dump(bin2hex($bty)); $ints = random_int(1, 1000); var_dump($ints); ### 输出 string(8) "093a14a4" int(746)bin2hex() - 函数把包含数据的二进制字符串转换为十六进制值可以使用 list () 函数来展开实现了 ArrayAccess 接口的对象PHP7.1特性可为空(Nullable)类型参数以及返回值的类型现在可以通过在类型前加上一个问号使之允许为空。当启用这个特性时,传入的参数或者函数返回的结果要么是给定的类型,要么是 null。<?php function testReturn(): ?string { return 'elePhPant'; } var_dump(testReturn()); function testReturn1(): ?string { return null; } var_dump(testReturn1()); function test(?string $name) { var_dump($name); } test('elePHPant'); test(null); test(); ### 输出 string(9) "elePhPant" NULL string(9) "elePHPant" NULL PHP Fatal error: Uncaught ArgumentCountError: Too few arguments to function test(), 0 passedvoid 函数一个新的返回值类型 void 被引入。返回值声明为 void 类型的方法要么干脆省去 return 语句,要么使用一个空的 return 语句。对于 void 函数来说,NULL 不是一个合法的返回值List操作 (Symmetric array destructuring)短数组语法([])现在作为 list() 语法的一个备选项,可以用于将数组的值赋给一些变量(包括在 foreach 中)。<?php $data = [ [1, 'Tom'], [2, 'Fred'] ]; // 使用 list() list($id1, $name1) = $data[0]; foreach($data as list($ids, $name)) { } // 使用 [] [$id1, $name1] = $data[0]; foreach($data as [$id, $name]) { }类常量可见性现在起支持设置类常量的可见性。<?php class ConstDemo { const PUBLIC_CONST_A = 1; public const PUBLIC_CONST_B = 2; protected const PROTECTED_CONST = 3; private const PRIVATE_CONST = 4; }可见性有助于确保不应该被覆盖的内容不会被覆盖。在 PHP 7.1 之前,对于类常量(始终是公共的)来说是不可能的。list () 现在支持键名现在 list() 和它的新的 [] 语法支持在它内部指定键名。多异常捕获处理一个 catch 语句块现在可以通过管道字符 (/)来实现多个异常的捕获。<?php try { } catch (FirstException | SecondException $e) { }支持为负的字符串偏移量现在所有支持偏移量的 字符串操作函数 都支持接收负数作为偏移量,包括通过 [] 或 {} 操作字符串下标。在这种情况下,一个负数的偏移量会被理解为一个从字符串结尾开始的偏移量<?php var_dump("abcdef"[-2]); var_dump(strpos("aabbcc", 'b', -3)); ### 输出 string(1) "e" int(3)PHP7.2新特性新的对象类型这种新的对象类型,object ,引进了可用于 逆变( contravariant )参数输入和 协变(covariant)返回任务对象类型。<?php function test(object $obj) :object { return new SqlQueue(); } test(new StdClass());通过名称加载扩展扩展文件不再需要通过文件加载进行指定。可以在 php.ini 配置文件进行启用,也可以使用 dl() 函数进行启用。<?php if (!extension_loaded('sqlite')) { if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { dl('php_sqlite.dll'); } else { dl('sqlite.so'); } } 允许重写抽象方法(Abstract method)当一个抽象类继承于另外一个抽象类的时候,继承后的抽象类可以重写被继承的抽象类的抽象方法。<?php abstract class A { abstract function test(string $s); } abstract class B extends A { abstract function test($s) :int; }使用 Argon2 算法生成密码散列暴露出来的常量PASSWORD_ARGON2IPASSWORD_ARGON2_DEFAULT_MEMORY_COSTPASSWORD_ARGON2_DEFAULT_TIME_COSTPASSWORD_ARGON2_DEFAULT_THREADS新增 ext/PDO (PDO 扩展)字符串扩展类型扩展的常量PDO::PARAM_STR_NATLPDO::PARAM_STR_CHARPDO::ATTR_DEFAULT_STR_PARAM为 ext/PDO 新增额外的模拟调试信息ext/PDP (LDAP 扩展)支持新的操作方式暴露的函数和常量ldap_parse_exop()ldap_exop()ldap_exop_passwd()ldap_exop_whoami()LDAP_EXOP_START_TLSLDAP_EXOP_MODIFY_PASSWDLDAP_EXOP_REFRESHLDAP_EXOP_WHO_AM_ILDAP_EXOP_TURNext/sockets(sockets 扩展)添加了地址信息添加的函数socket_addrinfo_lookup()socket_addrinfo_connect()socket_addrinfo_bind()socket_addrinfo_explain()允许分组命名空间的尾部逗号<?php use Foo\Bar\{ Foo, Bar, Baz, }PHP7.3特性取数组第一个/ 最后一个键从 PHP 7.3 开始,你可以很容易地得到数组的第一个键和最后一个键:$array = [ 'v' => 1, 'i' => 2, 'p' => 3 ]; $firstKey = array_key_first($array); $lastKey = array_key_last($array); print_r($firstKey); // v print_r($lastKey); // p真的很简单,因为它不影响内部数组指针。PHP7.4特性数组延展操作符数组延展操作符 (PHP 7.4) 该特性可以实现以下功能:$abc = range('a', 'c'); $def = range('d', 'f'); $ghi = range('g', 'i'); $all = [...$abc, ...$def, ...$ghi, 'j']; print_r($all);返回:Array ( [0] => a [1] => b [2] => c [3] => d [4] => e [5] => f [6] => g [7] => h [8] => i [9] => j )在大多数情况下,它基本取代了array_merge().箭头函数箭头函数 (PHP 7.4)请注意,因为现在它指的是只有一个表达式的短闭包(因此有了 “短” 这个字):$c = 3; $addC = fn($x) => $x + $c; echo $addC(70); // 73不需要use关键字。空合并运算符赋值<?php $array['key'] ??= computeDefault(); // 等同于以下旧写法 if (!isset($array['key'])) { $array['key'] = computeDefault(); } ?>数组展开操作<?php $parts = ['apple', 'pear']; $fruits = ['banana', 'orange', ...$parts, 'watermelon']; // ['banana', 'orange', 'apple', 'pear', 'watermelon']; ?>
2022年04月18日
160 阅读
0 评论
0 点赞
2022-04-17
整理PHP各个版本的特性以及区别
PHP各个版本的官网: PHP文档这里只列出大概的概要特性,具体的细节以及用法可以查看官网版本文档。PHP5.2特性支持jsonPHP5.3特性新增魔术方法、命名空间、const、三元运算符添加了命名空间的支持添加了静态晚绑定支持添加了跳标签支持添加了原生的闭包(Lambda/匿名函数)支持新增了两个魔术方法, __callStatic 和 __invoke添加了 Nowdoc 语法支持, 类似于 Heredoc 语法, 但是包含单引号使用 Heredoc 来初始化静态变量和类属性/常量变为可能可使用双引号声明 Heredoc, 补充了 Nowdoc 语法可在类外部使用 const 关键词声明 常量三元运算操作符有了简写形式: ?:HTTP 流包裹器将从 200 到 399 全部的状态码都视为成功。动态访问静态方法变为可能异常可以被内嵌新增了循环引用的垃圾回收器并且默认是开启的mail() 现在支持邮件发送日志. (注意: 仅支持通过该函数发送的邮件.)PHP5.4特性数组简写 []、 Traits新增支持 traits 。新增短数组语法,比如 $a = [1, 2, 3, 4];* 或 *$a = ['one' => 1, 'two' => 2, 'three' => 3, 'four' => 4];新增支持对函数返回数组的成员访问解析,例如 foo()[0] 。现在 闭包 支持 $this 。现在不管是否设置 short_open_tag php.ini选项,<?= 将总是可用。新增在实例化时访问类成员,例如: (new Foo)->bar() 。现在支持 Class::{expr}() 语法。新增二进制直接量,例如:0b001001101 。改进解析错误信息和不兼容参数的警告。SESSION 扩展现在能追踪文件的 上传进度 。内置用于开发的 CLI 模式的 web server 。PHP5.5 特性yield迭代器、生成器(foreach)--读取大文件时减少内存foreach 现在支持 list()PHP5.6特性常量增强、可变函数、命名空间增强使用表达式定义常量。使用 ** 进行运算大文件上传 (现在支持大于2G的上传)php://input 是可重用的pgsql 异步支持PHP7.0特性PHP5.6.X到PHP7版本改动比较大的一个阶段版本。官网地址 :http://php.net/manual/zh/migration70.new-features.php标量类型声明返回值类型声明null合并运算符、太空船操作符(组合比较符)通过define()定义常量数组命名空间分组匿名类PHP7.1 特性官网地址 :http://php.net/manual/zh/migration71.new-features.php可空(Nullable)类型list简写、指定keyconst常量可指定权限多异常捕获处理(一个catch)PHP7.2特性新的对象类型 【 逆变( contravariant )参数输入和 协变(covariant)】通过名称加载扩展允许重写抽象方法使用 Argon2 算法生成密码散列新增 ext/PDO (PDO 扩展)字符串扩展类型PHP7.3特性取数组第一个/最后一个键PHP7.4特性数组延展操作符 (...$a)箭头函数 (=>)空合并运算赋值PHP8.0 特性从php7.4就跳到php8版本了8.0 是 PHP 语言的一个主版本更新。 它包含了很多新功能与优化项,命名参数、联合类型、注解、构造器属性提升、match 表达式、nullsafe 运算符、JIT,改进了类型系统、错误处理、语法一致性。PHP8.1特性PHP 8.1 是 PHP 语言的一个主版本更新。它包含了许多新功能枚举、只读属性、First-class 可调用语法、纤程、交集类型和性能改进等。
2022年04月17日
939 阅读
0 评论
0 点赞
2022-02-16
【转】代码的简洁之道
简介原文作者:Summer转自链接:https://learnku.com/laravel/t/62638版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请保留以上作者信息和原文链接。我最近遇到了 这条 Twitter,其中 @samuelstancl 列出了在 Laravel 中编写更干净代码的技巧, 以及一些通用的 Laravel 编码建议。 这些是培养对什么是好的代码和什么是坏的代码的感觉的一个很好的起点 - 所以我在下面整理了它们(带有代码示例),没有特定的顺序。细节决定成败干净的代码是微观层面不断做出正确决策的结果。使用表查找不要编写重复的 else if 语句,而是使用数组根据您拥有的键查找所需值。 代码将更清晰且更具可读性,如果出现问题,您将看到可以理解的异常。 没有半途而废的边缘情况。不好的// 不好的 if ($order->product->option->type === 'pdf') { $type = 'book'; } else if ($order->product->option->type === 'epub') { $type = 'book'; } else if ($order->product->option->type === 'license') { $type = 'license'; } else if ($order->product->option->type === 'artwork') { $type = 'creative'; } else if $order->product->option->type === 'song') { $type = 'creative'; } else if ($order->product->option->type === 'physical') { $type = 'physical'; } if ($type === 'book') { $downloadable = true; } else if ($type === 'license') { $downloadable = true; } else if $type === 'creative') { $downloadable = true; } else if ($type === 'physical') { $downloadable = false; }正确的// 正确的 $type = [ 'pdf' => 'book', 'epub' => 'book', 'license' => 'license', 'artwork' => 'creative', 'song' => 'creative', 'physical' => 'physical', ][$order->product->option->type]; $downloadable = [ 'book' => true, 'license' => true, 'creative' => true, 'physical' => false, ][$type];尽早返回通过尽早返回值来避免不必要的嵌套。过多的嵌套和 else 语句往往会使代码难以理解。槽糕写法// 糟糕的示例 if ($notificationSent) { $notify = false; } else if ($isActive) { if ($total > 100) { $notify = true; } else { $notify = false; } else { if ($canceled) { $notify = true; } else { $notify = false; } } } return $notify;推荐// 推荐的示例 if ($notificationSent) { return false; } if ($isActive && $total > 100) { return true; } if (! $isActive && $canceled) { return true; } return false;正确分割线不要在随机的地方分割线,但也不要让它们太长。 使用 [ 打开数组并缩进值往往效果很好。 与长函数参数值相同。 其他拆分行的好地方是链式调用和闭包。// 糟糕的示例 // 没有分行 return $this->request->session()->get($this->config->get('analytics.campaign_session_key')); // 无意义分行 return $this->request ->session()->get($this->config->get('analytics.campaign_session_key'));推荐// 推荐的示例 return $this->request->session()->get( $this->config->get('analytics.campaign_session_key') ); // 闭包 new EventCollection($this->events->map(function (Event $event) { return new Entries\Event($event->code, $event->pivot->data); })); // 数组 $this->validate($request, [ 'code' => 'string|required', 'name' => 'string|required', ]);不要创建没用的变量可以直接传值,就不要创建没用的变量。// 坏的 public function create() { $data = [ 'resource' => 'campaign', 'generatedCode' => Str::random(8), ]; return $this->inertia('Resource/Create', $data); }推荐// 好的 public function create() { return $this->inertia('Resource/Create', [ 'resource' => 'campaign', 'generatedCode' => Str::random(8), ]); }能提高可读性的时候再创建变量和上一条相反,有时候一个值来自一整套复杂的计算,因此创建一个变量,可以提高可读性,甚至连注释都省了。记住,上下文很重要,并且你编写代码的最终目标是让代码更具有可读性。// 坏的 Visit::create([ 'url' => $visit->url, 'referer' => $visit->referer, 'user_id' => $visit->userId, 'ip' => $visit->ip, 'timestamp' => $visit->timestamp, ])->conversion_goals()->attach($conversionData);推荐// 好的 $visit = Visit::create([ 'url' => $visit->url, 'referer' => $visit->referer, 'user_id' => $visit->userId, 'ip' => $visit->ip, 'timestamp' => $visit->timestamp, ]); $visit->conversion_goals()->attach($conversionData);根据业务逻辑来创建模型的方法控制器应该尽量保持简单。 比如以 “为订单创建发票” 这样的方式调用方法。调用方法不需要关心你的数据库表结构的细节,这些由模型自己内部实现。// 糟糕的方式 // 为订单创建发票 DB::transaction(function () use ($order) { Sinvoice = $order->invoice()->create(); $order—>pushStatus(new AwaitingShipping); return $invoice; });// 优雅的方式 $order->createInvoice();创建动作类让我们来继续刚才的例子。有时,可以为某个动作单独创建一个类,这样会使代码更加整洁。模型封装的业务逻辑可以基于动作类,但是记得动作类不可太大。// 糟糕的方式 public function createInvoice(): Invoice { if ($this->invoice()->exists()) { throw new OrderAlreadyHasAnInvoice('Order already has an invoice.'); } return DB::transaction(function () use ($order) { $invoice = $order->invoice()->create(); $order->pushStatus(new AwaitingShipping); return $invoice; }); }推荐// 优雅的方式 // 订单模型 public function createInvoice(): Invoice { if ($this->invoice()->exists()) { throw new OrderAlreadyHasAnInvoice('Order already has an invoice.'); } return app(CreateInvoiceForOrder::class)($this); } // 订单创建发票动作类 class CreatelnvoiceForOrder { public function _invoke(Order $order): Invoice { return DB::transaction(function () use ($order) { $invoice = $order->invoice()->create(); $order->pushStatus(new AwaitingShipping); return $invoice; }); } }考虑表单请求考虑使用表单请求,它们是隐藏复杂验证逻辑的好地方,但要注意这一点 — 隐藏的东西。当您的验证逻辑很简单时,在控制器中执行它并没有错,将其移至表单请求使其变得不那么明确。/** * 获取适用于请求的验证规则. * * @返回数组 */ public function rules() { return [ 'title' => 'required|unique:posts|max:255', 'body' => 'required', ]; }使用事件考虑将一些逻辑从控制器写到事件。例如,在创建模型时,好处是创建这些模型在任何地方 (控制器,任务,…) 并且控制器不必担心 DB 模式的细节。// 糟糕的示例 // 只在这个地方有效并关注它 // 模型应该关心的细节. if (! isset($data['name'])) { $data['name'] = $data['code']; } $conversion = Conversion::create($data);// 推荐的示例 $conversion = Conversion::create($data); // 模型 class ConversionGoal extends Model { public static function booted() { static::creating(function (self $model) { $model->name ??= $model->code; }); } }拆分方法如果某些方法太长或是太复杂,很难理解究竟做了什么,可以尝试将复杂的逻辑拆分成多个方法。public function handle(Request $request, Closure $next) { // 我们将三段逻辑分别提取成单独的方法 $this->trackVisitor(); $this->trackCampaign(); $this->trackTrafficSource($request); $response = $next($request); $this->analytics->log($request); return $response; }创建助手函数如果你多次重复一段代码,考虑一下将它们提取为助手函数是不是可以让代码更简洁。// app/helpers.php 文件,在 composer.json 中自动加载 function money(int $amount, string $currency = null): Money { return new Money($amount, $currency ?? config('shop.base_currency')); } function html2text($html = ''): string { return str_replace(' ', ' ', strip_tags($html)); }避免使用助手类有时候人们会使用类来归类助手函数(注意),可要小心了,这可能会让代码变得更混乱。常见的做法是定义一个只包含一个作为助手函数使用的静态方法的类。更好的做法是将这些方法放入具有具体逻辑的类中,或者是只将它们当做是全局函数。// 坏的 class Helper { public function convertCurrency(Money $money, string $currency): self { $currencyConfig = config("shop.currencies.$currency"); $decimalDiff = ... return new static( (int) round($money->baseValue() * $currencyConfig[value] * 10**$decimalDiff, 0), $currency ); } } // 使用 use App\Helper; Helper::convertCurrency($total, 'EUR');// 好的 class Money { // 其他的 money/currency 逻辑 public function convertTo(string $currency): self { $currencyConfig = config("shop.currencies.$currency"); $decimalDiff = ... return new static( (int) round($this->baseValue() * $currencyConfig[value * 10**$decimalDiff, 0), $currency ); } } // 使用 $EURtotal = $total->convertTo('EUR');拿出一个周末来学习 OO了解静态(static)/ 实例(instance)方法和变量,还有私有的(private)/ 保护的(protected)/ 公共的(public)之间的可见性的区别。还要了解 Laravel 如何使用魔法方法。当你是初学者的时候可能不会很常用,但是随着你的编码水平增长,这些是至关重要的。不要在类中只写过程代码这将前面的推文与此处的其他提示联系起来。OOP 的存在就是为了让你的代码更加具有可读性,请使用 OOP。不要再在控制器中写好几百行的过程代码了!!!。阅读 SRP 之类的内容,并进行合理的扩展避免使用那种处理很多和当前类不相关逻辑的类,但是也不要为每件事都创建一个类。你是为了写干净的代码,而不是想在每件事上都做分离。避免函数中参数过多当您看到具有大量参数的函数时,它可能意味着:该函数包含太多职责,应该分离。职责没问题,但你应该学会重构他的长签名.以下是修复第二种情况的两种策略使用数据传输对象 (DTO)与其以特定顺序传递大量参数,不如考虑创建一个具有属性的对象来存储这些数据。 如果您发现某些行为可以移入此对象,则可以加分。// 糟糕的示例 public function log($url, $route_name, $route_data, $campaign_code, $traffic_source, $referer, $user_id, $visitor_id, $ip, $timestamp) { // ... }// 推荐的示例 public function log(Visit $visit) { // ... } class Visit { public string $url; public ?string $routeName; public array $routeData; public ?string $campaign; public array $trafficSource[]; public ?string $referer; public ?string $userId; public string $visitorId; public ?string $ip; public Carbon $timestamp; // ... }创建流式对象你可以使用流式 API 来创建对象。使用单独的方法调用来逐渐添加数据,并且只要构造函数中的绝对最小值。正是因为每个方法都返回 $this ,你可以在任意一次调用后让整个流程停下来。Visit::make($url, $routeName, $routeData) ->withCampaign($campaign) ->withTrafficSource($trafficSource) ->withReferer($referer) // ... 等等使用自定义集合创建自定义集合可以更好地写出更富有表现力的语法。参考这个订单合计的示例:// 坏的 $total = $order->products->sum(function (OrderProduct $product) { return $product->price * $product->quantity * (1 + $product->vat_rate); });// 好的 $order->products->total(); class OrderProductCollection extends Collection { public function total() { $this->sum(function (OrderProduct $product) { return $product->price * $product->quantity * (1 + $product->vat_rate); }); } }不要使用缩写不要觉得很长的变量名 / 方法名就是不对的,才不是这样,它们很有表现力。使用一个长的方法名比短的更好,配合查阅文档能更完整地了解它的功能。变量也是如此。不要使用无意义的几个字母的缩写。// 坏的 $ord = Order::create($data); // ... $ord->notify();// 好的 $order = Order::create($data); // ... $order->sendCreatedNotification();尝试在控制器中只使用 CURD 动作如果可以的话,只使用控制器中的 7 个 CURD 动作,通常来说会更少。不要在控制器中创建 20 多个方法,更短的控制器更好一些。使用更具有表现力的方法名称考虑「这个对象可以完成什么事情」,而不是「这个对象能做什么」。也会有例外,比如操作类。这是个很好的经验。// 坏的 $gardener->water($plant); $orderManager->lock($order);// 好的 $plant->water(); $order->lock();创建单次使用的 trait将方法添加到它所属的类中,比为每件事都创建操作类简洁得多,但是这会让类变得很大。尝试使用特征 traits,它主要是为了代码复用,但是单次使用的 trait 并没有错。class Order extends Model { use HasStatuses; // ... } trait HasStatuses { public static function bootHasStatuses() { ... } public static $statusMap = [ ... ]; public static $paymentStatusMap = [ ... ]; public static function getStatusId($status) { ... } public static function getPaymentStatusId($status): string { ... } public function statuses() { ... } public function payment_statuses() { ... } public function getStatusAttribute(): OrderStatusModel { ... } public function getPaymentStatusAttribute(): OrderPaymentStatus { ... } public function pushStatus($status, string $message = null, bool $notification = null) { ... } public function pushPaymentStatus($status, string $note = null) { ... } public function status(): OrderStatus { ... } public function paymentStatus(): PaymentStatus { ... } }创建一次性引入类似于一次性 traits. 当您有一个很长的模板并且希望使其更易于管理时,这种策略非常有用。布局中的 @include-ing 页眉和页脚或页面视图中的复杂表单等都没有问题。导入命名空间而不是使用别名有时您可能有多个同名的类。 与其使用别名导入它们,不如导入命名空间。// 糟糕的示例 use App\Types\Entries\Visit as VisitEntry; use App\Storage\Database\Models\Visit as VisitModel; class DatabaseStorage { public function log(VisitEntry $visit) { $visitModel = VisitModel::create([ // ... ]); } }// 推荐的示例 use App\Types\Entries; use App\Storage\Database\Models; class DatabaseStorage { public function log(Entries\Visit $visit) { $visitModel = Models\Visit::create([ // ... ]); } }为 where() 创建查询方法使用更具有表现力的名字创建查询方法,而不是编写完整的 where()。这可以让你的代码(例如控制器)尽可能更少地与数据库结构产生耦合,并且可以让代码更清晰。// 不好的 Order::whereHas('status', function ($status) { return true$status->where('canceled', true); })->get();// 好的 Order::whereCanceled()->get(); class Order extends Model { public function scopeWhereCanceled(Builder $query) { return $query>whereHas('status', function ($status) { return $status->where('canceled', true); }); } }不要使用模型方法来检索数据如果你想要从模型中获取数据,可以创建一个访问器。保留以某种方式改变模型的方法。// 坏的 $user->gravatarUrl(); class User extends Authenticable { // ... public function gravatarUrl() { return "https://www.gravatar.com/avatar/" . md5(strtolower(trim($this->email))); } }// 好的 Suser->gravatar_url; class User extends Authenticable { // ... public function getGravatarUrlAttribute() { return "https://www.gravatar.com/avatar/" . md5(strtolower(trim($this->email))); } }使用自定义配置文件你可以在配置文件中存储类似「每页几条数据」这样的内容。不要直接将它们放在 app 配置文件中。创建你自己的配置文件。例如,在一个电商项目中,你可以使用 config/shop.php。// config/shop.php return [ 'vat rates' => [ 0.21, 0.15, 0.10, 0.0, ], 'fee_vat_rate' => 0.21, 'image_sizes' => [ 'base' => 500, // detail 't1' => 250, // index 't2' => 50, // search ], ];不要使用控制器命名空间使用可调用的数组语法 [PostController::class, 'index'] ,而不是直接写控制器和方法名 PostController@index。这样写的话你可以点击 PostController 来跳转到类的定义。// 坏的 Route::get('/posts', 'PostController@index');// 好的 Route::get('/posts', [PostController::class, 'index']);考虑使用单动作控制器如果你有复杂的路由操作,考虑将它放在单独的控制器中。对于 OrderController::create,你可以创建 CreateOrderController。另一个解决方法是将该逻辑转移到一个动作类 —— 在你的实际情况中选择最好用的方法。// 我们使用上面提到的类语法 Route::post('/orders/', CreateOrderController::class); class CreateOrderController { public function _invoke(Request $request) { // ... } }友好型 IDE安装扩展,编写注释,使用类型提示。 您的 IDE 将帮助您让您的代码正常工作,这让您可以将更多的精力花在编写可读的代码上。$products = Product::with('options')->cursor(); foreach ($products as $product) { /** @var Product $product */ if ($product->options->isEmpty()) { // ... } } /////////////////////////// foreach (Order::whereDoesntHave('invoice')->whereIn('id', $orders->pluck('id'))->get() as $order) { /** @var Order $order */ $order->createInvoice(); // ... } /////////////////////////// $productImage ->help('Max 2 MB') ->store(function (NovaRequest $request, ProductModel $product) { /** @var UploadedFile $image */ $image = $request->image; // ... });使用短运算符PHP 有很多很棒的操作符可以替代丑陋的 if 检查。 记住它们。// 糟糕的 // truthy test if (! $foo) { $foo = 'bar'; } // null test if (is_null($foo)) { $foo = 'bar'; } // isset test if (! isset($foo)) { $foo = 'bar'; }// 优雅的 // truthy test $foo = $foo ?: 'bar'; // null test $foo = $foo ?? 'bar'; // PHP 7.4 $foo ??= 'bar'; // isset test $foo = $foo ?? 'bar'; // PHP 7.4 $foo ??= 'bar';决定您是否喜欢运算符周围的空格在上面你可以看到我在 ! 和我要否定的值之间使用了空格。 我喜欢这个,因为它清楚地表明该值被否定了。 我在点周围做同样的事情。 可以按照您的喜好来清理您的代码。助手函数而不是 Facades考虑使用助手函数而不是 Facades 。 因为他们可以使得代码变得很整洁。 这在很大程度上取决于个人喜好,但调用全局函数而不是导入类并静态调用方法对我来说感觉更好。 session('key') 语法的加分项。// 糟糕的示例 Cache::get('foo');// 推荐的示例 cache()->get('foo'); // Better cache('foo');为业务逻辑创建自定义 Blade 指令你可以通过创建自定义指令来让你的 Blade 模板更具有表现力。举个例子,你可以使用 @admin 来检查用户是否是管理员,而不是使用具体的逻辑来判断。// 不好的写法 @if(auth()->user()->hasRole('admin')) // ... @else // ... @endif// 好的写法 @admin // ... @else // ... @endadmin避免在 Blade 中查询数据库可能有的时候你想要在 Blade 中查询数据库。有些情况下这是可以的,例如布局文件。但是如果是在视图文件中,则需要在控制器中向视图传入查询好的数据。// 不好的写法 @foreach(Product::where('enabled', false)->get() as $product) // ... @endforeach// 好的写法 // 控制器 return view('foo', [ 'disabledProducts' => Product::where('enabled', false)->get(), ]); // 视图 @foreach($disabledProducts as $product) // ... @endforeach使用精确的比较运算符始终使用严格比较(=== 和 !==)。 如果需要,在比较之前将事物转换为正确的类型。 比奇怪的 == 结果要好。 还要考虑在您的代码中启用严格类型。 这将防止将错误数据类型的变量传递给函数。// 糟糕的示例 $foo == 'bar';// 推荐的示例 $foo === 'bar'; // Better declare(strict_types=1);仅当他们澄清事情时才使用文档块很多人会不同意这一点,因为他们这样做了。 但这没有任何意义。 当它们不提供任何额外信息时,使用 文档是没有意义的。 如果类型提示足够了,就不要添加文档块,那样只是多余的。// 糟糕的示例 // 全部没有类型 function add_5($foo) { return $foo + 5; } // @param 注释精确地添加了 0% 值和 100% 噪声。 /** *给一个数加 5。 * * @param int $foo * @return int */ function add_5(int $foo): int { return $foo + 5; }// 推荐的示例 // 没有文档块,一切都清楚 function add_5(int $foo) { return $foo + 5; } //类型提示说得尽可能多,注释说得更多。 /** * 把单词变成句子。 * * @param string[] $words * @返回字符串 */ function sentenceFromWords(array $words): string { return implode(' ', $words) . '.'; } // 个人最爱。 只使用能带来价值的注解。 不要仅仅因为它太常见就使用 description 或 @return。 /** @param string[] $words */ function sentenceFromWords(array $words): string { return implode(' ', $words) . '.'; }验证规则有单一的真实来源如果你在多个地方验证某个资源的属性,你肯定希望将这些验证规则集中起来,这样你就不会在一个地方更改它们而忘记其他地方。 我经常发现自己在模型的方法中保留了验证规则。 这让我可以在任何需要的地方重用它们 —— 包括在控制器或表单请求中。class Reply extends Model { public static function getValidationRules(): array { return [ 'thread_id' => ['required', 'integer'], 'user_id' => ['required', 'integer'], 'body' => ['required', 'string', new SpamRule()], ]; } }可以使用集合来优化代码不要仅仅因为 Laravel 提供它们就将所有数组转换为集合,而是当您可以使用集合语法来优化代码时将数组转换为集合。$collection = collect([ ['name' => 'Regena', 'age' => null], ['name' => 'Linda', 'age' => 14], ['name' => 'Diego', 'age' => 23], ['name' => 'Linda', 'age' => 84], ]); $collection->firstWhere('age', '>=', 18);在对你有利的时候编写函数式代码函数式代码不仅能使代码整洁,还能降低代码的易读性。 将常见循环重构为函数调用,但不要为了避免编写循环而编写愚蠢复杂的 reduce ()。 两者都有一个用例。// 糟糕的示例 return array_unique(array_reduce($keywords, function ($result, $keyword) { return array_merge($result, array_reduce($this->variantGenerators, function ($result2, $generator) use ($keyword) { return array_merge($result2, array_map(function ($variant) { return strtolower($variant); }, $generator::getVariants($keyword))); }, [])); }, []));// 推荐的示例 return $this->items()->reduce(function (Money $sum, OrderItem $item) { return $sum->addMoney($item->subtotal()); }, money(0, $this->currency));注释通常表明代码设计不佳在撰写评论之前,问问自己是否可以重命名某些内容或创建变量以提高可读性。 如果这是不可能的,请以您和您的同事在 6 个月后都能理解的方式撰写评论。上下文问题上面我说将业务逻辑转移到逻辑类 / 服务类是好的。 但上下文很重要, 这是来自流行的 “Laravel 最佳实践” 存储库的代码设计建议。 绝对没有理由将 3 行检查放入类中。 这只是过度设计。// 糟糕的示例 public function store(Request $request) { $this->articleService->handleUploadedImage($request->file('image')); } class ArticleService { public function handleUploadedImage($image) { if (!is_null($image)) { $image->move(public_path('images') . 'temp'); } } }// 推荐的示例 public function store(Request $request) { if ($request->hasFile('image')) { $request->file('image')->move(public_path('images') . 'temp'); } // ... }只使用对你有帮助的东西,忽略其他一切您的目标是编写更具可读性的代码。您的目标不是按照某人在互联网上所说的那样去做。这些技巧只是有助于写出优雅的代码。 牢记你的最终目标并问自己「这样更好吗?」
2022年02月16日
146 阅读
1 评论
0 点赞
2021-07-17
一文带你熟悉TP5.1的模型各种操作
前文带你熟悉TP5.1中的模型获取器、修改器、模型搜索器、模型数据集、模型自动时间戳、模型只读字段、模型转换器、模型范围搜索等..01. 模型获取器和修改器本节课我们来学习模型中操作比较方便的获取器和修改器。一. 模型获取器获取器的作用是对模型实例的数据做出自动处理;一个获取器对应模型的一个特殊方法,该方法为 public;方法名的命名规范为:getFieldAttr() ;举个例子,数据库表示状态 status 字段采用的是数值;而页面上,我们需要输出 status 字段希望是中文,就可以使用获取器;在 User 模型端,我创建一个对外的方法,如下:public function getStatusAttr($value) { $status = [-1=>'删除', 0=>'禁用', 1=>'正常', 2=>'待审核']; return $status[$value]; }然后,在控制器端,直接输出数据库字段的值即可得到获取器转换的对应值$user = UserModel::get(21); return $user->status;除了 getFieldAttr 中 Field 可以是字段值,也可以是自定义的虚拟字段;public function getNothingAttr($value, $data) { $myGet = [-1=>'删除', 0=>'禁用', 1=>'正常', 2=>'待审核']; return $myGet[$data['status']]; } return $user->nothing;Nothing 这个字段不存在,而此时参数$value 只是为了占位,并未使用;第二个参数$data 得到的是筛选到的数据,然后得到最终值;如果你定义了获取器,并且想获取原始值,可以使用 getData() 方法;return $user->getData('status');直接输出无参数的 getData(),可以得到原始值,而$user 输出是改变后的;dump($user->getData()); //获取原始值 dump($user); //获取改变后的值使用 WithAttr 在控制器端实现动态获取器,比如设置所有 email 为大写;$result = UserModel::WithAttr('email', function ($value) { return strtoupper($value); })->select(); return json($result);使用 WithAttr 在控制器端实现动态获取器,比如设置 status 翻译为中文;result = UserModel::WithAttr('status', function ($value) { $status = [-1=>'删除', 0=>'禁用', 1=>'正常', 2=>'待审核']; return $status[$value]; })->select(); return json($result);. 同时定义了模型获取器和动态获取器,那么模型修改器优先级更高二. 模型修改器模型修改器的作用,就是对模型设置对象的值进行处理;比如,我们要新增数据的时候,对数据就行格式化、过滤、转换等处理;模型修改器的命名规则为:setFieldAttr ;我们要设置一个新增,规定邮箱的英文都必须大写,修改器如下:public function setEmailAttr($value) { return strtoupper($value); }除了新增,会调用修改器,修改更新也会触发修改器;模型修改器只对模型方法有效,调用数据库的方法是无效的,比如->insert();02. 模型搜索器和数据集本节课我们来学习模型中的用于封装的搜索器和数据结果集的操作。一. 模型搜索器搜索器是用于封装字段(或搜索标识)的查询表达式;一个搜索器对应模型的一个特殊方法,该方法为 public;方法名的命名规范为:searchFieldNameAttr();举个例子,我们要封装一个邮箱字符模糊查询,然后封装一个时间限定查询;在 User 模型端,我创建两个对外的方法,如下://邮箱查询 public function searchEmailAttr($query, $value) { $query->where('email', 'like', $value.'%'); } //时间查询 public function searchCreateTimeAttr($query, $value) { $query->whereBetweenTime('create_time', $value[0], $value[1]); }然后,在控制器端,通过 withSearch() 静态方法实现模型搜索器的调用**;$result = UserModel::withSearch(['email', 'create_time'],[ 'email' => 'xiao', 'create_time' => ['2014-1-1', '2017-1-1'] ])->select();withSearch() 中第一个数组参数,限定搜索器的字段,第二个则是表达式值;如果想在搜索器查询的基础上再增加查询条件,直接使用链式查询即可;UserModel::withSearch(...)->where('gender', '女')->select()如果你想在搜索器添加一个可以排序的功能,具体如下://model层 public function searchEmailAttr($query, $value, $data) { $query->where('email', 'like', $value.'%'); if (isset($data['sort'])) { $query->order($data['sort']); } } //controller调用 $result = UserModel::withSearch(['email', 'create_time'],[ 'email' => 'xiao', 'create_time' => ['2014-1-1', '2017-1-1'], 'sort' => ['price'=>'desc'] ])->select();搜索器的第三个参数$data,可以得到 withSearch()方法第二参数的值;字段也可以设置别名:'create_time'=>'ctime'一. 模型数据集数据集由 all() 和 select() 方法返回数据集对象;数据集对象和数组操作方法一样,循环遍历、删除元素等;判断数据集是否为空,我们需要采用 isEmpty() 方法$resut = UserModel::where('id', 111)->select(); if ($resut->isEmpty()) { return '没有数据!'; }使用模型方法 hidden() 可以隐藏某个字段,使用 visible() 显示只某个字段使用 append() 可以添加某个获取器字段,使用 withAttr() 对字段进行函数处理;$result = UserModel::select(); $result->hidden(['password'])->append(['nothing'])->withAttr('email', function ($value) { return strtoupper($value); }); return json($result);使用模型方法 filter() 对筛选的数据进行过滤;$result = UserModel::select()->filter(function ($data) { return $data['price'] > 100; }); return json($result);也可以使用数据集之后链接 where()方法来代替 filter()方法;$result = UserModel::select()->where('price', '>', '100');数据集甚至还可以使用 order() 方法进行排序;$result = UserModel::select()->order('price', 'desc');使用 diff() 和 intersect()方法可以计算两个数据集的差集和交集;$result1 = UserModel::where('price', '>', '80')->select(); $result2 = UserModel::where('price', '<', '100')->select(); return json($result1->diff($result2)); return json($result2->intersect($result1)); 03. 模型自动时间戳和只读字段本节课我们来学习模型中用于记录时间的自动时间戳和不可更改只读字段。一. 模型自动时间戳系统自动创建和更新时间戳功能默认是关闭状态;如果你想全局开启,在 database.php 中,设置为 true;// 自动写入时间戳字段 'auto_timestamp' => true,如果你只想设置某一个模型开启,需要设置特有字段;class User extends Model { //开启自动时间戳 protected $autoWriteTimestamp = true; }当然,还有一种方法,就是全局开启,单独关闭某个或某几个模型为 false;自动时间戳开启后,会自动写入 create_time 和 update_time 两个字段;此时,它们的默认的类型是 int,如果是时间类型,可以更改如下:'auto_timestamp' => 'datetime', //或 protected $autoWriteTimestamp = 'datetime';都配置完毕后,当我们新增一条数据时,无须新增 create_time 会自动写入时间;同理,当我们修改一条数据时,无须修改 update_time 会自动更新时间;自动时间戳只能在模型下有效,数据库方法不可以使用;如果创建和修改时间戳不是默认定义的,也可以自定义;protected $createTime = 'create_at'; protected $updateTime = 'update_at';如果业务中只需要 create_time 而不需要 update_time,可以关闭它;protected $updateTime = false;也可以动态实现不修改 update_time,具体如下:$user->isAutoWriteTimestamp(false)->save();二. 模型只读字段模型中可以设置只读字段,就是无法被修改的字段设置;我们要设置 username 和 email 不允许被修改,如下:protected $readonly = ['username', 'email'];除了在模型端设置,也可以动态设置只读字段;$user->readonly(['username', 'email'])->save();同样,只读字段只支持模型方式不支持数据库方式;04. 模型类型转换和数据完成本节课我们来学习模型中字段的类型转换和数据操作的自动完成。一. 模型类型转换系统可以通过模型端设置写入或读取时对字段类型进行转换;我们这里,通过读取的方式来演示部分效果;在模型端设置你想要类型转换的字段属性,属性值为数组;protected $type = [ 'price' => 'integer', 'status' => 'boolean', 'create_time' => 'datetime:Y-m-d' ];数据库查询读取的字段很多都是字符串类型,我们可以转换成如下类型:integer(整型)、float(浮点型)、boolean(布尔型)、array(数组) object(对象)、se由于数据库没有那么多类型演示,常用度不显著,我们提供几个方便演示的;public function typeConversion() { $user = UserModel::get(21); var_dump($user->price); var_dump($user->status); var_dump($user->create_time); }需要注意的是: 类型转换还是会调用属性里的获取器等操作,编码时要注意这方面的问题;三. 模型数据完成模型中数据完成通过 auto、insert 和 update 三种形式完成;auto 表示新增和修改操作,insert 只表示新增,update 只表示修改;protected $auto = ['email']; protected $insert = ['uid'=>1]; protected $update = [];先理解insert,当我们新增一条数据时会触发新增数据完成;此时,并不需要自己去新增 uid,它会自动给 uid 赋值为 1;$user = new UserModel(); $user->username = '李白'; $user->password = '123'; $user->gender = '男'; $user->email = 'libai@163.com'; $user->price = 100; $user->details = '123'; $user->save();auto 表示新增和修改均要自动完成,而不给默认值的字段需要修改器提供;public function setEmailAttr($value) { return strtoupper($value); }新增时,邮箱字符串会被修改器自动完成大写,那数据完成的意义何在?修改时,如果你不去修改邮箱,在数据自动完成强制完成,会自动完成大写;也就是说,邮箱的大写,设置 update 更加合适,因为新增必填必然触发修改器;对于 update 自动完成,和 auto、insert 雷同,自行演示;05. 模型查询范围和输出本节课我们来学习模型中封装查询范围的方法以及模型对外输出的方式。一. 模型查询范围在模型端创建一个封装的查询或写入方法,方便控制器端等调用**;比如,封装一个筛选所有性别为男的查询,并且只显示部分字段 5 条;方法名规范:前缀 scope,后缀随意,调用时直接把后缀作为参数使用;public function scopeGenderMale($query) { $query->where('gender', '男')->field('id,username,gender,email')->limit(5); }在控制器端,我们我们直接调用并输出结果即可;public function queryScope() { $result = UserModel::scope('gendermale')->select(); //$result = UserModel::gendermale()->select(); return json($result); }也可以实现多个查询封装方法连缀调用,比如找出邮箱 xiao 并大于 80 分的;public function scopeEmailLike($query, $value) { $query->where('email', 'like', '%'.$value.'%'); } public function scopePriceGreater($query, $value) { $query->where('price', '>', 80); } //调用 $result = UserModel::emailLike('xiao')->priceGreater(80)->select();查询范围只能使用 find() 和 select()两种方法;全局范围查询,就是在此模型下不管怎么查询都会加上全局条件;//全局范围查询 protected function base($query) { $query->where('status', 1); }在定义了全局查询后,如果某些不需要全局查询可以使用 useGlobalScope 取消;UserModel::useGlobalScope(false)当然,设置为 true,则开启全局范围查询,注意:这个方法需要跟在::后面;UserModel::useGlobalScope(true)二. 模型输出方式通过模版进行数据输出;public function view() { $user = UserModel::get(21); $this->assign('user', $user); return $this->fetch(); }根据错误提示,可以创建相对应的模版,然后进行数据显示;{$user.username}. {$user.gender}. {$user.email}使用 toArray()方法,将对象按照数组的方式输出;$user = UserModel::get(21); print_r($user->toArray());和之前的数据集一样,它也支持 hidden、append、visible 等方法;print_r($user->hidden(['password,update_time'])->toArray());toArray() 方法也支持 all()和 select()等列表数据;print_r(UserModel::select()->toArray());使用 toJson() 方法将数据对象进行序列化操作,也支持 hidden 等方法;print_r($user->toJson());
2021年07月17日
744 阅读
1 评论
0 点赞
2021-07-17
ThinkPHP5.1中各种表达式查询
01. 查询表达式本节课我们要学习查询中的几种查询方式:比较查询、区间查询、其它查询等。一. 比较查询在查询数据进行筛选时,我们采用 where() 方法,比如 id=80;Db::name('user')->where('id', 80)->find(); //where(字段名,查询条件) Db::name('user')->where('id','=',80)->find(); //where(字段名,表达式,查询条件)其中,表达式不区分大小写,包括了比较、区间和时间三种类型的查询;使用<>、>、<、>=、<= 可以筛选出各种符合比较值的数据列表;Db::name('user')->where('id','<>',80)->select();二. 区间查询使用like 表达式进行模糊查询;Db::name('user')->where('email','like','xiao%')->select();like 表达式还可以支持数组传递进行模糊查询;Db::name('user')->where('email','like',['xiao%','wu%'], 'or')->select(); //SELECT * FROM `tp_user` WHERE (`email` LIKE 'xiao%' OR `email` LIKE 'wu%')like 表达式具有两个快捷方式 whereLike() 和 whereNoLike();Db::name('user')->whereLike('email','xiao%')->select(); Db::name('user')->whereNotLike('email','xiao%')->select();between 表达式具有两个快捷方式 whereBetween() 和 whereNotBetween() ;Db::name('user')->where('id','between','19,25')->select(); Db::name('user')->where('id','between',[19, 25])->select(); Db::name('user')->whereBetween('id',[19, 25])->select(); Db::name('user')->whereNotBetween('id',[19, 25])->select();in 表达式具有两个快捷方式 whereIn()和 whereNotIn();Db::name('user')->where('id','in', '19,21,29')->select(); Db::name('user')->whereIn('id','19,21,29')->select(); Db::name('user')->whereNotIn('id','19,21,29')->select();null 表达式具有两个快捷方式 whereNull()和 whereNotNull() ;Db::name('user')->where('uid','null')->select(); Db::name('user')->where('uid','not null')->select(); Db::name('user')->whereNull('uid')->select(); Db::name('user')->whereNotNull('uid')->select();三. 其他查询使用 exp 可以自定义字段后的 SQL 语句;Db::name('user')->where('id','exp','IN (19,21,25)')->select(); Db::name('user')->whereExp('id','IN (19,21,25)')->select();02. 时间查询本节课我们要单独学习一下时间的所有查询方式,包括传统式、快捷方式和固定查询等。一. 传统方式可以使用>、<、>=、<= 来筛选匹配时间的数据Db::name('user')->where('create_time', '> time', '2018-1-1')->select();可以使用 between 关键字来设置时间的区间;Db::name('user')->where('create_time', 'between time', ['2018-1-1','2019-12-31'])->select(); Db::name('user')->where('create_time', 'not between time', ['2018-1-1','2019-12-31'])->select();二. 快捷方式时间查询的快捷方法为 whereTime() ,直接使用>、<、>=、<=;Db::name('user')->whereTime('create_time', '>', '2018-1-1')->select();快捷方式也可以使用 between 和 not between;Db::name('user')->whereBetween('create_time', ['2018-1-1','2019-12-31'])->select();还有一种快捷方式为:whereBetweenTime() ,如果只有一个参数就表示一天;Db::name('user')->whereBetweenTime('create_time', '2018-1-1','2019-12-31')->select(). 默认的大于>,可以省略;Db::name('user')->whereTime('create_time', '2018-1-1')->select();三. 固定查询关键词说明today 或 d今天yesterday昨天week 或者 w本周last week上周month 或者 m本月last month 或者m上月year 或者 y今年last year去年Db::name('user')->whereTime('create_time','d')->select(); Db::name('user')->whereTime('create_time','y')->select();四. 其他查询查询指定时间的数据,比如两小时内的;Db::name('user')->whereTime('create_time', '-2 hour')->select();查询两个时间字段时间有效期的数据,比如会员开始到结束的期间;Db::name('user')->whereBetweenTimeField('start_time','end_time')->select();03. 聚合、原生和子查询本节课我们来学习三种查询方式:包括聚合查询、子查询和原生查询。一. 聚合查询使用 count() 方法,可以求出所查询数据的数量;Db::name('user')->count();count() 可设置指定 id,比如有空值(Null)的 uid,不会计算数量;Db::name('user')->count('uid');使用 max() 方法,求出所查询数据字段的最大值;Db::name('user')->max('price');如果 max() 方法,求出的值不是数值,则通过第二参数强制转换;Db::name('user')->max('price', false);使用 min() 方法,求出所查询数据字段的最小值,也可以强制转换;Db::name('user')->min('price');使用 avg() 方法,求出所查询数据字段的平均值Db::name('user')->avg('price');使用 sum() 方法,求出所查询数据字段的总和Db::name('user')->sum('price');二. 子查询使用 fetchSql() 方法,可以设置不执行 SQL,而返回 SQLDb::name('user')->fetchSql(true)->select();使用 buidSql() 方法,也是返回 SQL 语句,但不需要再执行 select(),且有括号;Db::name('user')->buildSql(true);结合以上方法,我们实现一个子查询;#one过滤找出所有男性的信息 $subQuery = Db::name('two')->field('uid')->where('gender','男')->buildSql(true); $result = Db::name('one')->where('id','exp','IN '.$subQuery)->select();使用闭包的方式执行子查询;$result = Db::name('one')->where('id', 'in', function ($query) { $query->name('two')->where('gender', '男')->field('uid'); })->select();三. 原生查询使用 query() 方法,进行原生 SQL 查询,适用于读取操作,SQL 错误返回 false;Db::query('select * from tp_user');使用 execute 方法,进行原生 SQL 更新写入等,SQL 错误返回 false;Db::execute('update tp_user set username="孙悟空" where id=29');
2021年07月17日
515 阅读
1 评论
0 点赞
2021-07-17
ThinkPHP5.1的模型中常用的CURD操作
01 模型定义本节课我们来学习模型篇章中的定义方法,设置以及一些基本的操作。一. 定义模型定义一个和数据库表向匹配的模型;class User extends Model模型会自动对应数据表,并且有一套自己的命名规则模型类需要去除表前缀(tp_),采用驼峰式命名,并且首字母大写tp_user(表名) => User tp_user_type(表名) => UserType如果担心设置的模型类名和 PHP 关键字冲突,可以开启应用类后缀;在 app.php 中,设置class_suffix 属性为 true 即可;// 应用类库后缀 'class_suffix' => true,设置完毕后,所有的控制器类名和模型类名需要加上 Controller 和 Model;class UserModel二. 设置模型默认主键为 id,你可以设置其它主键,比如 uid;protected $pk = 'uid';从控制器端调用模型操作,如果和控制器类名重复,可以设置别名use app\model\User as UserModel;在模型定义中,可以设置其它的数据表;protected $table = 'tp_one';模型和控制器一样,也有初始化,在这里必须设置 static 静态方法;//模型初始化 protected static function init() { //第一次实例化的时候执行 init echo '初始化 User 模型'; }三. 模型操作模型操作数据和数据库操作一样,只不过不需要指定表了;UserModel::select();数据库操作返回的列表是一个二维数组,而模型操作返回的是一个结果集;[[]] 和 [{}]02. 模型中的增删改查本节课我们来学习模型中的修改和查询、删除、添加操作。一. 数据添加使用实例化的方式添加一条数据,首先实例化方式如下,两种均可:$user = new UserModel(); $user = new \app\model\User();设置要新增的数据,然后用 save() 方法写入到数据库中,save()返回布尔值$user->username = '李白'; $user->password = '123'; $user->gender = '男'; $user->email = 'libai@163.com'; $user->price = 100; $user->details = '123'; $user->uid = 1011; $user->create_time = date('Y-m-d H:i:s'); $user->save();也可以通过 save() 传递数据数组的方式,来新增数据;$user = new UserModel(); $user->save([ 'username' => '李白', 'password' => '123', 'gender' => '男', 'email' => 'libai@163.com', 'price' => 100, 'details' => '123', 'uid' => 1011, 'create_time' => date('Y-m-d H:i:s') ]);模型新增也提供了 replace() 方法来实现 REPLACE into 新增;$user->replace()->save();当新增成功后,使用$user->id ,可以获得自增 ID(主键需是 id);echo $user->id;使用 saveAll() 方法,可以批量新增数据,返回批量新增的数组;$dataAll = [ [ 'username' => '李白 1', 'password' => '123', 'gender' => '男', 'email' => 'libai@163.com', 'price' => 100, 'details' => '123', 'uid' => 1011, 'create_time' => date('Y-m-d H:i:s') ], [ 'username' => '李白 2', 'password' => '123', 'gender' => '男', 'email' => 'libai@163.com', 'price' => 100, 'details' => '123', 'uid' => 1011, 'create_time' => date('Y-m-d H:i:s') ] ]; $user = new UserModel(); print_r($user->saveAll($dataAll));二. 数据删除使用 get() 方法,通过主键(id)查询到想要删除的数据$user = UserModel::get(93);然后再通过 delete() 方法,将数据删除,返回布尔值;$user->delete();也可以使用静态方法调用 destroy() 方法,通过主键(id)删除数据;UserModel::destroy(92)静态方法 destroy()方法,也可以批量删除数据UserModel::destroy('80, 90, 91'); UserModel::destroy([80, 90, 91]);通过数据库类的查询条件删除;UserModel::where('id', '>', 80)->delete();使用闭包的方式进行删除;UserModel::destroy(function ($query) { $query->where('id', '>', 80); });三. 数据修改使用 get() 方法通过主键获取数据,然后通过 save()方法保存修改,返回布尔值;$user = UserModel::get(118); $user->username = '李黑'; $user->email = 'lihei@163.com'; $user->save();通过 where() 方法结合 find() 方法的查询条件获取的数据,进行修改;$user = UserModel::where('username', '李黑')->find(); $user->username = '李白'; $user->email = 'libai@163.com'; $user->save();save()方法只会更新变化的数据,如果提交的修改数据没有变化,则不更新;但如果你想强制更新数据,即使数据一样,那么可以使用 force() 方法;$user->force()->save();Db::raw() 执行SQL 函数的方式,同样在这里有效;$user->price = Db::raw('price+1');如果只是单纯的增减数据修改,可以使用 inc/dec;$user->price = ['inc', 1];直接通过 save([],[]) 两个数组参数的方式更新数据;$user->save([ 'username' => '李黑', 'email' => 'lihei@163.com' ],['id'=>118]);通过saveAll() 方法,可以批量修改数据,返回被修改的数据集合;$list = [ ['id'=>118, 'username'=>'李白', 'email'=>'libai@163.com'], ['id'=>128, 'username'=>'李白', 'email'=>'libai@163.com'], ['id'=>129, 'username'=>'李白', 'email'=>'libai@163.com'] ]; $user->saveAll($list);批量更新 saveAll() 只能通过主键 id 进行更新使用静态方法结合 update() 方法来更新数据,这里返回的是影响行数UserModel::where('id', 118)->update([ 'username' => '李黑', 'email' => 'lihei@163.com' ]);另外一种静态方法 update(),返回的是对象实例;UserModel::update([ 'id' => 118, 'username' => '李黑', 'email' => 'lihei@163.com' ]);模型的新增和修改都是 save()进行执行的 ,它采用了自动识别体系来完成;实例化模型后调用 save()方法表示新增 ,查询数据后调用 save()表示修改;当然,如果在 save() 传入更新修改条件后也表示修改;再当然,如果编写的代码比较复杂的话,可以用 isUpdate() 方法显示操作;//显示更新 $user->isUpdate(true)->save(); //显示新增 $user->isUpdate(false)->save();四. 数据查询使用 get() 方法,通过主键(id)查询到想要的数据;$user = UserModel::get(129); return json($user);也可以使用 ·where()· 方法进行条件筛选查询数据$user = UserModel::where('username', '辉夜')->find(); return json($user);不管是get() 方法还是 find() 方法,如果数据不存在则返回 Null;和数据库查询一样,模型也有 getOrFail() 方法,数据不存在抛出异常;同上,还有 findOrEmpty() 方法,数据不存在返回空模型;通过模型->符号,可以得到单独的字段数据;return $user->username;如果在模型内部获取数据,请不要用$this->username,而用如下方法;public function getUserName() { return self::where('username', '辉夜')->find()->getAttr('username'); }通过 all() 方法,实现 IN 模式的多数据获取;$user = UserModel::all('79, 118, 128'); $user = UserModel::all([79, 118, 128]);使用链式查询得到想要的数据;UserModel::where('gender', '男')->order('id', 'asc')->limit(2)->select();获取某个字段或者某个列的值;UserModel::where('id', 79)->value('username'); //某个字段 UserModel::whereIn('id',[79,118,128])->column('username','id'); //某个列的值模型支持动态查询:getBy *,* 表示字段名UserModel::getByUsername('辉夜'); UserModel::getByEmail('huiye@163.com');模型支持聚合查询;UserModel::max('price');
2021年07月17日
523 阅读
2 评论
1 点赞
1
2
...
4