首页
关于
Search
1
给你10个市场数据调研报告的免费下载网站!以后竞品数据就从这里找!
140 阅读
2
php接口优化 使用curl_multi_init批量请求
131 阅读
3
2024年备考系统架构设计师
102 阅读
4
《从菜鸟到大师之路 ElasticSearch 篇》
102 阅读
5
PHP 文件I/O
89 阅读
php
thinkphp
laravel
工具
开源
mysql
数据结构
总结
思维逻辑
令人感动的创富故事
读书笔记
前端
vue
js
css
书籍
开源之旅
架构
消息队列
docker
教程
代码片段
redis
服务器
nginx
linux
科普
java
c
ElasticSearch
测试
php进阶
php基础
登录
Search
标签搜索
php函数
php语法
性能优化
安全
错误和异常处理
问题
vue
Composer
Session
缓存
框架
Swoole
api
并发
异步
正则表达式
php-fpm
mysql 索引
开发规范
协程
dafenqi
累计撰写
785
篇文章
累计收到
8
条评论
首页
栏目
php
thinkphp
laravel
工具
开源
mysql
数据结构
总结
思维逻辑
令人感动的创富故事
读书笔记
前端
vue
js
css
书籍
开源之旅
架构
消息队列
docker
教程
代码片段
副业
redis
服务器
nginx
linux
科普
java
c
ElasticSearch
测试
php进阶
php基础
页面
关于
搜索到
785
篇与
的结果
2023-08-11
一文详解PHP的内存泄露
一文详解PHP的内存泄露FPM 的黑魔法首先,传统的跑在 FPM 下的 PHP 代码是没有“内存泄漏”一说的,所谓的内存泄漏就是忘记释放内存,导致进程占用的物理内存(附1)持续增长,得益于 PHP 的短生命周期,PHP 内核有一个关键函数叫做php_request_shutdown此函数会在请求结束后,把请求期间申请的所有内存都释放掉,这从根本上杜绝了内存泄漏,极大的提高了 PHPer 的开发效率,同时也会导致性能的下降,例如单例对象,没必要每次请求都重新申请释放这个单例对象的内存。(这也是Swoole等cli方案的优势之一,因为 cli 请求结束不会清理内存)。Cli 下的内存泄漏相信 PHPer 都遇见过这个报错Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 12288 bytes),是由于向 PHP 申请的内存达到了上限导致的,在 FPM 下一定是因为这次 web 请求有大内存块申请,例如 Sql 查询返回一个超大结果集,但在 Cli 下报这个错大概率是因为你的 PHP 代码出现了内存泄漏。常见的泄漏姿势有:向类的静态属性中追加数据,例如://不停的调用foo() 内存就会一直涨function foo(){ ClassA::$pro[] = "the big string";}向 $GLOBAL 全局变量中追加数据,例如://不停的调用foo() 内存就会一直涨function foo(){ $GLOBAL['arr'][] = "the big string";}向函数的静态变量中追加数据,例如://不停的调用foo() 内存就会一直涨function foo(){ static $arr = []; $arr[] = "the big string";}我们需要检测工具有的同学可能会说很简单嘛,把追加的变量在请求结束后unset()掉就可以了。但真实场景远没有你想的那么简单:例一:function foo(){$obj = new ClassA(); //foo函数结束后将自动释放 $obj对象 $obj->pro[] = str_repeat("big string", 1024);}while (1) {foo(); sleep(1);}上述代码 Cli 运行起来会泄漏吗?肉眼来看肯定不会泄漏,因为foo()函数结束后$obj是栈上的对象自动释放掉了,但答案是可能泄漏也可能没泄漏,这取决于ClassA的定义:class classA{public $pro; public function __construct() { $this->pro = &$GLOBALS['arr']; //pro是其他变量的引用 }}如果ClassA的定义是上面的样子,那么这个例子就是泄漏的!!例二:class Test{public $pro = null; function run() { $var = "Im global var now";//此处 $var 是长生命周期。 $http = new \Swoole\Http\Server("0.0.0.0", 9501, SWOOLE_BASE); $http->on("request", function($req, $resp) { //此处没有给类的静态属性赋值,没有给全局变量赋值, //也没有给函数的静态变量赋值,但是这里是泄漏的,因为 $this 变成长生命周期了。 $this->pro[] = str_repeat("big string", 1024); $resp->end("hello world"); }); $http->start(); echo "run done\n"; //输出不了 //这个函数永远不会结束,局部变量也变成了"全局变量" } }(new Test())->run();new Test()的本意虽然是创建一个临时的对象,但是run()方法触发了server->start()方法,代码将不向下执行,run()函数结束不了,run()函数的局部变量$var和临时对象本身都可以视为全局变量了,给其追加数据都是泄漏的!!例三:由于php_request_shutdown的存在,很多 PHP 扩展其实是有内存泄漏的(emalloc 后没有 efree),但是在 FPM 下是可以正常运行的,而这些扩展放到 Cli 下就会有内存泄漏问题,如果没有工具,Cli 下遇到扩展的泄漏问题,那也只能 gg 了-.-!还有就是当我们调用第三方的类库的函数,要传一个参数,这个参数是全局变量,我不知道这个第三方库会不会给这个参数追加数据,一旦追加数据就会产生泄漏,同理别人给我的函数传的参数我也不敢赋值,第三方函数的返回值有没有全局变量我也不知道。综上我们需要一个检测工具,相对于其他语言 PHP 在这个领域是空白的,可以说没有这个工具整个 Cli 生态就无法真正的发展起来,因为复杂的项目都会遇到泄漏问题。Swoole Tracker可以检测泄漏问题,但它是一款商业产品,现在我们决定重构这个工具,把内存泄漏检测的功能(下文简称Leak工具)完全免费给 PHP 社区使用,完善 PHP 生态,回馈社区,下面我将概述它的具体用法和工作原理。Swoole Tracker 用法Leak工具的实现原理是直接拦截系统底层的 emalloc,erealloc,以及 efree 调用,记录一个巨大的指针表,emalloc/erealloc 的时候添加,efree 的时候删除表中的记录,如果请求结束,指针表中仍然有值就证明产生了内存泄漏,不仅能发现 PHP 代码的泄漏,扩展层甚至 PHP 语言层面的泄漏都能发现,从根本上杜绝泄漏问题。使用方式很简单:前往官网下载最新的 tracker(3.0+) 扩展。php.ini 加入以下配置:extension=swoole_tracker.so;总开关apm.enable=1;Leak检测开关apm.enable_malloc_hook=1在 Cli 模式下主业务逻辑一定是可以抽象成循环体函数的,例如Swoole的OnReceive函数,workerman 的OnMessage函数,以及上文例一中的foo()函数, 在循环体主函数(下文简称主函数)最开始加上trackerHookMalloc()调用即可:function foo(){trackerHookMalloc(); //标记主函数,开始hook malloc $obj = new ClassA(); $obj->pro[] = str_repeat("big string", 1024);}while (1) {foo(); sleep(1);}每次调用主函数结束后(第一次调用不会被记录),都会生成一个泄漏的信息到/tmp/trackerleak日志里面。查看泄漏结果在 Cli 命令行调用trackerAnalyzeLeak()函数即可分析泄漏日志,生成泄漏报告,可以直接php -r "trackerAnalyzeLeak();"即可。下面是泄漏报告的格式:没有内存泄漏的情况:[16916 (Loop 5)] ✅ Nice!! No Leak Were Detected In This Loop其中16916表示进程 id,Loop 5表示第 5 次调用主函数生成的泄漏信息有确定的内存泄漏:[24265 (Loop 8)] /Users/guoxinhua/tests/mem_leak/http_server.php:125 => [12928][24265 (Loop 8)] /Users/guoxinhua/tests/mem_leak/http_server.php:129 => [12928][24265 (Loop 8)] ❌ This Loop TotalLeak: [25216]表示第 8 次调用http_server.php的 125 行和 129 行,分别泄漏了 12928 字节内存,总共泄漏了 25216 字节内存。通过调用trackerCleanLeak()可以清除泄漏日志,重新开始。技术特性(技术难点)支持持续增长检测:想象一个场景,第一次请求运行主函数的时候申请 10 字节内存,然后请求结束前释放掉,然后第二次请求申请了 100 字节,请求结束再释放掉,虽然每次都能正确的释放内存但是每次又都申请更多的内存,最终导致内存爆掉,Leak工具支持这种检测,如果某一行代码有N次(默认 5 次)这种行为就会报"可疑的内存泄漏",格式如下:The Possible Leak As Malloc Size Keep Growth:/Users/guoxinhua/tests/mem_leak/hook_malloc_incri.php:39 => Growth Times : [8]; Growth Size : [2304]表示 39 行有 8 次 malloc size 的增长,总共增长了 2304 字节。支持跨 loop 分析://Swoole Http Server的OnRequest回调$http->on("request", function($request, $response) {trackerHookMalloc(); if(isset(classA::$leak['tmp'])){ unset(classA::$leak['tmp']);//每一次loop都释放上一次loop申请的内存}classA::$leak['tmp'] = str_repeat("big string", 1024);//申请内存 并在本次loop结束后不释放 $response->end("hello world");});按照正常的检测泄漏的理论,上述代码每次都会检测出泄漏,因为每次都给classA::$leak['tmp']赋值并在 Loop 结束也没有释放,但实际业务代码经常这样写,并且此代码也是不会产生泄漏的,因为本次 Loop 的泄漏会在下次释放掉,Leak工具会跨相邻 2 个Loop 进行分析,自动对冲上面这种情况的泄漏信息,如果是跨多个 Loop 的释放,会以如下格式输出:[28316 (Loop 2)] /Users/guoxinhua/tests/mem_leak/hook_efree_pre_loop.php:37 => [-12288]Free Pre (Loop 0) : /Users/guoxinhua/tests/mem_leak/hook_efree_pre_loop.php:42 => [12288][28316 (Loop 2)] /Users/guoxinhua/tests/mem_leak/hook_efree_pre_loop.php:42 => [12288][28316 (Loop 2)] ✅ Nice!! No Leak Were Detected In This Loop上述信息表示 Loop 2 释放了 Loop 0 的 12288 字节内存,然后 Loop 2 又申请了 12288 字节内存,总体来说本次 Loop 跑下来没有内存泄漏。支持循环引用情况:首先简单的介绍一下循环引用问题:function foo(){$o = new classA(); $o->pro[] = $o; //foo结束后 $o无法释放,因为自己引用了自己,即循环引用}while (1) {foo(); sleep(1);}因为循环引用,上面的代码每次运行foo()内存都会增长,但是这个代码确实没有内存泄漏的,因为增长到一定程度 PHP 会开启同步垃圾回收,把这种循环引用的内存都释放掉。但是这给Leak工具带来了麻烦,因为$o的变量是延迟释放的,foo()结束后会报泄漏,而这种写法又确实不是泄漏。Swoole Tracker的Leak工具会自动识别上面的情况,会马上释放循环引用的内存,不会造成误报。如果你发现你的进程内存一直涨,开启了 Tracker 的泄漏检测,通过memory_get_usage(false);打印发现内存不涨了,那么证明你的应用存在循环引用,并且本来就没有内存泄漏问题。支持子协程统计:function loop(){ trackerHookMalloc(); classA::$leak[] = str_repeat("big string", 1024);//申请内存go(function() { echo co::getcid() . "child\n"; go(function() { echo co::getcid()."child2\n"; classA::$leak = [];//释放内存 }); });}Co\run(function(){while (1) { loop(); sleep(1); }});上述代码申请的内存会在第二个子协程里面释放,Leak工具会自动识别协程环境,会在所有子协程都结束后才统计汇总,所以上述代码不会有误报情况。支持 defer,context:$http->on("request", function($request, $response) {trackerHookMalloc(); $context = Co::getContext(); $context['data'] = str_repeat("big string", 1024);//context会在协程结束自动释放 classA::$leak[] = str_repeat("big string1", 1024); defer(function() { classA::$leak = [];//注册defer释放内存 }); $response->end("hello world");});Leak工具会自动识别协程环境,如果存在 defer 和 context,会在 defer 执行结束和 context 释放之后再统计汇总,所以上述代码不会有误报情况,当然如果上面没有注册 defer 也会正确的报告泄漏信息。支持旁路函数干扰排除:例如一个进程由主函数响应请求(OnRequest 等),然后还有个定时器在运行(旁路函数),我们希望检测的是主循环函数的泄漏情况,而当主循环函数执行到一半的时候定时器函数执行了,并申请了内存,然后又切回到主循环函数,此时会误报,Leak工具会支持识别出旁路函数然后不收集旁路函数的 malloc 数据。除了上述这些,Leak工具还支持internd string抓取等等,在此不再展开。注意前几次 Loop 的泄漏信息不用管,因为大部分项目都有一些初始化的缓存是不释放的。检测期间尽量不要有并发。由于开启泄漏检测后性能会非常差,不要在 php.ini 中开启apm.enable_malloc_hook = 1压测。和 Swoole Tracker2.x 的检查泄漏原理不一样,不能一起用。一个进程只能有一个地方调用trackerHookMalloc()函数。Swoole4.5.3由于底层 api 有问题,Leak工具无法正常工作,请升级到最新版Swoole或者降级Swoole版本。附件:免费公开课--如何正确查看进程内存占用
2023年08月11日
13 阅读
0 评论
0 点赞
2023-08-11
PHP实现简单RPC
PHP实现简单RPC1.什么是rpcRPC全称为Remote Procedure Call,翻译过来为“远程过程调用”。目前,主流的平台中都支持各种远程调用技术,以满足分布式系统架构中不同的系统之间的远程通信和相互调用。远程调用的应用场景极其广泛,实现的方式也各式各样。2.从通信协议的层面基于HTTP协议的(例如基于文本的SOAP(XML)、Rest(JSON),基于二进制Hessian(Binary))基于TCP协议的(通常会借助Mina、Netty等高性能网络框架)3.从不同的开发语言和平台层面单种语言或平台特定支持的通信技术(例如Java平台的RMI、.NET平台Remoting)支持跨平台通信的技术(例如HTTP Rest、Thrift等)4.从调用过程来看同步通信调用(同步RPC)异步通信调用(MQ、异步RPC)5.常见的几种通信方式远程数据共享(例如:共享远程文件,共享数据库等实现不同系统通信)消息队列RPC(远程过程调用)6.php实现简单的rpc目录结构image.pngrpc服务端<?php/**User: yuzhaoCreateTime: 2018/11/15 下午11:46Description: Rpc服务端 */class RpcServer {/** * User: yuzhao * CreateTime: 2018/11/15 下午11:51 * @var array * Description: 此类的基本配置 */ private $params = [ 'host' => '', // ip地址,列出来的目的是为了友好看出来此变量中存储的信息 'port' => '', // 端口 'path' => '' // 服务目录 ]; /** * User: yuzhao * CreateTime: 2018/11/16 上午12:14 * @var array * Description: 本类常用配置 */ private $config = [ 'real_path' => '', 'max_size' => 2048 // 最大接收数据大小 ]; /** * User: yuzhao * CreateTime: 2018/11/15 下午11:50 * @var nul * Description: */ private $server = null; /** * Rpc constructor. */ public function __construct($params) { $this->check(); $this->init($params); } /** * User: yuzhao * CreateTime: 2018/11/16 上午12:0 * Description: 必要验证 */ private function check() { $this->serverPath(); } /** * User: yuzhao * CreateTime: 2018/11/15 下午11:48 * Description: 初始化必要参数 */ private function init($params) { // 将传递过来的参数初始化 $this->params = $params; // 创建tcpsocket服务 $this->createServer(); } /** * User: yuzhao * CreateTime: 2018/11/16 上午12:0 * Description: 创建tcpsocket服务 */ private function createServer() { $this->server = stream_socket_server("tcp://{$this->params['host']}:{$this->params['port']}", $errno,$errstr); if (!$this->server) exit([ $errno,$errstr ]); } /** * User: yuzhao * CreateTime: 2018/11/15 下午11:57 * Description: rpc服务目录 */ public function serverPath() { $path = $this->params['path']; $realPath = realpath(__DIR__ . $path); if ($realPath === false ||!file_exists($realPath)) { exit("{$path} error!"); } $this->config['real_path'] = $realPath; } /** * User: yuzhao * CreateTime: 2018/11/15 下午11:51 * Description: 返回当前对象 */ public static function instance($params) { return new RpcServer($params); } /** * User: yuzhao * CreateTime: 2018/11/16 上午12:06 * Description: 运行 */ public function run() { while (true) { $client = stream_socket_accept($this->server); if ($client) { echo "有新连接\n"; $buf = fread($client, $this->config['max_size']); print_r('接收到的原始数据:'.$buf."\n"); // 自定义协议目的是拿到类方法和参数(可改成自己定义的) $this->parseProtocol($buf,$class, $method,$params); // 执行方法 $this->execMethod($client, $class, $method, $params); //关闭客户端 fclose($client); echo "关闭了连接\n"; } } } /** * User: yuzhao * CreateTime: 2018/11/16 上午12:19 * @param $class * @param $method * @param $params * Description: 执行方法 */ private function execMethod($client, $class, $method, $params) { if($class && $method) { // 首字母转为大写 $class = ucfirst($class); $file = $this->params['path'] . '/' . $class . '.php'; //判断文件是否存在,如果有,则引入文件 if(file_exists($file)) { require_once $file; //实例化类,并调用客户端指定的方法 $obj = new $class(); //如果有参数,则传入指定参数 if(!$params) { $data = $obj->$method(); } else { $data = $obj->$method($params); } // 打包数据 $this->packProtocol($data); //把运行后的结果返回给客户端 fwrite($client, $data); } } else { fwrite($client, 'class or method error'); } } /** * User: yuzhao * CreateTime: 2018/11/16 上午12:10 * Description: 解析协议 */ private function parseProtocol($buf, &$class, &$method, &$params) { $buf = json_decode($buf, true); $class = $buf['class']; $method = $buf['method']; $params = $buf['params']; } /** * User: yuzhao * CreateTime: 2018/11/16 上午12:30 * @param $data * Description: 打包协议 */ private function packProtocol(&$data) { $data = json_encode($data, JSON_UNESCAPED_UNICODE); } }RpcServer::instance(['host' => '127.0.0.1', 'port' => 8888, 'path' => './api'])->run();rpc 客户端<?php/**User: yuzhaoCreateTime: 2018/11/16 上午12:2Description: Rpc客户端 */class RpcClient {/** * User: yuzhao * CreateTime: 2018/11/16 上午12:21 * @var array * Description: 调用的地址 */ private $urlInfo = array(); /** * RpcClient constructor. */ public function __construct($url) { $this->urlInfo = parse_url($url); } /** * User: yuzhao * CreateTime: 2018/11/16 上午12:2 * Description: 返回当前对象 */ public static function instance($url) { return new RpcClient($url); } public function __call($name, $arguments) { // TODO: Implement __call() method. //创建一个客户端 $client = stream_socket_client("tcp://{$this->urlInfo['host']}:{$this->urlInfo['port']}", $errno, $errstr); if (!$client) { exit("{$errno} : {$errstr} \n"); } $data = [ 'class' => basename($this->urlInfo['path']), 'method' => $name, 'params' => $arguments ]; //向服务端发送我们自定义的协议数据 fwrite($client, json_encode($data)); //读取服务端传来的数据 $data = fread($client, 2048); //关闭客户端 fclose($client); return $data; }}$cli = new RpcClient('http://127.0.0.1:8888/test');echo $cli->tuzisir1()."\n";echo $cli->tuzisir2(array('name' => 'tuzisir', 'age' => 23));提供服务的文件<?php/**User: yuzhaoCreateTime: 2018/11/16 上午12:28Description: */class Test {public function tuzisir1() { return '我是无参方法'; } public function tuzisir2($params) { return $params; }}效果image.png7.RPC的注意事项性能:影响RPC性能的主要在几个方面:1.序列化/反序列化的框架2.网络协议,网络模型,线程模型等安全RPC安全的主要在于服务接口的鉴权和访问控制支持。跨平台跨不同的操作系统,不同的编程语言和平台。
2023年08月11日
16 阅读
0 评论
0 点赞
2023-08-11
最新PHP中的const和define 定义常量的区别
最新PHP中的const和define 定义常量的区别常量是一个简单的标识符。在脚本执行期间该值不能改变(除了所谓的魔术常量,他们其实不是常量)。常量大小写敏感(php 7.4*)。通常常量标识符总是大写的。const 通常用于类成员变量的定义。define不可用于类成员变量的定义,可用于全局常量。const是在编译时定义, 不能在函数,循环及if条件中使用; 函数define() 定义的常量是在执行define()函数时定义的,因此可以在函数内、循环内、if语句内等函数能够被调用的任何地方使用define()函数定义常量。<?phpconst FOO='BAR';//有效的validdefine('FOO', 'BAR');// 有效的validif (1) {const FOO='BAR';//无效的invalid}if (1) {define('FOO', 'BAR');// 有效的valid}3 .const定义的常量时大小写敏感的,而define可通过第三个参数(为true表示大小写不敏感)来指定大小写是否敏感(php7.4被弃用)。例如:<?phpdefine('FOO', 'BAR', true); //不区分大小写的常量的声明已从php7.4被弃用。现在将其TRUE作为第三个参数传递 给define()会生成弃用警告。不建议使用不区分大小写的常量const BAR =123;echo BAR;echo FOO;// BARecho foo; // BARconst的常量命名只能用直白的文本,而define()允许你用任何表达式来对常量命名。这样我们就可以执行以下操作:for ($i = 0; $i < 32; ++$i) {define('BIT_'. $i, 1<< $i);}类常量可见性从 php7.4开始支持.class ConstDemo{const PUBLIC_CONST_A=1;public const PUBLIC_CONST_B=2;protected const PROTECTED_CONST=3;private const PRIVATE_CONST=4;}需要注意的一点是,const可以在class和interface当中使用定义类常量 ,而define是做不到这一点的,但define可以在类方法中使用:class Foo {const BAR=2; // 有效}class Baz {define('QUX', 2); // 无效public function foo(define('QUX', 2); // 有效)}const默认在当前的namespace下定义常量,而使用define则需要你写明整个namespace的完整路径:命名空间的示例namespace A\B\C;// 如果要定义常量 A\B\C\FOO:const FOO = 'BAR';define('A\B\C\FOO', 'BAR');namespace A{const A1 = 1; //处在命名空间A中define('A2', 2); //全局可调用define('A\A3', 3); //处在命名空间A中}namespace B{use const\A\A1;use const\A\A3;echo A1;echo A2; //全局调用echo A3;}
2023年08月11日
8 阅读
0 评论
0 点赞
2023-08-11
谈谈PHP中的匿名函数与闭包
谈谈PHP中的匿名函数与闭包<?php //匿名函数,说白了就是“没有名字的函数”,不多说。给一段代码可能更为明白: //例一,定义匿名函数并调用 $str='world'; $func=function ($str) { echo 'hello '.$str; }; $func($str);// 输出 hello world //例二,定义匿名函数,用use 引入变量并调用 $str='world'; $func=function () use ($str) { echo 'hello '.$str; }; $func();//输出 hello world //例三 //在函数中把匿名函数返回,并且调用它 function getPrintStrFunc() { $func = function ($str) { echo $str; }; return $func; } $printStrFunc = getPrintStrFunc(); $printStrFunc('some string'); // 例四 //把匿名函数当做参数传递,并且调用它,这里实现了先定义参数 ,再传入函数的效果(和普通的函数先定义函数,再传入参数调用相反) function callFunc($func) { $func('some string'); } $printStrFunc = function ($str) { echo $str; }; callFunc($printStrFunc); //也可以直接将匿名函数进行传递。如果你了解js,这种写法可能会很熟悉 callFunc(function ($str) { echo $str; }); // 匿名函数和普通函数最大的区别是在于,匿名函数可以作为一个具体的“值”赋予给变量或者对象属性,其次,由于匿名函数可以被定义在不同地方,使得他可以有效利用他所在的局域内的变量(或者说上下文中的变量)。下面例子中就是这样一种情况。 class foo { public function exec(Closure $callback) { echo $callback(); } } $name = 'nick'; (new foo)->exec(function () use ($name) { return 'hi, '. $name; }); // 输出: hi, nick // 我们可以看到,匿名函数使用了上下文中的变量$name。而实际上,这个匿名函数是在另一个地方被执行(是在foo类里面被执行的)。这样使得我们不必将变量name的值通过参数传递到类foo的exec方法中,而且可以减少在exec方法中不必要的处理逻辑,使得类更容易专注于自己的职责。 // 匿名函数的作用还有很多,要知道,函数定义的时候是不会执行的,除非被调用。上文中例子其实就是这样,我们可以看到,例子中,对$name变量的处理是在方法exec被调用后才发生,且利用了匿名函数被定义时的上下文中的变量。这种特性,我们可以利用来实现一个控制反转(IoC)容器。 /** * 一个简单的IoC容器 */ class Container { protected static $bindings; public static function bind($abstract, Closure $concrete) { static::$bindings[$abstract] = $concrete; } public static function make($abstract) { return call_user_func(static::$bindings[$abstract]); } } /** * 示例用的 talk 类 */ class talk { public function greet($target) { echo 'hi, ' . $target->getName(); } } /** * 示例用的 A 类 */ class A { public function getName() { return 'Nick'; } } /** * 示例用的 B 类 */ class B { public function getName() { return 'Amy'; } } // 以下代码是主要示例代码 // 创建一个talk类的实例 $talk = new talk; // 将A类绑定至容器,命名为foo Container::bind('foo', function () { return new A; }); // 将B类绑定至容器,命名为bar Container::bind('bar', function () { return new B; }); // 通过容器取出实例 $talk->greet(Container::make('foo')); // hi, Nick $talk->greet(Container::make('bar')); // hi, Amy // 上述例子中,只有在通过make方法获取实例的时候,实例才被创建,这样使得我们可以实现容器,我们依照这一特性,还可以更多的实现高级的特性如事件触发等。利用好匿名函数,可以让应用变得更加丰满。 //彩蛋 匿名函数还可以这样调用:) (function ($name) { echo 'My name is ' . $name; })('jack ma'); // 输出: My name is jack ma
2023年08月11日
11 阅读
0 评论
0 点赞
2023-08-11
laravel-admin安装使用
laravel-admin安装使用laravel-admin是一个超级棒的PHP后台框架,只用写极其少量的代码,就可以开发后台功能,不用写方法,不用创建与修改模板,开发速度是其它框架的N倍。堪称用最少的时间构建出功能完善的管理后台!!!安装方法:一.可参考(https://github.com/w3yyb/laravel-admin-skeleton,使用laravel 6)二.使用laravel 71.安装laravel 7.xcomposer create-project --prefer-dist laravel/laravel blog2.mysql新建数据库laraveladmin3.修改.env文件数据库信息DB_CONNECTION=mysqlDB_HOST=127.0.0.1DB_PORT=3306DB_DATABASE=laraveladminDB_USERNAME=rootDB_PASSWORD=××××××4.安装laravel-admincomposer require encore/laravel-adminphp artisan vendor:publish --provider="Encore\Admin\AdminServiceProvider"php artisan admin:install在config/filesystems.php中disks中添加admin:'disks'=> ['local'=> ['driver'=>'local','root'=>storage_path('app'),],'public'=> ['driver'=>'local','root'=>storage_path('app/public'),'url'=>env('APP_URL').'/storage','visibility'=>'public',],'s3'=> ['driver'=>'s3','key'=>env('AWS_ACCESS_KEY_ID'),'secret'=>env('AWS_SECRET_ACCESS_KEY'),'region'=>env('AWS_DEFAULT_REGION'),'bucket'=>env('AWS_BUCKET'),'url'=>env('AWS_URL'),'endpoint'=>env('AWS_ENDPOINT'),],'admin'=> ['driver'=>'local','root'=>public_path('upload'),'visibility'=>'public','url'=>env('APP_URL').'/upload/',],6.运行: php artisan serve打开路由:http://127.0.0.1:8000/admin认证:账号 admin,密码 adminhttp://www.dcatadmin.com/ 这个是 基于laravel-admin开发的,比laravel-admin更好,推荐使用。
2023年08月11日
12 阅读
0 评论
0 点赞
1
...
84
85
86
...
157