PHP学习笔记

写评论
PHP学习笔记

PHP学习笔记

卷心菜 卷心菜
摘要 # PHP8新特性JIT [PHP 8新特性之JIT简介](https://www.laruence.com/2020/06/27/5963.html) # OOP的七大设计原则是什么? * 开闭原则:对扩展开放,对修改关闭 * 里

PHP8新特性JIT

PHP 8新特性之JIT简介

OOP的七大设计原则是什么?

  • 开闭原则:对扩展开放,对修改关闭

  • 里氏替换原则:继承 必须保证 父类中的性质在子类中仍然成立

  • 依赖倒置原则:面向接口编程,而不面向实现类

  • 单一职责原则:控制 类的 粒度的大小 ,增强内聚性,减少耦合

  • 接口隔离原则:要为各个类提供所需的专用接口

  • 迪米特法则:迪米特法则(Law of Demeter)又叫作最少知识原则(The Least Knowledge Principle),

  • 一个类对于其他类知道的越少越好,就是说一个对象应当对其他对象有尽可能少的了解,只和朋友通信,不和

  • 陌生人说话。英文简写为: LOD。

  • 合成复用原则:尽可能使用组合或者聚合等关系来关联类,其次才考虑使用继承。

前五个合称 SOLID原则(单一职责原则、开放关闭原则、里氏替换原则、接口隔离原则和依赖倒置原则)

数组相加和array_merge

数组相加,如果第二个数组的某个键在第一个数组有相同的键,第二个数组中的键值对会被忽略,否则被追加在数组后面,例如

<?php

$arr1 = [4, 'apple' => 1, 'orange' => 2];

$arr2 = [3, 4, 'apple' => 2, 5, 6];

var_dump($arr1 + $arr2);

输出结果如下

array(6) {    
  [0]=>       
  int(4)      
  ["apple"]=> 
  int(1)      
  ["orange"]=>
  int(2)      
  [1]=>       
  int(4)      
  [2]=>       
  int(5)      
  [3]=>
  int(6)
}

其中第二个数组中键为0和apple的元素的值被抛弃,其他值被追加在后面。

array_merge 字符串键的值被后面的数组中相同键的值覆盖,索引键追加在后面

<?php

$arr1 = [4, 'apple' => 1, 'orange' => 2];

$arr2 = [3, 4, 'apple' => 2, 5, 6];

var_dump(array_merge($arr1, $arr2));

输出

array(7) {    
  [0]=>       
  int(4)      
  ["apple"]=> 
  int(2)      
  ["orange"]=>
  int(2)      
  [1]=>       
  int(3)      
  [2]=>       
  int(4)      
  [3]=>
  int(5)
  [4]=>
  int(6)
}

文件导出优化

PHP导出CSV

$fp = fopen('./badges.csv', 'w+');  
fputs($fp, chr(239) . chr(187) . chr(191));   // utf-8 BOM头
fputcsv($fp, ['title', 'badge']);

代码示例

set_time_limit(0); // 设置不超时

header("Content-Type: application/octet-stream"); // 声明文件类型 以二进制流文件(可以是任何格式的文件)
// 根据浏览器类型 声明作为附件处理和下载后文件的名称
// Content-Disposition : 以什么方式下载 ; Content-Disposition:attachment :以附件的形式下载
// 例如如果下载的文件是txt 用户下载时保存时命名为 1.txt
if (preg_match("/MSIE/", $_SERVER['HTTP_USER_AGENT'])) {
    header('Content-Disposition:attachment;filename="export.csv"');
} elseif (preg_match("/Firefox/", $_SERVER['HTTP_USER_AGENT'])) {
    header('Content-Disposition:attachment;filename*="export.csv"');
} else {
    header('Content-Disposition:attachment;filename="export.csv"');
}

// 用while(true) 和 sleep模拟分批查询,组装数据的过程
while (true) {
    sleep(1); 
    echo 'hello<br>'; // 模拟数据输出
    ob_flush();
    flush();
}

常用header

header('HTTP/1.1 200 OK');  // ok 正常访问
header('HTTP/1.1 404 Not Found'); //通知浏览器 页面不存在
header('HTTP/1.1 301 Moved Permanently'); //设置地址被永久的重定向 301
header('Location: http://www.ithhc.cn/'); //跳转到一个新的地址
header('Refresh: 10; url=http://www.ithhc.cn/'); //延迟转向 也就是隔几秒跳转
header('X-Powered-By: PHP/6.0.0'); //修改 X-Powered-By信息
header('Content-language: en'); //文档语言
header('Content-Length: 1234'); //设置内容长度
header('Last-Modified: '.gmdate('D, d M Y H:i:s', $time).' GMT'); //告诉浏览器最后一次修改时间
header('HTTP/1.1 304 Not Modified'); //告诉浏览器文档内容没有发生改变
  
###内容类型###
header('Content-Type: text/html; charset=utf-8'); //网页编码
header('Content-Type: text/plain'); //纯文本格式
header('Content-Type: image/jpeg'); //JPG、JPEG
header('Content-Type: application/zip'); // ZIP文件
header('Content-Type: application/pdf'); // PDF文件
header('Content-Type: audio/mpeg'); // 音频文件
header('Content-type: text/css'); //css文件
header('Content-type: text/javascript'); //js文件
header('Content-type: application/json'); //json
header('Content-type: application/pdf'); //pdf
header('Content-type: text/xml'); //xml
header('Content-Type: application/x-shockw**e-flash'); //Flash动画
  
######
  
###声明一个下载的文件###
header('Content-Type: application/octet-stream'); //声明输出的是二进制字节流
header('Accept-Ranges:bytes');//声明浏览器返回大小是按字节进行计算
header('Content-Disposition: attachment; filename="ITblog.zip"');
//声明作为附件处理和下载后文件的名称//告诉浏览器文件的总大小
 
//告诉浏览器文件的总大小
    $fileSize = filesize($filePath);//坑 filesize 如果超过2G 低版本php会返回负数
    header('Content-Length:' . $fileSize); //注意是'Content-Length:' 非Accept-Length
 
header('Content-Transfer-Encoding: binary');
readfile('test.zip');
######
  
###对当前文档禁用缓存###
header('Cache-Control: no-cache, no-store, max-age=0, must-revalidate');
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
######
  
###显示一个需要验证的登陆对话框###
header('HTTP/1.1 401 Unauthorized');
header('WWW-Authenticate: Basic realm="Top Secret"');
######
  
  
###声明一个需要下载的xls文件###
header('Content-Disposition: attachment; filename=ithhc.xlsx');
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
header('Content-Length: '.filesize('./test.xls'));
header('Content-Transfer-Encoding: binary');
header('Cache-Control: must-revalidate');

proc_open使用

main.php

<?php

$proc = proc_open('php sub.php', [
    0 => ['pipe', 'r'],
    1 => ['pipe', 'w'],
], $pipes);

pcntl_async_signals(true); // 如果没有这句,那需要在while循环中调用pcntl_signal_dispatch() 函数
foreach ($pipes as $pipe) {
    stream_set_blocking($pipe, false);
}

pcntl_signal(SIGINT, function () {
    echo "Exit";
    exit;
});

while (true) {
    sleep(1);
    echo stream_get_contents($pipes[1]) . "123";
    fwrite($pipes[0], "World");
    echo "Main\n";
}

sub.php

<?php

while (true) {
    sleep(1);
    echo "Hello";
    echo fread(STDIN, 1024); # 每1秒向标准输出输出Hello
}

EventSource php简单实现

Server-Sent Events 教程

EventSource - Web API 接口参考 | MDN

  • EventStream 的每条消息使用字段 + : + 内容构成。若没有冒号:,则整段将被当成一个字段名;若冒号前没有消息类型,则这是一条注释,会被前端忽略。

  • EventStream 虽然是基于 HTTP,但是在前端使用EventSource接口时,和 WebSocket 一样,并不能自定义 HTTP 请求头。鉴权方面只能通过传输同源下的 Cookie。并不能自定义一个 Authorization 头带 Token

  • 同时,在不使用 HTTP/2 时,会受到浏览器的最大连接数限制。在 Chrome 和 Firefox 浏览器中所有打开选项卡下同域的连接最多只能有 6 个。而使用 HTTP/2 时,HTTP 同一时间内的最大连接数由服务器和客户端之间协商(默认为100)。https://developer.mozilla.org/zh-CN/docs/Server-sent_events/Using_server-sent_events#Event_stream_format

const event = new EventSource("http://localhost:8080/index.php")

event.addEventListener("message", function (event) {
	  console.log(event.data)
});
<?php

header('Content-Type: text/event-stream'); // 主要是这个头部
header("Cache-Control", "no-cache");
header("Connection", "keep-alive");
header("X-Accel-Buffering", "no"); // 这个主要是用于让nginx不要做缓冲

$i = 0;
while (true) {
    sleep(1);
    echo "id: 1\nretry: 3\nevent: message\ndata: " . $i++ . "\n\n";
    ob_flush();
    flush();
}

爬虫,HTML DOM操作

The DomCrawler Component (Symfony Docs)

安装

composer require symfony/dom-crawler

使用

替换img地址

$html = '<img src="https://codeemo.cn/images/logo.png">';
$crawler = new Crawler($html);

foreach ($crawler->filter('img') as $domElement) {
	$domElement->hasAttribute('src');
	$domElement->getAttribute('src');
	$domElement->setAttribute('src', 'https://www.google.com');
}

echo $crawler->html();

这是基于PHP: DOM - Manual 的一个库。功能太强大,建议在大人陪同下使用。

yield 生成器,迭代器,聚合迭代器,协程

语法

$data = (yield $express);

yield 的左边是一个赋值语句,右边可以是值(也可是表达式) 。而yield 会先执行右边的表达式,并把值$value送到生成器外面。当生成器收到值后,会执行yield左边的语句,赋值给$data.

使用

<?php

$generator = (function () {
    $a = yield 'first';
    echo PHP_EOL;var_dump($a);
    $b = yield 'second';
    echo PHP_EOL;var_dump($b);
    $c = yield;
    echo PHP_EOL;var_dump($c);
})();

echo $generator->current();
$generator->next();
echo $generator->current();
$generator->next();
$generator->send('third');

php7之前需要(yield 123)

  • 执行$generator->current(); 会执行第一个yield, 然后暂停

  • 当执行$generator->next()或者$generator->send(null); 会往下执行直到第二个yield

  • 当执行$generator->send('third'); 时候,yield实际是send的值, 将yield的值赋值给$a,所以代码中的$c为third,并且会执行下面的语句, 执行下一个yield,此时再调用$generator->current() 就是下一个yield

  • 当生成器全部迭代完毕可以调用$generator->getReturn() 获取返回值

  • 调用$generator->next()会到达下一个yield

  • 第一次直接send() 会导致yield弹出值丢失

  • yield from 左边不能有接收,因为没有意义,但是可以嵌套

  • $c[] = yield 'name' => 'zhangsan' 支持这样的写法

  • 当$gen迭代器被创建的时候一个rewind()方法已经被隐式调用,rewind执行会导致第一个yield被执行且忽略返回值

$g = function () {
   yield 1;
   yield 2;
};

$g = $g();

var_dump($g->send('test')); // 2

yield from 是一个强大且不可缺少的语法,如果只有 yield 那么就只是有了生成器,有了 yield from 那就有了一根强大的“针”——穿过一个个 生成器,按照call stack 把一个个生成器串了起来。 调用方法用 call_user_func(),调用 生成器用 yield from .

yield from: https://segmentfault.com/a/1190000022754223

协程

<?php

$logger = (function () {
    $fd = fopen('./log.log', 'a+');
    while (true) {
        fwrite($fd, yield);
    }
    fclose($fd);
})();

$logger->send('test'. PHP_EOL);

ZH-CN : https://www.laruence.com/2015/05/28/3038.html EN-US: https://www.npopov.com/2012/12/22/Cooperative-multitasking-using-coroutines-in-PHP.html

中间件的实现

laravel pipeline

$pipes = [function (ArrayObject $passable, $next) {
    $passable->offsetSet('name', 'zhangsan');
    return $next($passable);
}];

$passable = new ArrayObject();

$res = array_reduce($pipes, function ($stack, $pipe) {
    return function ($passable) use ($stack, $pipe) {
        return $pipe($passable, $stack);
    };
}, function ($passable) {
    var_dump('then', $passable);
	
    return 'I am response';
});

var_dump($res($passable));

psr实现(未实现接口,仅供参考)


$requestHandler = new class
{
    protected $pipes;
    protected $then;

    public function __construct()
    {
        $this->pipes = [
            new class
            {
                public function process(ArrayObject $request, $handler)
                {
                    $request->offsetSet('name', 'zhangsan');
                    return $handler->handle($request);
                }
            },
            new class
            {
                public function process(ArrayObject $request, $handler)
                {
                    $request->offsetSet('gender', 'male');
                    return $handler->handle($request);
                }
            }
        ];

        $this->then = function (ArrayObject $request) {
            var_dump($request);
						
            return 'I am response';
        };
    }

    public function handle(ArrayObject $request)
    {
        if ($pipe = array_shift($this->pipes)) {
            return $pipe->process($request, $this);
        }

        return ($this->then)($request);
    }
};

$request = new ArrayObject();
$response = $requestHandler->handle($request);

var_dump($response);

ImagickPDF转图片

$im = new \Imagick();
$im->setResolution(120, 120);
$im->setCompressionQuality(100);
$im->readImage(public_path($originalLocalUrl));
foreach ($im as $key => $var) {
    $var->setImageFormat('jpg');
    $var->setImageCompressionQuality(0);
    $width     = $Var->getImageWidth();  
	$height    = $Var->getImageHeight();  
	$newWidth  = 600;  
	$radio     = $newWidth / $width;  
	$newHeight = $radio * $height;
    $var->resizeImage($newWidth, $newHeight, \Imagick::FILTER_LANCZOS, 1);
    $filename = $key + 1 . '.jpg';
    if ($var->writeImage(rtrim($realFilePath, '/') . '/' . $filename)) {
        $pages[] = ltrim($filePath, '/') . '/' . $filename;
    }
}
$im->clear();
$im->destroy();

下载

兼容 ios Safari 浏览器的下载头部

function download_headers($filename, $charset = 'UTF-8', $mimeType = 'application/octet-stream'): array  
{  
    if (preg_match("/MSIE/", $_SERVER["HTTP_USER_AGENT"])) {  
        $filename   = urlencode($filename);  
        $filename   = str_replace("+", "%20", $filename);// 替换空格  
        $attachment = "attachment; filename=\"{$filename}\"; charset={$charset}";  
    } else if (preg_match("/Firefox/", $_SERVER["HTTP_USER_AGENT"])) {  
        $attachment = 'attachment; filename*=utf-8\'\'' . $filename;  
    } else if (preg_match("/Safari/", $_SERVER["HTTP_USER_AGENT"])) {  
        $filename   = rawurlencode($filename); // 注意:rawurlencode与urlencode的区别  
        $attachment = 'attachment; filename*=utf-8\'\'' . $filename;  
    } else {  
        $attachment = "attachment; filename=\"{$filename}\"; charset={$charset}";  
    }  
  
    return [  
        'Cache-Control'       => 'public, must-revalidate, max-age=0',  
        'Content-Type'        => $mimeType,  
        'Content-Disposition' => $attachment,  
    ];  
}

XDebug + Phpstorm

php.ini 文件中添加如下配置

[xdebug]
zend_extension=<path-to-xdebug>
xdebug.mode = debug
xdebug.client_host = localhost
xdebug.idekey = "PHPSTORM"
xdebug.start_with_request= 1

Phpstorm Run/Debug Configurations 中添加 PHP Web Page ,添加Server ,host填127.0.0.1,port填8000,命令行使用php artisan serve启动服务监听8000端口,点击PhpStormStart Listen for PHP Debug Connections,点击debug按钮开始debug

也可以添加PHP script,然后使用php artisan serve就可以了

THE END

登录 后才能评论~