ホームページ >バックエンド開発 >PHPチュートリアル >現在のphpを理解していますか? (二)

現在のphpを理解していますか? (二)

PHPz
PHPzオリジナル
2017-04-04 16:00:491996ブラウズ

グッドプラクティス、今回は主にいくつかの開発者を選択しましたPHP申請時に使用すべきグッドプラクティスは、特にグッドプラクティスセクション パスワードとフローの 2 つのポイントについて簡単に説明します

2 番目に、標準 PHP-FIG と PSR を知っている場合は、この部分をスキップできます

PHP コンポーネントと フレームワークはたくさんあります、そして発生する問題は、独自に開発されたフレームワークが他のフレームワークとの通信を考慮していないということです。これは、開発者とフレームワーク自体の両方にとって有害で​​す。

PHP-FIG の古い状況を打破してくださいPHP の多くの開発者。フレームワーク
はこの問題を認識し、2009 年の php|tek (人気のある PHP カンファレンス) でそれについて話し合いました。議論の結果、次の結論に達しました: フレームワークの相互運用性を向上させるには、次のような標準が必要です。 php|tek で予期せず出会った PHP フレームワーク開発者は、PHP-FIG と呼ばれる PHP Framework Interop Group を組織しました PHP-FIG はフレームワークを代表する任意の組織であり、そのメンバーは選出されていません。 PHP-FIG に参加し、提案段階で推奨仕様についてフィードバックを送信してください。また、PHP-FIG は必須の規制ではなく、推奨仕様を公開します

PSR とは、PHP Standards Recomm

end

ation の略です。 (PHP 推奨標準) 現在、PHP-FIG は 5 つの推奨仕様をリリースしています:

PSR-1: 基本的なコード スタイル

コミュニティ標準に準拠する

PHP コード

を作成するには、まずこの標準に準拠する必要があります。規格の詳細は書きません。

3.PSR-2: 厳密なコーディング スタイル

PSR-1 に基づいています。この標準は、インデントや中括弧など、何世紀にもわたる多くの問題を解決します。さらに、多くの IDE (PHPStorm など) には、コードの書式設定関数を使用して、コードの書式設定の標準を設定し、コードを記述して、すべてを書式設定します。これにより、推奨仕様に従い、改行、インデント、中括弧などの詳細を修正することができます。標準を設定します 4.PSR-3: ロガー インターフェイス

この推奨仕様は、前の 2 つとは異なります。これは、PHP ロガー コンポーネントが実装できるメソッドを指定するインターフェイスです。 PSR-3 推奨事項に準拠する PHP ロガー コンポーネントには、PsrLogLoggerInterface インターフェイスを実装する PHP クラスが含まれている必要があります。 PSR-3 インターフェイスは、RFC 5424 システム ログ プロトコルを再利用し、実装される 9 つのメソッドを指定します。

<?php
namespace Psr\Log;

interface LoggerInterface
{
  public function emergency($message, array $context = array());
  public function alert($message, array $context = array());
  public function critical($message, array $context = array());
  public function error($message, array $context = array());
  public function warning($message, array $context = array());
  public function notice($message, array $context = array());
  public function info($message, array $context = array());
  public function debug($message, array $context = array());
  public function log($level, $message, array $context = array());
}

各メソッドは、RFC 5424 プロトコルのログ レベルに対応します。

PRS-3 ロガーの使用

独自の PSR-3 ロガーを作成している場合は、すぐにやめてください。なぜなら、すでに優れたロガーコンポーネントがいくつか存在するからです。例: monolog/monolog、直接使用してください。要件を満たせない場合は、これに基づいて拡張することもお勧めします。

5.PSR-4: オートローダー現在のphpを理解していますか? (二)
この推奨事項では、標準的なオートローダー戦略について説明します。オートローダー戦略とは、実行時にオンデマンドで PHP クラス、インターフェイス、またはトレイトを検索し、それらを PHP インタープリターにロードすることを指します。

オートロードが重要な理由

PHP ファイルの先頭に次のようなコードがよく見られますか?

<?php
include &#39;path/to/file1.php&#39;;
include &#39;path/to/file2.php&#39;;
include &#39;path/to/file3.php&#39;;

いくつかの PHP スクリプトをロードするだけの場合は、これらの

関数

(include()、

include_once()、require
()、

require_once

()) を使用すると、非常にうまく作業を完了できます。しかし、1,000 個の PHP スクリプトをインポートしたい場合はどうすればよいでしょうか?

PSR-4 推奨以前は、PHP コンポーネントとフレームワークの作成者は、autoload() 関数と spl_autoload_register() 関数を使用してカスタム オートローダー戦略を登録していました。ただし、各 PHP コンポーネントとフレームワークのオートローダーは、固有のオートローダーを使用します。そのため、多くのコンポーネントを使用する場合にも非常に面倒です。

推荐使用PSR-4自动加载器规范,就是解决这个问题,促进组件实现互操作性。

PSR-4自动加载器策略
PSR-4推荐规范不要求改变代码的实现方式,只建议如何使用文件系统目录结构PHP命名空间组织代码。PSR-4的精髓是把命名空间的前缀和文件系统中的目录对应起来。比如,我可以告诉PHP,\Oreilly\ModernPHP命名空间中的类、接口和性状在物理文件系统的src/目录中,这样PHP就知道,前缀为\Oreilly\ModernPHP的命名空间中的类、接口和性状对应的src/目录里的目录和文件。

如何编写PSR-4自动加载器
如果你在写自己的PSR-4自动加载器,请停下来。我们可以使用依赖管理器Composer自动生成的PSR-4自动加载器。

三、良好实践

1.过滤、验证和转义

过滤HTML

使用htmlentities()函数过滤输入。

<?php
$input = &#39;<p><script>alert("You won the Nigerian lottery!");</script></p>';
echo htmlentities($input, ENT_QUOTES, 'UTF-8');

需要注意的是:默认情况下,htmlentities()函数不会转义单引号,而且也检测不出输入字符串的字符集。正确的使用方式是:第一个参数输入字符串;第二个参数设为ENT_QUOTES常量,转移单引号;第三个参数设为输入字符串的字符集

更多过滤HTML输入的方式,可以使用HTML Purifier库。这个库强健且安全,缺点:慢,且可能难以配置。

SQL查询

构建SQL查询不好的方式:

$sql = sprintf(
  'UPDATE users SET password = "%s" WHERE id = %s',
  $_POST['password'],
  $_GET['id']
);

如果 psasword=abc";-- ,则导致修改了整个users表的记录password都未abc。如果需要在SQL查询中使用输入数据,要使用PDO预处理语句

用户资料信息

A.过滤用户资料中的电子邮件地址
这里会删除除字符、数字和!#$%&'*+-/=?^_{|}~@.[]`之外的所有其他符号。

<?php
$email = &#39;beckjiang@meijiabang.cn&#39;;
$emailSafe = filter_var($email, FILTER_SANITIZE_EMAIL);

B.过滤用户资料中的外国字符

<?php
$string = "外国字符";
$safeString = filter_var(
  $string,
  FILTER_SANITIZE_STRING,
  FILTER_FLAG_STRIP_LOW|FILTER_FLAG_ENCODE_HIGH
);

验证数据

验证数据与过滤不同,验证不会从输入数据中删除信息,而是只确认输入数据是否符合预期。

验证电子邮件地址
我们可以把某个FILTER_VALIDATE_*标志传给filter_var()函数,除了电子邮件地址,还可以验证布尔值、浮点数、整数、IP地址、正则表达式和URL。

<?php
$input = &#39;beckjiang@meijiabang.cn&#39;;
$isEmail = filter_var($input, FILTER_VALIDAE_EMAIL);
if ($isEmail !== false) {
  echo "Success";
} else {
  echo "Fail";
}

2.密码

哈希算法有很多种,例如:MD5SHA1、bcrypt和scrypt。有些算法的速度很快,用于验证数据完整性;有些算法速度则很慢,旨在提高安全性。生成密码和存储密码时需要使用速度慢、安全性高的算法。

目前,经同行审查,最安全的哈希算法是bcrypt。与MD5和SHA1不同,bcrypt是故意设计的很慢。bcrypt算法会自动加盐,防止潜在的彩虹表攻击。bcrypt算法永不过时,如果计算机的运算速度变快了,我们只需提高工作因子的值。

重新计算密码的哈希值
下面是登录用户的脚本:

<?php
session_start();
try {
  // 从请求主体中获取电子邮件地址
  $email = filter_input(INPUT_POST, &#39;email&#39;);

  // 从请求主体中获取密码
  $password = filter_input(INPUT_POST, &#39;password&#39;);

  // 使用电子邮件地址获取用户(注意,这是虚构代码)
  $user = User::findByEmail($email);

  // 验证密码和账户的密码哈希值是否匹配
  if (password_verify($password, $user->password_hash) === false) {
    throw new Exception('Invalid password');
  }

  // 如果需要,重新计算密码的哈希值
  $currentHashAlgorithm = PASSWORD_DEFAULT;
  $currentHashOptions = array('cost' => 15);
  $passwordNeedRehash = password_needs_rehash(
    $user->password_hash,
    $currentHashAlgorithm,
    $currentHashOptions
  );
  if ($passwordNeedsRehash === true) {
    // 保存新计算得出的密码哈希值(注意,这是虚构代码)
    $user->password_hash = password_hash(
      $password,
      $currentHashAlgorithm,
      $currentHashOptions
    );
    $user->save();
  }
  // 把登录状态保存到回话中
  ...
  // 重定向到个人资料页面
  ...

} catch (Exception $e) {
  //异常处理
  ...
}

值得注意的是:在登录前,一定要使用password_needs_rehash()函数检查用户记录中现有的密码哈希值是否过期。如果过期了,要重新计算密码哈希值

PHP5.5.0之前的密码哈希API
如果无法使用PHP5.5.0或以上版本,可以使用安东尼·费拉拉开发的ircmaxell/password-compat组件。这个组件实现了PHP密码哈希API中的所有函数:

  • password_hash()

  • password_get_info()

  • password_needs_rehash()

  • password_verify()

3.日期、时间和时区

DateTime

DateTime类提供一个面向对象接口,用于管理日期和时间。

没有参数,创建的是一个表示当前日期和时间的实例:

<?php
$datetime = new DateTime();

传入参数创建实例:

<?php
$datetime = new DateTime(&#39;2017-01-28 15:27&#39;);

指定格式,静态构造:

<?php
$datetime = DateTime::createFromFormat(&#39;M j, Y H:i:s&#39;, &#39;Jan 2, 2017 15:27:30&#39;);

DateInterval类

DateInterval实例表示长度固定的时间段(比如,“两天”),或者相对而言的时间段(比如,“昨天”)。DateInterval实例用于修改DateTime实例。

使用DateInterval类:

<?php
// 创建DateTime实例
$datetime = new DateTime();

// 创建长度为两周的间隔
$interval = new DateInterval(&#39;P2W&#39;);

// 修改DateTime实例
$datetime->add($interval);
echo $datetime->format('Y-m-d H:i:s');

创建反向的DateInterval实例:

<?php
// 过去一天
$interval = new DateInterval(&#39;-1 day&#39;);

DateTimeZone类

如果应用要迎合国际客户,可能要和时区斗争。

创建、使用时区:

<?php
$timezone = new DateTimeZone(&#39;America/New_York&#39;);
$datetime = new DateTime(&#39;2017-01-28&#39;, $timezone);

实例化之后,也可以使用setTimeZone()函数设置市区:

$datetime->setTimeZone(new DateTimeZone('Asia/Hong_Kong'));

DatePeriod类

有时我们需要迭代处理一段时间内反复出现的一系列日期和时间,DatePeriod类可以解决这种问题。DatePeriod类的构造方法接受三个参数,而且都必须提供:

  • 一个Datetime实例,表示迭代开始时的日期和时间。

  • 一个DateInterval实例,表示到下个日期和时间的间隔。

  • 一个整数,表示迭代的总次数。

DatePeriod实例是迭代器,每次迭代时都会产出一个DateTime实例

使用DatePeriod类:

<?php
$start = new DateTime();
$interval = new DateInterval(&#39;P2W&#39;);
$period = new DatePeriod($start, $interval, 3);

foreach ($period as $nextDateTime) {
  echo $nextDateTime->format('Y-m-d H:i:s'), PHP_EOL;
}

4.数据库

PHP应用可以在很多种数据库中持久保存信息,比如:MySQLSQLite、Oracle等。如果在项目中使用多种数据库,需要安装并学习多种PHP数据库扩展和接口,这增加了认知和技术负担。

正是基于这个原因,PHP原生提供了PDO扩展(PHP Data Objects,意思是PHP数据对象),PDO是一系列PHP类,抽象了不同数据库的具体实现。PDO的介绍和使用就不写了,比较常用。

5.流

在现代的PHP特性中,流或许是最出色但最少使用的。虽然PHP4.3.0就引入了流,但很多开发者不知道流的存在,因为很少人提及流,而且流的文档也匮乏。官方的解释比较难理解,一句话说就是:流的作用是在出发地和目的地之间传输数据

我把流理解为管道,相当于把水从一个地方引到另一个地方。在水从出发地流到目的地的过程中,我们可以过滤水,可以改变水质,可以添加水,也可以排出水(提示:水是数据的隐喻)。

流封装协议

流式数据的种类各异,每种类型需要独特的协议,以便读写数据。我们称这些协议为流封装协议。比如,我们可以读写文件系统,可以通过HTTP、HTTPS或SSH与远程Web服务器通信,还可以打开并读写ZIP、RAR或PHAR压缩文件。这些通信方式都包含下述相同的过程:

  1. 开始通信。

  2. 读取数据。

  3. 写入数据。

  4. 结束通信。

虽然过程一样的,但是读写文件系统中文件的方式与手法HTTP消息的方式有所不同。流封装协议的作用是使用通用的几口封装这些差异。

每个流都有一个协议和一个目标。格式如下:

<scheme>://<target>

说这么多有点懵,先看例子,使用HTTP流封装协议与Flickr API通信:

<?php
$json = file_get_contents(
  &#39;http://api.flickr.com/services/feeds/photos_public.gne?format=json&#39;
);

不要误以为这是普通的网页URL,file_get_contents()函数的字符串参数其实是一个流标识符。http协议会让PHP使用HTTP流封装协议。看起来像是普通的网页URL,是因为HTTP流封装协议就是这样规定的:)。其他流封装协议可能不是这样。

file://流封装协议

我们使用file_get_contents()<a href="http://www.php.cn/wiki/1325.html" target="_blank">fopen</a>()fwrite()<a href="http://www.php.cn/wiki/1303.html" target="_blank">fclose</a>()函数读写文件系统。因为PHP默认使用的流封装协议是file://,所以我们很少认为这些函数使用的是PHP流。

隐式使用file://流封装协议:

<?php
$handle = fopen(&#39;/etc/hosts&#39;, &#39;rb&#39;);
while (feof($handle) !== true) {
  echo fgets($handle);
}
fclose($handle);

显式使用file://流封装协议:

<?php
$handle = fopen(&#39;file:///etc/hosts&#39;, &#39;rb&#39;);
while (feof($handle) !== true) {
  echo fgets($handle);
}
fclose($handle);

流上下文

有些PHP流能接受一些列可选的参数,这些参数叫流上下文,用于定制流的行为。流上下文使用stream_context_create()函数创建。

比如,你知道可以使用file_get_contents()函数发送HTTP POST请求吗?如果想这么做,可以使用一个流上下文对象:

<?php
$requestBody = &#39;{"username": "beck"}&#39;;
$context = stream_context_create(array(
  &#39;http&#39; => array(
    'method' => 'POST',
    'header' => "Content-Type: application/json;charset=utf-8;\r\n" . 
                "Content-Length: " . mb_strlen($requestBody),
    "content" => $requestBody
  )
));
$response = file_get_contents('https://my-api.com/users', false, $context);

过滤器

关于PHP的流,其实真正强大的地方在于过滤、转换、添加或删除流中传输的数据

注意:PHP内置了几个流过滤器:string.rot13、string.toupper、string.tolower和string.strp_tags。这些过滤器没什么用,我们要使用自定义的过滤器。

若想把过滤器附加到现有的流上,要使用stream_filter_append()函数。比如,想要把文件中的内容转换成大写字母,可以使用string.toupper过滤器。书中不建议使用这个过滤器,这里只是演示如何把过滤器附加到流上:

<?php
$handle = fopen(&#39;data.txt&#39;, &#39;rb&#39;);
stream_filter_append($handle, &#39;string.toupper&#39;);
while (feof($handle) !== true) {
  echo fgets($handle); // <-- 输出的全是大写字母
}
fclose($handle);

使用<a href="http://www.php.cn/wiki/377.html" target="_blank">php://</a>filter流封装协议把过滤器附加到流上:

<?php
$handle = fopen(&#39;php://filter/read=string.toupper/resource=data.txt&#39;, &#39;rb&#39;);
while (feof($handle) !== true) {
  echo fgets($handle); // <-- 输出的全是大写字母
}
fclose($handle);

来看个更实际的流过滤器示例,假如我们nginx访问日志保存在rsync.net,一天的访问情况保存在一个日志文件中,而且会使用bzip2压缩每个日志文件,名称格式为:YYYY-MM-DD.log.bz2。某天,领导让我提取过去30天某个域名的访问数据。使用DateTime类和流过滤器迭代bzip压缩的日志文件

<?php
$dateStart = new \DateTime();
$dateInterval = \DateInterval::createFromDateString(&#39;-1 day&#39;);
$datePeriod = new \DatePeriod($dateStart, $dateInterval, 30);//创建迭代器
foreach ($datePeriod as $date) {
  $file = &#39;sftp://USER:PASS@rsync.net/&#39; . $date->format('Y-m-d') . 'log.bz2';
  if (file_exists($file)) {
    $handle = fopen($file, 'rb');
    stream_filter_append($handle, 'bzip2.decompress');
    while (feof($handle) !== true) {
      $line = fgets($handle);
      if (strpos($line, 'www.example.com') !== false) {
        fwrite(STDOUT, $line);
      }
    }
    fclose($handle);
  }
}

计算日期范围,确定日志文件的名称,通过FTP连接rsync.net,下载文件,解压缩文件,逐行迭代每个文件,把相应的行提取出来,然后把访问数据写入一个输出目标。使用PHP流,不到20行代码就能做完所有这些事情。

自定义流过滤器

其实大多数情况下都要使用自定义的流过滤器。自定义的流过滤器是个PHP类,继承内置的php_user_filter类。这个类必须实现filter()onCreate()onClose()方法。而且,必须使用stream_filter_register()函数注册自定义的流过滤器。

PHP流会把数据分成按次序排列的桶,一个桶中盛放的流数据量是固定的。一定时间内过滤器接收到的桶叫做队列。桶队列中的每个桶对象都有两个公开属性:data和datalen,分别是桶中的内容和内容的长度。

下面定义一个处理脏字的流过滤器

<?php
class DirtyWordsFilter extends php_user_filter
{
  /**
   * @param resource $in         流来的桶队列
   * @param resource $out        流走的桶队列
   * @param resource $consumed   处理的字节数
   * @param resource $closing    是流中最后一个桶队列吗?
   */
  public function filter()
  {
    $words = array(&#39;grime&#39;, &#39;dirt&#39;, &#39;grease&#39;);
    $wordData = array();
    foreach ($words as $word) {
      $replacement = array_fill(0, mb_strlen($word), &#39;*&#39;);
      $wordData[$word] = implode(&#39; &#39;, $replacement);
    }
    $bad = array_keys($wordData);
    $goods = array_values($wordData);

    // 迭代流来的桶队列中的每个桶
    while ($bucket = stream_bucket_make_writeable($in)) {
      // 审查桶数据中的脏字
      $bucket->data = str_replace($bad, $goods, $bucket->data);

      // 增加已处理的数据量
      $consumed += $bucket->datalen;

      // 把桶放入流向下游的队列中
      stream_bucket_append($out, $bucket);
    }

    return PSFS_PASS_ON;
  }
}

filter()方法的作用是接受、处理再转运桶中的流数据。这个方法的返回值是PSFS_PASS_ON常量,表示操作成功。

注册流过滤器
接着,我们必须使用stream_filter_register()函数注册这个自定义的DirtWordsFilter流过滤器:

<?php
stream_filter_register('dirty_words_filter', 'DirtWordsFilter');

第一个参数是用于识别这个自定义过滤器的过滤器名,第二个参数是自定义过滤器的类名。

使用DirtWordsFilter流过滤器

<?php
$handle = fopen('data.txt', 'rb');
stream_filter_append($handle, 'dirty_words_filter');
while (feof($handle) !== true) {
  echo fgets($handle); // <-- 输出审查后的文本
}
fclose($handle);

6.错误与异常

对错误和异常的处理,一定要遵守四个规则:

  • 一定要让PHP报告错误。

  • 在开发环境中要显示错误。

  • 在生产环境中不能显示错误。

  • 在开发环境和生产环境中都要记录错误。

错误与异常在日常使用的比较多,就不记录啦。

以上が現在のphpを理解していますか? (二)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。