标签归档:PHP

Laravel查询在死锁发生时返回空集合

想象一下,您有两个进程同时执行以下PHP代码,将显示什么结果?

\DB::transaction(function (){
   dump(User::where('id',1)->lockForUpdate()->first());
});

令人惊讶的是,一个进程打印正常的查询结果,而另一个进程打印一个空集合(数组)。 返回空集合的代码并未按照我们想的那样触发死锁异常!然而,如果您查阅了transaction方法的源代码,则会发现在发现死锁之后,该方法会自动重试事务。 而且当死锁的数量达到设定值时,最终死锁将被作为异常抛出。 但是,当前这个实际发生死锁的查询看起来却很正常,因为它返回了一个空集合!

这个意外返回的空数组和未抛出的死锁异常实际上是一个古老且棘手的PHP-PDO bug。 在7.4.13发布之前,它已经广泛存在于许多PHP版本中。 有许多与此相关的讨论,例如:

  1. https://bugs.php.net/bug.php?id=76742
  2. https://github.com/php/php-src/pull/5937
  3. https://github.com/php/php-src/pull/6203
  4. https://github.com/php/php-src/commit/b03776adb5bbb9b54731a44377632fcc94a59d2f

在PHP7.4.13之前,你几乎没有办法直接检测到此错误。 切勿尝试打开PDO :: ATTR_EMULATE_PREPARES来解决此问题! 因为此选项将使您所有的PDO查询结果都变成字符串,数据类型的丢失将会导致更严重的后果。

因此,唯一可行的方法是手动编译已修复的PHP源代码,或安装新的预编译版本的PHP7.4.13。

Laravel Union操作后排序错误的问题 Fix the wrong order after using union method in Laravel query

解决方法,在两个子查询中分别加上limit即可。可以使用较大的数确保所有记录返回。

The solution is to use the limit method on the two subqueries. You can use a larger number to ensure that all records are returned.

$orders1 = Order::where('id', '=', $user->id)
    ->where('status', '!=', 0)
    ->orderBy('id', 'asc')
    ->limit(1e8);

$orders2 = Order::where('id', '=', $user->id)
    ->where('status', '=', 0)
    ->orderBy('created_at', 'desc')
    ->limit(1e8)
    ->unionAll($orders1)
    ->get();

 

Laravel 5.5在浏览器中预览Notification渲染 Previewing Laravel Notification In Browser

对于Mail,我们可以通过下面的代码在浏览器中预览Mailables

For Mail, we can preview Mailables in the browser with the following code.

Route::get('/mailable', function () {
    $invoice = App\Invoice::find(1);
    return new App\Mail\InvoicePaid($invoice);
});

然而Notification类中toMail返回的MailMessage实例,Laravel文档中并没有给出直接方法在浏览器中预览,我们可以采取下面的方法。

However, the MailMessage instance returned by toMail method in the Notification class does not give a direct method to preview in the browser in the Laravel document. We can use the following code.

Route::get('/mailable', function () {
    $message = (new \App\Notifications\FooNotification()->toMail("bar");
    return app()->make(\Illuminate\Mail\Markdown::class)->render($message->markdown, $message->data());
});

在Laravel5.8及以上,MailMessage实现了Renderable接口(PR in Github),所以可以直接return MailMessage实例作为响应了。

In Laravel 5.8 and above, MailMessage implements the Renderable interface (PR in Github), so you can directly return the MailMessage instance as a response.

 

在mac上使用vmbox映射本地目录开发laravel时,解决storage目录Permission denied的问题

我目前使用vmbox中的ubuntu虚拟机共享代码目录来开发laravel,在这期间遇到一个古怪的问题,就是发现storage目录下面的一些文件虚拟机里的php-fpm貌似没有权限(Permission denied),包括但不限于storage/app,storage/logs等。

而当我在mac中把这些目录的权限改为777后发现并不能解决问题。其中在storage/framework/cache中,php-fpm的确拥有了在这个目录创建二级目录的权限,但是貌似在他自己创建的目录中,php-fpm又并没有w权限了。

后来登录到ubuntu中测试发现,php-fpm在cache文件夹创建了缓存索引文件夹,所有者居然是root的,而且的确没有其他人的w权限。后来又经过其他测试我发现,在vmbox的共享目录中,无论你在虚拟机里使用哪个用户创建什么文件,它的所有者都是root:root,权限也都如下图所示。

后来这个问题用了很多方法,包括mac这边设置用户组,ubuntu设置用户组,但都没有很好的解决。后来想了一个终极办法,就是让php-fpm以root的权限运行。解决方案参考:https://onlyke.com/html/883.html

让php-fpm以root的权限运行

注意,本方法仅用于开发环境来解决一些奇怪问题或者方便使用,请勿在生产环境让php-fpm以root身份运行,否则一切后果自负。

  1. 编辑/etc/php/7.0/fpm/pool.d/www.conf,把user和group修改为root
    ; Unix user/group of processes 
    ; Note: The user is mandatory. If the group is not set, the default user's group ; will be used. 
    user = root 
    group = root
    
  2. 编辑/lib/systemd/system/php7.0-fpm.service,在ExecStart的–nodaemonize前面加上–allow-to-run-as-root,就像下面这样
    ExecStart=/usr/sbin/php-fpm7.0 --allow-to-run-as-root --nodaemonize...
  3. 执行命令
    systemctl daemon-reload
  4. 重启php-fpm即可
    service php7.0-fpm restart
  5. 最后,可以通过ps命令来查看效果
    ps auwx | grep php

    可以看到,php-fpm已经以root身份运行了。

Nginx+PHP-fpm中开启日志,error_log的有关问题

要保障PHP在error_log中输出日志,要确保以下设置为正确的值

php.ini中

; Common Values:
;   E_ALL (Show all errors, warnings and notices including coding standards.)
;   E_ALL & ~E_NOTICE  (Show all errors, except for notices)
;   E_ALL & ~E_NOTICE & ~E_STRICT  (Show all errors, except for notices and coding standards warnings.)
;   E_COMPILE_ERROR|E_RECOVERABLE_ERROR|E_ERROR|E_CORE_ERROR  (Show only errors)
; Default Value: E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED
; Development Value: E_ALL
; Production Value: E_ALL & ~E_DEPRECATED & ~E_STRICT
; http://php.net/error-reporting
error_reporting = E_ALL & ~E_NOTICE & ~E_STRICT

...

; Besides displaying errors, PHP can also log errors to locations such as a
; server-specific log, STDERR, or a location specified by the error_log
; directive found below. While errors should not be displayed on productions
; servers they should still be monitored and logging is a great way to do that.
; Default Value: Off
; Development Value: On
; Production Value: On
; http://php.net/log-errors
log_errors = On

php-fpm.conf中error_log覆盖了php.ini中的设置

; Error log file
; If it's set to "syslog", log is sent to syslogd instead of being written
; in a local file.
; Note: the default prefix is /var
; Default Value: log/php-fpm.log
error_log = /var/log/php7.0-fpm.log

pool.d/www.conf中,确保开启输出(非常重要)

; Redirect worker stdout and stderr into main error log. If not set, stdout and
; stderr will be redirected to /dev/null according to FastCGI specs.
; Note: on highloaded environement, this can cause some delay in the page
; process time (several ms).
; Default Value: no
catch_workers_output = yes

 

Nginx对新版本PHP7.0.11关于PATH_INFO问题的修正

从这个版本以后,使用下面的配置使得Nginx支持ThinkPHP

server {
	listen		80;
	server_name	xxx.dev.onlyke.com;
	index		index.html index.htm index.php;
	root		/home/nginx/xxx;
	
	location ~ .*\.(gif|jpg|jpeg|png|bmp|ico|webp)$
	{
		expires 30d;
	}
	location ~ .*\.(woff|ttf|svg|otf|eot)$
	{
		expires 180d;
	}
	location ~ .*\.(js|css)?$
	{
		expires 12h;
	}
	location /
	{
		if (!-e $request_filename) { 
			rewrite ^(.*)$ /index.php/$1 last;
			break;
		}
	}
	location ~ \.php($|/)
	{
		fastcgi_pass unix:/run/php/php7.0-fpm.sock;
		fastcgi_index index.php;
		fastcgi_param SCRIPT_FILENAME  $document_root$fastcgi_script_name;
		fastcgi_param PATH_INFO $fastcgi_script_name;
		include	fastcgi_params;
	}
}

 

Ubuntu 配置Node.js+Nginx+PHP+MySQL最新版本

配置环境

#有的vps需要解决php源乱码的问题
sudo apt-get install -y language-pack-en-base
vi /etc/profile
export LANG=en_US.UTF-8
export LC_ALL=en_US.UTF-8
source /etc/profile
#解决php源乱码的问题 ending

#安装add-apt-repository
apt-get install software-properties-common

#安装nodejs源
curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -

#新版本Ubuntu16.04默认支持openssl 1.0.2h
add-apt-repository ppa:nginx/stable

#老版本Ubuntu 14.04建议使用 PPA for NGINX with HTTP/2 on Ubuntu 12.04 LTS and higher,使用下面的源可以同时升级openssl,可以开启http2
#https://launchpad.net/~ondrej/+archive/ubuntu/nginx/
add-apt-repository ppa:ondrej/nginx

#下面安装php7 mysql5.7源
add-apt-repository ppa:ondrej/php
add-apt-repository ppa:ondrej/mysql-5.7

apt-get update
apt-get install nodejs openssl nginx mysql-server php7.3 php7.3-gd php7.3-mbstring php7.3-xml php7.3-zip php7.3-curl php7.3-fpm php7.3-mysql php7.3-bcmath php7.3-dev

#查看openssl版本
openssl version

nginx.conf配置

user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

events {
	worker_connections 768;
	# multi_accept on;
}

http {

	##
	# Basic Settings
	##

	sendfile on;
	tcp_nopush on;
	tcp_nodelay on;
	keepalive_timeout 65;
	types_hash_max_size 2048;
	# server_tokens off;

	# server_names_hash_bucket_size 64;
	# server_name_in_redirect off;

	include /etc/nginx/mime.types;
	default_type application/octet-stream;

	##
	# SSL Settings
	##

	ssl_protocols               TLSv1 TLSv1.1 TLSv1.2;
	ssl_ciphers                 EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
	ssl_prefer_server_ciphers   on;

	client_max_body_size 2m;

	##
	# Logging Settings
	##

	access_log /var/log/nginx/access.log;
	error_log /var/log/nginx/error.log;

	##
	# Gzip Settings
	##

	gzip on;
	gzip_disable "msie6";
	gzip_min_length 1k;
	gzip_buffers 4 16k;
	gzip_comp_level 2;
	gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png font/ttf font/otf image/svg+xml;
	gzip_vary on;

	##
	# Virtual Host Configs
	##

	include /etc/nginx/conf.d/*.conf;
	include /etc/nginx/sites-enabled/*;
}


#mail {
#	# See sample authentication script at:
#	# http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript
# 
#	# auth_http localhost/auth.php;
#	# pop3_capabilities "TOP" "USER";
#	# imap_capabilities "IMAP4rev1" "UIDPLUS";
# 
#	server {
#		listen     localhost:110;
#		protocol   pop3;
#		proxy      on;
#	}
# 
#	server {
#		listen     localhost:143;
#		protocol   imap;
#		proxy      on;
#	}
#}

默认服务器,ip返回403配置

server_tokens off;
proxy_hide_header X-Powered-By;


server {
	listen 80 default_server;
	server_name _;
	return      403;
}

server {
	listen 443 ssl http2 default_server;
	server_name _;
	ssl			on;
	ssl_certificate		/etc/letsencrypt/live/xxx/fullchain.pem;
	ssl_certificate_key	/etc/letsencrypt/live/xxx/privkey.pem;
	return 403;
}

fastcgi_params限制PHP脚本执行目录

#PHP Prohibit cross-Hosting
fastcgi_param  PHP_VALUE  "open_basedir=$document_root:/tmp/";

纯静态配置

server {
	listen		80;
	server_name	xxxx;
	root		/home/nginx/xxxx;
	index		index.html index.htm index.php;
	location ~ .*\.(gif|jpg|jpeg|png|bmp)$
	{
		expires 30d;
	}
	location ~ .*\.(woff|ttf|svg)$
	{
		expires 180d;
	}
	location ~ .*\.(js|css)?$
	{
		expires 12h;
	}
	location /
	{
 	 	 try_files $uri $uri/ =404;
	}
}

PHP一般通用配置

server {
	listen			80;
	server_name		xxxx;
	index			index.php index.html index.htm;
	root			/home/nginx/xxx;
	
	location ~ .*\.(gif|jpg|jpeg|png|bmp)$
	{
		expires 30d;
	}
	location ~ .*\.(woff|ttf|svg)$
	{
		expires 180d;
	}
	location ~ .*\.(js|css)?$
	{
		expires 12h;
	}
	location ~ \.php($|/)
	{
		try_files $uri = 404;
		fastcgi_pass unix:/run/php/php7.0-fpm.sock;
		fastcgi_index index.php;
		fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
		include fastcgi_params;
	}
}

开启fix_pathinfo(一般默认已开启)

#确认php.ini中的如下配置,其实默认已经为1,当框架出现问题时可以来检查一下
cgi.fix_pathinfo = 1

PHP通用框架配置

server {
	listen		80;
	server_name	xxxx;
	index		index.html index.htm index.php;
	root		/home/nginx/xxxx;
	
	location ~ .*\.(gif|jpg|jpeg|png|bmp|ico|webp)$
	{
		expires 30d;
	}
	location ~ .*\.(woff|ttf|svg|otf|eot)$
	{
		expires 180d;
	}
	location ~ .*\.(js|css)?$
	{
		expires 12h;
	}
	location ^~ /.svn
	{
		deny all;
	}
	location ^~ /.git
	{
		deny all;
	}
	location /
	{
		if (!-e $request_filename) { 
			rewrite ^(.*)$ /index.php/$1 last;
			break;
		}
	}
	location ~ \.php($|/)
	{
		fastcgi_split_path_info ^((?U).+.php)(/?.+)$;
		fastcgi_param PATH_INFO $fastcgi_path_info;
		fastcgi_pass unix:/run/php/php7.0-fpm.sock;
		fastcgi_param SCRIPT_FILENAME  $document_root$fastcgi_script_name;
		include	fastcgi_params;
	}
}

SSL+HTTP2的PHP框架配置

server {
	listen				443 ssl http2;
	server_name			xxx;
	index				index.html index.htm index.php;
	root				/home/nginx/xxx;

	ssl_session_cache		shared:SSL:10m;
	ssl_session_timeout		60m;

	ssl_session_tickets		on;

	ssl_certificate			/etc/letsencrypt/live/xxx/fullchain.pem;
	ssl_certificate_key		/etc/letsencrypt/live/xxx/privkey.pem;

	location ~ .*\.(gif|jpg|jpeg|png|bmp|ico|webp)$
	{
		expires 30d;
	}
	location ~ .*\.(woff|ttf|svg|otf|eot)$
	{
		expires 180d;
	}
	location ~ .*\.(js|css)?$
	{
		expires 12h;
	}
	location ^~ /.svn
	{
		deny all;
	}
	location ^~ /.git
	{
		deny all;
	}
	location /
	{
		if (!-e $request_filename) { 
			rewrite ^(.*)$ /index.php/$1 last;
			break;
		}
	}
	location ~ \.php($|/)
	{
		fastcgi_split_path_info ^((?U).+.php)(/?.+)$;
		fastcgi_param PATH_INFO $fastcgi_path_info;
		fastcgi_pass unix:/run/php/php7.0-fpm.sock;
		fastcgi_param SCRIPT_FILENAME  $document_root$fastcgi_script_name;
		include	fastcgi_params;
	}
}