此文章仅是Laravist群中Abraham同学在日常聊天对PHP、对Laravel、对项目的一些杂谈的简单整理.
Abraham,给人感觉是一位在PHP语言方面理解比较深的人
laravel以及MVC
1.个人粗浅的理解,所谓业务逻辑就是验证表单,发短信,发邮件,查询转成数据库sql等等,这些除了数据库查询之外常用的东西,也包括构造页面的标题了,或者 ajax 接口生成 json 结果这些琐碎的事。
这些东西不该被写在控制器中的原因是根据SOLID原则,类的职责必须是单一的,不该让一个类知道太多的事,也有人管这叫“关注分离”(seprate concerned),当然这是设计模式的思想,落实到实践中的好处有很多。
据个栗子,例如你的应用有照片、文章、笑话等等很多栏目,用户可以在每个栏目下发布对应的内容,你想在它们发送完内容后自动给他们发送一封 email。
如果写在控制器中, 一是违背 dry 原则, 每个控制器中都要有重复的代码, 十分不利于维护,日后你想更换 email 的发送服务,则要修改每个控制器中的 email 发送代码;二 是 email 发送类和控制器类是紧耦合的, 你无法独立去测试其中任何一个部分,你想测试 email 是否跑通就要去执行控制器类, 想测试控制器是否正常工作则必须得有 email 类, 如果项目很大,两个部分是不同的人完成的,工作进度不一样,这样会极大影响开发效率。 控制器依赖 email, 正确的做法是把 email 接口独立出来(先写接口),再去用一个发送类实现他,然后把接口注入到控制器,这样控制器无需关心email 类的细节,只知道那个接口能发送 email 就足够。
在Laravel中,对于这些发邮件之类的可以新建个 service 、或者是 你的 app 名之类的目录, 或者是 repository 目录。 控制器需要依赖他的时候用 laravel 的服务容器在参数中注入他。
正常情况下,控制器代码行数太多,比如多于 10 行。 或者是控制器中有 if 之类的判断,都是不合格的、 要让控制器傻到,接收请求,调用对应组件赋值给变量,把变量传进view 就好。
2.理清了路由模型绑定的逻辑,非常智能。先以数组注册所有绑定, 键为路由变量名,值是闭包。调用控制器之前,判断路由是不是命中了模型绑定,如果命中,执行闭包, 注意这里, 返回的类型是什么, 调用控制器的时候,就反射到哪个参数上。
3.资料库不是必须得加, 小的控制器能直接在里查询数据库就挺好。 以下几种情况,可以考虑加 Repository。
1) 是你要测试控制器的方法,让 orm 和控制器隔离写测试方便
2) 隔离方法之间的责任,降低复杂度,方便维护
我是第二点, 因为要加缓存, 如果把读缓存,判断缓存生效之类的动作写在控制器方法里,对一个只是用来转发的控制器方法来说,他知道太多内容了。
但是中间加资料库, 就可以在资料库中处理这些细节。控制器和资料库之间,都知道资料库实现了特定的接口就行。
不管系统用不用缓存,资料库只要实现对应的接口,控制器在任何时候都不需要修改、 如果要维护和数据有关的东西, 也知道只去找资料库、
先理解“数据流”该怎么处理,就可以更好地理解MVC。正常的数据流应该是这样的M--->C---->V。M是数据提供者,所以业务逻辑应该在M里。C只是分发数据流,以只有一个作用,解耦M和V,让数据和视图分离。
一个功能点一个目录,里面存放和他有关的一切信息,包括 Eloquent 类。
缓存简介
L1是页面级缓存,L2是APC缓存(php5.3以后没有APC了,用鸟哥的yac吧),L3是Redis缓存。缓存全部没命中才会到数据库的,数据库根本就没几个读写。缓存也不敢按时间或者LRU淘汰的,是另外有php命令行的进程推送缓存更新。
opcode缓存自然也是有的,连自动检查php文件修改更新opcode都要关掉,只能通过另外一个进程手动更新opcode。
应该有参数可以控制模板编译缓存(这句话整理者实在不懂)
至于文件缓存不好,试试文件系统下分256*256级目录,里面有好几万个文件的时候,性能有多糟糕。直接上redis吧,虽然有点网络开销
正式项目应该都会有代码发布系统吧,不会直接ftp或者scp传文件吧在发布系统里,统一控制重新编译就可以。关掉各种编译文件的更新检查。 git commit hook + phing
上传图片
图片上传过来,强制处理一次,用ImageMagick处理,所谓的原图也是压缩成大小相等的图片,删掉原图,exif信息存数据库,这样才安全
代码图片事例 --- (整理者:我的注释跟他比起来就是渣)
图片重力旋转
图片的 Exif 中有个Orientation 字段,用来存放照片方向。手机拍照有重力感应,所以照片都带有这个信息,手机的图库会正确识别显示拍照方向,显示出来。所以你要么在PHP识别旋转存储并且去掉exif,要么原样不动保留exif留给前端js显示的时候根据exif信息旋转。
UPYUN:
过年分享的小插件和开源项目及建议
https://github.com/hirak/prestissimo
composer 的并行下载插件,原理是先并行下载,再从缓存里恢复。
极大提升 update 速度。
官方提供的 laravel/laravel 只是基础,你可以按需自由调整。
比如:
https://github.com/laravel/spark
这是 laravel 作者写的项目,虽然没 release,但大家尽量学习下。
通常大家划分目录按以下几种形式(可以同时采用多种)。
1,体现在前台的功能模块(比如邮件,授权,多步认证)
2,体现在编程中的基础通能(文件处理,队列操作之类的)
3,设计模式命名目录,一眼就知道目录作用
这如果展开了说,那就要说的太多了。总之目标就是,保证同一个目录,只放同一类东西。 出了一个问题,知道去哪个目录找文件。提高维护的方便程度。
Mysql 数据表MyISAM还是innodb
从长远角度看,改成innodb后面的可维护性将大大提高
1、MYISAM表锁,性能影响比较大,访问量大的时候尤其明显。
2、MYISAM很容易crash,修复后可能导致数据丢失。
3、MYISAM只能将索引load到内存,不像innodb,innodb索引和数据都可以load到内存。
4、CDB这边对innodb做了专门的优化,innodb比MYISAM更适合在CDB上跑。
实际项目的某些功能看法
1.耦合处理
比如你要给全站的文章进行网址唯一化,根据标题去自动生成拼音网址,存在 slug 列中。 此时垃圾的写法就是在控制器里每个涉及到的方法(增加、修改)都把标题生成拼音,一起存进去,这样当控制器中存在大量其它代码时(缓存、验证等等),a 是会变得臃肿难以维护, b 是控制器类中的修改、增加方法耦合太严重,要改得一起改。 这时候最好的办法是把 slug 过程独立出来。 如果在 serviceprovider 中 listen eloquent.saving* ,用独立的类处理,这样日后你修改 slug 方式,甚至不用改动控制器里的代码。
2.功能是否写在控制器
你的会员注册、 订单确认、 都要发送验证邮件时,写在控制器里是灾难的。详情和上面差不多。
php 框架里的事件机制基本都是四人帮“观察者模式”的简单实现,建议直接去读理论原文。 看点设计模式对写出好代码很重要,laravel 框架本身应用了 5 - 10 种设计模式,对于理解作者的设计也很重要。
可维护性强。 你可以任何更换 slug 类,而完全不用碰 控制器。
也不用非得判断, 你可以在 provider 里 listen 一个自定义事件,注册和订单时 fire , 名字可以随便起。 eloquent 只是默认提供的。
3.在线人数统计
如果你不想用 redis,那继续用 mysql session,按时间查找按用户id去重count一下也行。考虑到性能,可以不对时间加索引,配置个crontab每分钟执行一次,把去重count的结果缓存到某个表的某个字段里。如果用redis,可以找一个单独的 redis 库,访问最频繁的几个页面加入写在线人数的代码,key为 用户uId,value 随便写或者写一些其他需要的信息,ttl 设置成 60 秒。用户过来,通过 pipline 或者 multi 写入 key - value 和 expire。获取在线人数就是 dbsize 这个库,如果访问频率非常高的页面获取在线人数,也可以配置 crontab 把 dbsize 的结果缓存。
Laravel的Eloquent和Facade(整理者水平不够看源代码萌萌的)
eloquent 的默认触发事件是基于 laravel 的事件机制,并不是只有使用数据库才可以用事件。 你可以在程序任何地方触发时间,并且用对应的监听器进行处理。 你可以读一下 eloquent 的基类 model.php, 里面的 saving 其实就是 fire 这个函数的封装。 讨论是很难懂的,最好直接看源码。
Facade 是一个快速调用对象的方法,省掉了你实例化的过程(底层使用的是 ioc 自动邦你 new xxx),框架本身需要更好的测试(不必测试 facade 调用是否成功),所以都是原生写法。
但 facade 这个组件本身也经过成熟测试,所以你可以在客户端代码尽情使用。
为什么laravel中有的函数根本显示不出来,提示不了,但是还是可以使用呢!比如latest(),无论是使用静态方法,还是使用对象调用,phpstorm都不能提示有这个方法,可是竟然能用
阅读面向对象
Tip:Eloquent注意点
$redis = \Redis::connection();
$catelist = $redis->get($foo);
先写 sql 再写 Eloquent,防止 Eloquent 用着爽,数据库遭殃。。。
你的取一条随机记录应该是这样大概
select user_id from table where id >= floor(rand() * max(id))
插播一句想用好 Eloquent 必须得知道方法链中的每个方法的返回值类型是什么。 不然第一个方法 返回数千个记录, 紧接着又进行集合操作。 我觉得运气不好的话,php 甚至会内存超限。
针对Laravel某些功能不如意
框架是基于接口的,如果某个功能框架默认的源码不能实现。 找到相应的实现,可以整个重写,也可以继承他。然后在配置文件里把 provider 替换掉
Guard 这个类是最容易替换的。这算是一个类似控制器的东西,里面注入了各种依赖。 你简单的继承它,把对应的方法覆写就行。
看了下 config/app.php ,貌似没有 Guard , 如果想改 auth 部分, 看来只能重写 AuthServiceProvider 了。
这里面有个 registerAuthenticator() 方法, 在这个方法里做手脚即可,把里面的 UserServiceProvider 和 Guard 任意替换成你自己的实现,希望我说明白了。
主要是配置文件里只暴露一个 AuthServiceProvider ,如果要动 guard 就得覆写我上面说的方法。
如果想取个 salt 什么的,重写默认的 EloquentUserProvider,然后把 config/auth.php 里的 Eloquent 改成自己的。
日常学习进阶
静态工场, 单例模式, 都要求构造方法私有。没事多看看设计模式吧,对写程序帮助真的挺大的。我的建议是把 martin fowlor 的博客的所有文章都读了。他是 java 大牛,属于设计模式的教父级人物,言简意赅。