Laravel Sail

Laravel Sail 需要 Laravel 8 以后的版本,如果你和我一样用的是 Laravel 7,需要先升级到 Laravel 8

 75 total views,  2 views today

PHP 8

PHP 8.0 发布了,官网甚至还上线了新的宣传页面。作为一个 PHP 程序员,还是要学习一下的。

命名参数

  • 仅仅指定必填参数,跳过可选参数。
  • 参数的顺序无关、自己就是文档(self-documented)
htmlspecialchars($string, ENT_COMPAT | ENT_HTML401, 'UTF-8', false);
htmlspecialchars($string, double_encode: false);

注解

现在可以用 PHP 原生语法来使用结构化的元数据,而非 PHPDoc 声明。

class PostsController
{
    /**
     * @Route("/api/posts/{id}", methods={"GET"})
     */
    public function get($id) { /* ... */ }
}
class PostsController
{
    #[Route("/api/posts/{id}", methods: ["GET"])]
    public function get($id) { /* ... */ }
}

构造器属性提升

更少的样板代码来定义并初始化属性。

class Point {
  public float $x;
  public float $y;
  public float $z;
  public function __construct(
    float $x = 0.0,
    float $y = 0.0,
    float $z = 0.0
  ) {
    $this->x = $x;
    $this->y = $y;
    $this->z = $z;
  }
}
class Point {
  public function __construct(
    public float $x = 0.0,
    public float $y = 0.0,
    public float $z = 0.0,
  ) {}
}

联合类型

相较于以前的 PHPDoc 声明类型的组合, 现在可以用原生支持的联合类型声明取而代之,并在运行时得到校验。

class Number {
  /** @var int|float */
  private $number;
  /**
   * @param float|int $number
   */
  public function __construct($number) {
    $this->number = $number;
  }
}
new Number('NaN'); // Ok
class Number {
  public function __construct(
    private int|float $number
  ) {}
}
new Number('NaN'); // TypeError

Match 表达式

新的 match 类似于 switch,并具有以下功能:

  • Match 是一个表达式,它可以储存到变量中亦可以直接返回。
  • Match 分支仅支持单行,它不需要一个 break; 语句。
  • Match 使用严格比较。
switch (8.0) {
  case '8.0':
    $result = "Oh no!";
    break;
  case 8.0:
    $result = "This is what I expected";
    break;
}
echo $result;
//> Oh no!
echo match (8.0) {
  '8.0' => "Oh no!",
  8.0 => "This is what I expected",
};
//> This is what I expected

Nullsafe 运算符

现在可以用新的 nullsafe 运算符链式调用,而不需要条件检查 null。 如果链条中的一个元素失败了,整个链条会中止并认定为 Null。

$country =  null;
if ($session !== null) {
  $user = $session->user;
  if ($user !== null) {
    $address = $user->getAddress();
 
    if ($address !== null) {
      $country = $address->country;
    }
  }
}
$country = $session?->user?->getAddress()?->country;

字符串与数字的比较更符合逻辑

PHP 8 比较数字字符串(numeric string)时,会按数字进行比较。 不是数字字符串时,将数字转化为字符串,按字符串比较。

0 == 'foobar' // true
0 == 'foobar' // false

内部函数类型错误的一致性

现在大多数内部函数在参数验证失败时抛出 Error 级异常。

strlen([]); // Warning: strlen() expects parameter 1 to be string, array given
array_chunk([], -1); // Warning: array_chunk(): Size parameter expected to be greater than 0
strlen([]); // TypeError: strlen(): Argument #1 ($str) must be of type string, array given
array_chunk([], -1); // ValueError: array_chunk(): Argument #2 ($length) must be greater than 0

即时编译

PHP 8 引入了两个即时编译引擎。 Tracing JIT 在两个中更有潜力,它在综合基准测试中显示了三倍的性能, 并在某些长时间运行的程序中显示了 1.5-2 倍的性能改进。 典型的应用性能则和 PHP 7.4 不相上下。

Just-In-Time compilation
关于 JIT 对 PHP 8 性能的贡献

类型系统与错误处理的改进

  • 算术/位运算符更严格的类型检测 RFC
  • Abstract trait 方法的验证 RFC
  • 确保魔术方法签名正确 RFC
  • PHP 引擎 warning 警告的重新分类 RFC
  • 不兼容的方法签名导致 Fatal 错误 RFC
  • 操作符 @ 不再抑制 fatal 错误。
  • 私有方法继承 RFC
  • Mixed 类型 RFC
  • Static 返回类型 RFC
  • 内部函数的类型 Email thread
  • 扩展 Curl、 Gd、 Sockets、 OpenSSL、 XMLWriter、 XML 以 Opaque 对象替换 resource。

其他语法调整和改进

  • 允许参数列表中的末尾逗号 RFC、 闭包 use 列表中的末尾逗号 RFC
  • 无变量捕获的 catch RFC
  • 变量语法的调整 RFC
  • Namespace 名称作为单个 token RFC
  • 现在 throw 是一个表达式 RFC
  • 允许对象的 ::class RFC

类型系统与错误处理的改进

参考:

https://www.php.net/releases/8.0/index.php

 274 total views,  1 views today

MySQL 窗口函数

MySQL 8.0 引入了 窗口函数(window functions) ,我们看一下都有哪些功能。

NameDescription
CUME_DIST()Cumulative distribution value
DENSE_RANK()Rank of current row within its partition, without gaps
FIRST_VALUE()Value of argument from first row of window frame
LAG()Value of argument from row lagging current row within partition
LAST_VALUE()Value of argument from last row of window frame
LEAD()Value of argument from row leading current row within partition
NTH_VALUE()Value of argument from N-th row of window frame
NTILE()Bucket number of current row within its partition.
PERCENT_RANK()Percentage rank value
RANK()Rank of current row within its partition, with gaps
ROW_NUMBER()Number of current row within its partition

这里我们使用 world 示例数据库进行演示,首先使用 docker 安装 mysql,然后导入 world.sql。

docker run --name db -e MYSQL_ROOT_PASSWORD=123456 -d mysql
docker exec -it db mysql -u root -p < C:\Users\tanghengzhi\Downloads\world.sql




比较常用的有排序:rank(), dense_rank() 和 row_number() 。

mysql> select Name, LifeExpectancy, rank() over (w), dense_rank() over(w), row_number() over(w) 
from country 
where Continent = 'Europe' 
window w as (order by LifeExpectancy desc) 
limit 10;
+---------------+----------------+-----------------+----------------------+----------------------+
| Name          | LifeExpectancy | rank() over (w) | dense_rank() over(w) | row_number() over(w) |
+---------------+----------------+-----------------+----------------------+----------------------+
| Andorra       |           83.5 |               1 |                    1 |                    1 |
| San Marino    |           81.1 |               2 |                    2 |                    2 |
| Switzerland   |           79.6 |               3 |                    3 |                    3 |
| Sweden        |           79.6 |               3 |                    3 |                    4 |
| Iceland       |           79.4 |               5 |                    4 |                    5 |
| Gibraltar     |           79.0 |               6 |                    5 |                    6 |
| Italy         |           79.0 |               6 |                    5 |                    7 |
| Spain         |           78.8 |               8 |                    6 |                    8 |
| France        |           78.8 |               8 |                    6 |                    9 |
| Liechtenstein |           78.8 |               8 |                    6 |                   10 |
+---------------+----------------+-----------------+----------------------+----------------------+
10 rows in set (0.00 sec)




还有第 N 行的值,first_value(), last_value() 和 nth_value()。

mysql> select Name, LifeExpectancy, first_value(Name) over(w) as firth, last_value(Name) over(w) as last, nth_value(Name, 2) over(w) as second, nth_value(Name, 4) over (w) as fourth 
from country 
where Continent = 'Europe' 
window w as (order by LifeExpectancy desc) 
limit 10;
+---------------+----------------+---------+------------+------------+--------+
| Name          | LifeExpectancy | firth   | last       | second     | fourth |
+---------------+----------------+---------+------------+------------+--------+
| Andorra       |           83.5 | Andorra | Andorra    | NULL       | NULL   |
| San Marino    |           81.1 | Andorra | San Marino | San Marino | NULL   |
| Switzerland   |           79.6 | Andorra | Sweden     | San Marino | Sweden |
| Sweden        |           79.6 | Andorra | Sweden     | San Marino | Sweden |
| Iceland       |           79.4 | Andorra | Iceland    | San Marino | Sweden |
| Gibraltar     |           79.0 | Andorra | Italy      | San Marino | Sweden |
| Italy         |           79.0 | Andorra | Italy      | San Marino | Sweden |
| Spain         |           78.8 | Andorra | Monaco     | San Marino | Sweden |
| France        |           78.8 | Andorra | Monaco     | San Marino | Sweden |
| Liechtenstein |           78.8 | Andorra | Monaco     | San Marino | Sweden |
+---------------+----------------+---------+------------+------------+--------+
10 rows in set (0.00 sec)




其他的窗口函数不是很常用,就不一一介绍了,需要用到的请参考官方文档。


接下来是一个在实际使用中遇到的 Bug:

由于涉及到具体业务,这里不方便直接展示相关 SQL 语句。

还是使用演示数据库,查询各大洲的总人口,总人口排名,以及该洲人口最多的国家,按照总人口倒叙排列。

mysql> select Continent, sum(Population), rank() over(order by sum(Population) desc) as `rank`, substring_index(group_concat(Name order by Population desc), ',', 1) as `The most populous country` 
from country 
group by Continent 
order by sum(Population) desc;




MySQL 8.0.16:

+---------------+-----------------+------+----------------------------------------------+
| Continent     | sum(Population) | rank | The most populous country                    |
+---------------+-----------------+------+----------------------------------------------+
| Asia          |      3705025700 |    1 | China                                        |
| Africa        |       784475000 |    2 | Nigeria                                      |
| Europe        |       730074600 |    3 | Russian Federation                           |
| North America |       482993000 |    4 | United States                                |
| South America |       345780000 |    5 | Brazil                                       |
| Oceania       |        30401150 |    6 | Australia                                    |
| Antarctica    |               0 |    7 | South Georgia and the South Sandwich Islands |
+---------------+-----------------+------+----------------------------------------------+
7 rows in set (0.00 sec)




MySQL 8.0.18:

+---------------+-----------------+------+----------------------------------------------+
| Continent     | sum(Population) | rank | The most populous country                    |
+---------------+-----------------+------+----------------------------------------------+
| Asia          |      3705025700 |    1 | South Georgia and the South Sandwich Islands |
| Africa        |       784475000 |    2 | South Georgia and the South Sandwich Islands |
| Europe        |       730074600 |    3 | South Georgia and the South Sandwich Islands |
| North America |       482993000 |    4 | South Georgia and the South Sandwich Islands |
| South America |       345780000 |    5 | South Georgia and the South Sandwich Islands |
| Oceania       |        30401150 |    6 | South Georgia and the South Sandwich Islands |
| Antarctica    |               0 |    7 | South Georgia and the South Sandwich Islands |
+---------------+-----------------+------+----------------------------------------------+
7 rows in set (0.00 sec)




可以看到,同样一个 SQL 语句,在 MySQL 8.0.16 和 MySQL 8.0.18 版本的查询结果不一样的。最新的 MySQL 8.0.22 版本也存在这个问题,目前已经反馈给阿里云,阿里云的小伙伴提交了 MySQL Bug,等待后续解决方案吧。

在这个 Bug 被修复之前,只能先临时修改一下 SQL 语句,如下:

mysql> select Continent, sum(Population), rank() over(w) as `rank`, substring_index(group_concat(Name order by Population desc), ',', 1) as `The most populous country` 
from country 
group by Continent 
window w as (order by sum(Population) desc);

参考:

https://hub.docker.com/_/mysql

https://dev.mysql.com/doc/world-setup/en/

https://dev.mysql.com/doc/refman/8.0/en/mysql-nutshell.html

https://dev.mysql.com/doc/refman/8.0/en/window-functions.html

https://bugs.mysql.com/bug.php?id=101691

 591 total views,  1 views today

Nginx Log Rotation

Nginx 日志切割,在网上查了很多教程。还不如直接看 Ubuntu / CentOS 打包的 Nginx 日志切割配置靠谱。

CentOS 8

sudo dnf install nginx
cat /etc/logrotate.d/nginx
/var/log/nginx/*log {
     create 0664 nginx root
     daily
     rotate 10
     missingok
     notifempty
     compress
     sharedscripts
     postrotate
         /bin/kill -USR1 cat /run/nginx.pid 2>/dev/null 2>/dev/null || true
     endscript
 }

Ubuntu 20.10

sudo apt install nginx
cat /etc/logrotate.d/nginx
/var/log/nginx/*.log {
     daily
     missingok
     rotate 14
     compress
     delaycompress
     notifempty
     create 0640 www-data adm
     sharedscripts
     prerotate
         if [ -d /etc/logrotate.d/httpd-prerotate ]; then \
             run-parts /etc/logrotate.d/httpd-prerotate; \
         fi \
     endscript
     postrotate
         invoke-rc.d nginx rotate >/dev/null 2>&1
     endscript
 }

参考:

https://www.nginx.com/resources/wiki/start/topics/examples/logrotation/

https://www.digitalocean.com/community/tutorials/how-to-configure-logging-and-log-rotation-in-nginx-on-an-ubuntu-vps

 211 total views,  2 views today

PHP Interview Questions(18)

面试时间:2020-02-20,星期四,上午 10:30。


1. 怎么获取字符串倒数第二个字符。

function getSecondLastChar($string) {
    $length = strlen($string);

    if ($length >= 2) {
        return $string[$length - 2];
    } else {
        return '';
    }
}
function getSecondLastChar($string) {
    return $string[-2];
}

2.常用的框架 区别

 1,152 total views

PHP Interview Questions(17)

面试时间:2020年1月7号,星期一,上午十点。


1.What does “&” mean in ‘&$var’?

Passing by Reference

https://www.php.net/manual/en/language.references.pass.php

2.What is MVC?

MVC is a design pattern used to decouple user-interface (view), data (model), and application logic (controller). This pattern helps to achieve separation of concerns.

https://dotnet.microsoft.com/apps/aspnet/mvc

3.What is the difference between $_GET and $_POST?

$_GET — HTTP GET variables
$_POST — HTTP POST variables

https://www.php.net/manual/en/reserved.variables.php

4.What will be the output of each statements below and why?

var_dump(0123 == 123);

var_dump(‘0123’ == 123);

var_dump(‘0123’ === 123);

False
True
False

https://www.php.net/manual/en/language.types.integer.php

5.After the code below is exexuted, what will be the value of $text and what will be strlen($text) return? Explain your answer.

$text = ‘John ‘;

$text[10] = ‘Doe’;

$text = 'John      D';
strlen($text) = 11;

Javascript

1.What is the potential pitfall with using typeof bar == “object” to determine if bar is an object? How can this pitfall be avoided?

typeof null == "object"
bar != null && typeof bar == "object"

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof

2.What is NaN? What is its type? How can you reliably test if a value is equal to NaN?

Not a number
Number
isNaN()

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NaN

3.What is the difference between jQuery.get() and jQuery.ajax()?

$.get(url, data, success, dataType) is a shorthand Ajax function, which is equivalent to:

$.ajax({
  url: url,
  data: data,
  success: success,
  dataType: dataType
});

https://api.jquery.com/jQuery.get/

4.Write a simple function(less than 80 characters) returns a boolean indicating whether or not a string is palindrome.

A palindrome is a word, phrase, number, or other sequence of characters which reads the same backward or forward. Allowances may be made for adjustments to capital letters, punctuation, and word dividers.

console.log(isPalindrome("level"));                    // logs 'true'
console.log(isPalindrome("levels"));                   // logs 'false'
console.log(isPalindrome("A car, a man, a maraca"));   // logs 'true'

function isPalindrome(str) {
    str = str.replace(/\W/g, "").toLowerCase();
    return str.split("").reverse().join("") == str;
}

 5,863 total views

【翻译】MySQL 优化概述

原文链接:https://dev.mysql.com/doc/refman/8.0/en/optimize-overview.html


数据库性能取决于数据库层面的多个因素,例如表、查询和配置。这些软件构造会导致硬件层面的 CPU 和 I/O 操作,你必须尽可能最小化并提高效率。当你刚开始优化数据库性能的时候,首先你要学习软件方面的高级规则和准则,和使用系统时间测量性能。当你成为专家后,你将了解更多的内部细节,并开始测量 CPU 周期和 I/O 操作等指标。

普通用户的目标是从现有的软件和硬件配置中获得最佳的数据库性能。高级用户则会寻找机会来改进 MySQL 软件本身,或者开发自己的存储引擎和硬件设备来扩展 MySQL 生态系统。

数据库层面优化

使数据库应用变快的最重要因素是其基本设计:

表结构是否正常?特别是列是否具有正确的数据类型,并且每个表是否具有适用于工作类型的相应列?例如,需要频繁更新的应用程序通常有许多表,但列很少,而分析大量数据的应用程序通常只有很少的表,但有许多列。

是否使用了正确的索引来提高查询效率?

你是否为每个表选择适当的存储引擎,并且充分利用每种存储引擎的优势和功能?特别是选择事务性存储引擎(如 InnoDB)还是非事务性存储引擎(如 MyISAM)对于性能和可伸缩性非常重要。

注意
InnoDB 是新表的默认存储引擎。实际上,先进的 InnoDB 性能特点意味着 InnoDB 表通常优于更简单的 MyISAM 表,尤其是对于繁忙的数据库。

是否每个表都使用适当的行格式?此选项还取决于用于表的存储引擎。特别是,压缩表占用的磁盘空间较少,因此读取和写入数据所需的磁盘 I/O 更少。压缩可用于具有 InnoDB 表的所有类型的工作负载以及只读的 MyISAM 表。

应用程序是否使用适当的锁定策略?例如,在可能的情况下允许共享访问,以便数据库操作可以同时运行,并在适当时请求独占访问,以便关键操作获得最高优先级。同样,存储引擎的选择也非常重要。InnoDB 存储引擎无需您参与即可处理大多数锁定问题,从而在数据库中实现更好的并发性,并减少代码的实验和调优量。

所有用于缓存的内存区域大小是否正确?也就是说,足够大,可以容纳频繁访问的数据,但规模不够大,以至于使物理内存过载并导致分页。要配置的主要内存区域是 InnoDB 缓冲池和 MyISAM 密钥缓存。

硬件层面优化

随着数据库变得越来越繁忙,任何数据库应用程序最终都达到硬件限制。DBA 必须评估是否可以调整应用程序或重新配置服务器以避免这些瓶颈,或者是否需要更多的硬件资源。系统瓶颈通常来自以下来源:

磁盘查找。磁盘查找数据段需要时间。对于现代磁盘,其平均时间通常低于 10 毫秒,因此理论上我们可以做大约 100 个查找秒。此时间使用新磁盘进行缓慢改进,并且很难针对单个表进行优化。优化寻道时间的方法是将数据分发到多个磁盘上。

磁盘读取和写入。当磁盘处于正确位置时,我们需要读取或写入数据。使用现代磁盘,一个磁盘至少可提供 10*20MB/s 的吞吐量。这比查找更容易优化,因为可以从多个磁盘并行读取。

CPU 周期。当数据位于主内存中时,我们必须处理它以获得结果。与内存量相比,使用大型表是最常见的限制因素。但是,对于小桌子,速度通常不是问题。

内存带宽。当 CPU 需要的数据超过 CPU 缓存容量时,主内存带宽将成为瓶颈。对于大多数系统来说,这是一个不常见的瓶颈,但需要注意。

平衡可移植性和性能

要在便携式 MySQL 程序中使用面向性能的 SQL 扩展,你可以把语句中的 MySQL 特有关键字放到 /*! */ 注释分隔符里面。其他 SQL 服务器会忽略注释的关键字。有关撰写注释的信息,请看 Section 9.6, “Comment Syntax”

 13,520 total views,  4 views today

PHP Interview Questions(14)

面试时间:2019年11月20号,星期三,下午两点。


最近的项目中有使用 Laravel 吗?Laravel 的目录结构。

https://laravel.com/docs/6.x/structure

一道编程测试题。(代码点这里)

With the best of your knowledge, create 2 Apis as following: 

1 - Endpoint: /mail/contact

Description: This endpoint is used to send contact form to a specific email, It needs to use the SMTP server provided to dispatch the email to the target user, the email must include Name, Email, Message, and optionally an Attachment

Required Payload: 

{
   name: String,
   email: String,
   message: String,
   attachment: File
}

Tasks

- [ ] Send email using sendgrid to a configurable user email
- [ ] Handle errors
- [ ] Send attachments
- [ ] Send formatted html emails (can be vary basic using tags like div, b, pre etc)

2 - Endpoint: /mail/subscription

Description: This endpoint is used to store user subscription emails, the goal is to keep every email stored in a persistent storage, where it can be later retrieve for further usage. 

Required Payload: { email: string }

Tasks:

- [ ] Handles the case of duplicated entries
- [ ] Persist on local database

Additional Requirements: 

The apis has to work asynchronously (Non Blocking), follow RESTful conventions, clean code, follow Laravel standards, and lastly don’t use excuses like: “I didn’t do this because it’s was a simple project i didn’t felt it was necessary” we want see what you are capable of so do everything that’s under your knowledge. 

Deliverable:
- A ZIP of the project (without the vendor folder)

说说你对设计模式的理解和你运用设计模式的实例。

设计原则 > 设计模式

装饰器模式

适配器模式

你对PHP新版本的特性有关注吗?7.4 有哪些新特性你比较喜欢。

https://laravel-news.com/tag/php74

// 类型属性
https://wiki.php.net/rfc/typed_properties_v2

// 箭头函数
https://wiki.php.net/rfc/arrow_functions_v2

// 数组扩展运算符
https://wiki.php.net/rfc/spread_operator_for_array

What‘s your favorite framework?Why?

Laravel

Document, Community, Packages, Eloquent ORM(ActiveRecord)

https://www.appclonescript.com/laravel-pros-cons/

What‘s the difference between left join and right join?

Can you explain what‘s Eager Loading?

https://laravel.com/docs/6.x/eloquent-relationships#eager-loading

//If we have 25 books, this loop would run 26 queries

$books = App\Book::all();

foreach ($books as $book) {
    echo $book->author->name;
}

//For this operation, only two queries will be executed
$books = App\Book::with('author')->get();

foreach ($books as $book) {
    echo $book->author->name;
}

 1,640 total views,  1 views today