ホームページ  >  記事  >  バックエンド開発  >  高度な PHP 開発の 10 のヒント

高度な PHP 開発の 10 のヒント

WBOY
WBOYオリジナル
2016-06-20 13:05:081558ブラウズ

高度な PHP 開発の 10 のヒント

PHP の効率を向上させるための 10 の高度な PHP ヒント。

1. SQL インジェクション チートシートを使用する
基本的な経験則は、ユーザーが送信したデータを決して信頼しないことです。
もう 1 つのルールは、データを送信または保存するときにデータをエスケープすることです。
これは、入力フィルター、出力エスケープ (FIEO) として要約できます。
SQL インジェクションの脆弱性の通常の原因は、次のステートメントにあるように、入力がフィルターされていないことです。 ​

<?php
$query = "SELECT *
FROM users
WHERE name = '{$_GET['name']}'";
この例では、$_GET['name'] はユーザーが送信したデータから取得されており、エスケープもフィルターもされていません~~
出力のエスケープに関しては、プログラム外で使用することを目的としたデータはエスケープする必要があることを覚えておく必要があります。エスケープしないと、正しく解析されない可能性があります。
対照的に、入力をフィルタリングすると、使用前にデータが正しいことが保証されます。
入力のフィルタリングと同様に、プログラム外の生データは信頼できないため、フィルタリングする必要があることを覚えておく必要があります。
次の例は、入力フィルタリングと出力エスケープを示しています。 ​

SQL インジェクションを防ぐもう 1 つの効果的な方法は、次のような準備ステートメントを使用することです。
<?php
// Initialize arrays for filtered and escaped data, respectively.
$clean = array();
$sql = array();
// Filter the name. (For simplicity, we require alphabetic names.)
if (ctype_alpha($_GET['name'])) {
$clean['name'] = $_GET['name'];
} else {
// The name is invalid. Do something here.
}
// Escape the name.
$sql['name'] = mysql_real_escape_string($clean['name']);
// Construct the query.
$query = "SELECT *
FROM users
WHERE name = '{$sql['name']}'";
?>


<?php
// Provide the query format.
$query = $db->prepare('SELECT *
FROM users
WHERE name = :name');
// Provide the query data and execute the query.
$query->execute(array('name' => $clean['name']));
?>
2. 比較演算子の違いを理解する
たとえば、strpos() を使用して文字列内に部分文字列が存在するかどうかを確認すると (部分文字列が見つからない場合、関数は FALSE を返します)、結果はエラーになる可能性があります:

上の例では、部分文字列が先頭にあるため、strpos() 関数は正しく 0 を返し、部分文字列が文字列の先頭にあることを示します。次に、条件文は結果をブール型 (Boolean) として扱うため、PHP では 0 が FALSE として計算され、最終的に条件文が失敗します。
<?php
$authors = 'Chris & Sean';
if (strpos($authors, 'Chris')) {
echo 'Chris is an author.';
} else {
echo 'Chris is not an author.';
}
?>
もちろん、このバグは厳密な比較ステートメントを使用して修正できます:


<?php
if (strpos($authors, 'Chris') !== FALSE) {
echo 'Chris is an author.';
} else {
echo 'Chris is not an author.';
}
?>

3. else をショートカットする (else をショートカットする)
変数を使用する前に必ず初期化してください。 ユーザー名に基づいてユーザーが管理者であるかどうかを検出するには、次の条件ステートメントを検討してください:


これなら一目で分かりやすいので安心できそうです。便宜上、名前と電子メールの両方に変数を設定する、より複雑な例を想像してください:
<?php
if (auth($username) == 'admin') {
$admin = TRUE;
} else {
$admin = FALSE;
}
?>


$admin はまだ明示的に TRUE または FALSE に設定されているため、すべて問題ないようです。ただし、後で別の開発者がコードに elseif ステートメントを追加した場合、その開発者はそれを忘れてしまう可能性があります:
<?php
if (auth($username) == 'admin') {
$name = 'Administrator';
$email = 'admin@example.org';
$admin = TRUE;
} else {
/* Get the name and email from the database. */
$query = $db->prepare('SELECT name, email
FROM users
WHERE username = :username');
$query->execute(array('username' => $clean['username']));
$result = $query->fetch(PDO::FETCH_ASSOC);
$name = $result['name'];
$email = $result['email'];
$admin = FALSE;
}
?>


ユーザーが elseif 条件をトリガーするユーザー名を指定した場合、$admin は初期化されず、望ましくない動作、さらにはセキュリティ上の脆弱性が発生する可能性があります。さらに、最初の条件で初期化されていない $moderator 変数にも同様の状況が存在します。
<?php
if (auth($username) == 'admin') {
$name = 'Administrator';
$email = 'admin@example.org';
$admin = TRUE;
} elseif (auth($username) == 'mod') {
$name = 'Moderator';
$email = 'mod@example.org';
$moderator = TRUE;
} else {
/* Get the name and email. */
$query = $db->prepare('SELECT name, email
FROM users
WHERE username = :username');
$query->execute(array('username' => $clean['username']));
$result = $query->fetch(PDO::FETCH_ASSOC);
$name = $result['name'];
$email = $result['email'];
$admin = FALSE;
$moderator = FALSE;
}
?>
$admin と $moderator を初期化することで、これを回避するのは非常に簡単です:


コードの残りの部分に関係なく、明示的に別の値に設定されない限り、$admin の値が FALSE であることがわかります。 $moderator についても同様です。起こり得る最悪の事態は、$admin または $moderator がいかなる条件下でも変更されず、その結果、管理者またはモデレーターである人が、対応する管理者またはモデレーターとして扱われないことです。
<?php
$admin = FALSE;
$moderator = FALSE;
if (auth($username) == 'admin') {
$name = 'Administrator';
$email = 'admin@example.org';
$admin = TRUE;
} elseif (auth($username) == 'mod') {
$name = 'Moderator';
$email = 'mod@example.org';
$moderator = TRUE;
} else {
/* Get the name and email. */
$query = $db->prepare('SELECT name, email
FROM users
WHERE username = :username');
$query->execute(array('username' => $clean['username']));
$result = $query->fetch(PDO::FETCH_ASSOC);
$name = $result['name'];
$email = $result['email'];
}
?>
何かをショートカットしたいのに、例に else が含まれていることを見て少しがっかりした場合は、次のようにします。興味があるかもしれないボーナスのヒントがあります。これが近道と言えるかどうかはわかりませんが、それでも役立つことを願っています。
ユーザーが特定のページを表示する権限を持っているかどうかを検出する関数を考えてみましょう:


この例は非常に単純で、考慮すべきルールは 3 つだけです:
<?php
function authorized($username, $page) {
if (!isBlacklisted($username)) {
if (isAdmin($username)) {
return TRUE;
} elseif (isAllowed($username, $page)) {
return TRUE;
} else {
return FALSE;
}
} else {
return FALSE;
}
}
?>
管理者は常にアクセスを許可されます。
ブラックリストに登録されているユーザーは常にアクセスを禁止されます。
isAllowed() は、他の人がアクセスできるかどうかを判断します。
(管理者がブラックリストに含まれている場合という特殊なケースもありますが、これは考えにくいため、ここではこの状況を無視します)。
コードをシンプルに保ち、ビジネス ロジックに重点を置くために、関数を使用してこの決定を行います。
例:


実際、関数全体を複合条件に減らすことができます:
<?php
function authorized($username, $page) {
if (!isBlacklisted($username)) {
if (isAdmin($username) || isAllowed($username, $page)) {
return TRUE;
} else {
return FALSE;
}
} else {
return FALSE;
}
}
?>


最後に、これは 1 つの戻り値のみに減らすことができます:
<?php
function authorized($username, $page) {
if (!isBlacklisted($username) && (isAdmin($username) || isAllowed($username, $page)) {
return TRUE;
} else {
return FALSE;
}
}
?>


コード行を転写することが目的の場合は、次の方法で実行します。ただし、isBlacklisted()、isAdmin()、および isAllowed() を使用していることに注意してください。これらの判断に含まれる内容によっては、コードを 1 つの複合条件のみに減らすことは魅力的ではない可能性があります。
<?php
function authorized($username, $page) {
return (!isBlacklisted($username) && (isAdmin($username) || isAllowed($username, $page));
}
?>
次に、ちょっとしたトリックである「即時復帰」関数に移ります。したがって、できるだけ早く復帰したい場合は、これらのルールを非常に簡単に表現できます。


この例ではより多くのコード行が使用されていますが、非常にシンプルで目立たないものになっています。さらに重要なのは、このアプローチにより、考慮する必要があるコンテキストの量が減ります。たとえば、ユーザーがブラックリストに登録されているかどうかを判断した後は、そのことを忘れても問題ありません。これは、特にロジックが複雑な場合に非常に役立ちます。
4. 总是使用大括号
PS:原谅是“扔掉那些方括号 Drop Those Brackets”
根据本文的内容, 我们相应作者的意思应该是 “braces,” 而不是brackets. “Curly brackets” 可能有大括号的意思, 但是”brackets” 通常表示 “方括号”的意思。这个技巧应该被无条件的忽略,因为,没有大括号,可读性和可维护性被破坏了。
举一个简单的例子:
 
<?php
if (date('d M') == '21 May')
$birthdays = array('Al Franken',
'Chris Shiflett',
'Chris Wallace',
'Lawrence Tureaud');
?>

If you're good enough, smart enough, secure enough, notorious enough, or pitied enough, 你可能会想在5月21号参加社交聚会:
 
<?php
if (date('d M') == '21 May')
$birthdays = array('Al Franken',
'Chris Shiflett',
'Chris Wallace',
'Lawrence Tureaud');
party(TRUE);
?>


没有大括号,这个简单的条件导致你每天参加社交聚会 。也许你有毅力,因此这个错误是一个受欢迎的。希望那个愚蠢的例子并不分散这一的观点,那就是过度狂欢是一种出人意料的副作用。
为了提倡丢掉大括号,先前的文章使用类似下面的简短的语句作为例子:
 
<?php
if ($gollum == 'halfling') $height --;
else $height ++;
?>


因为每个条件被放在单独的一行, 这种错误似乎会较少发生, 但是这将导致另一个问题:代码的不一致和需要更多的时间来阅读和理解。一致性是这样一个重要的特性,以致开发人员经常遵守一个编码标准,即使他们不喜欢编码标准本身。
我们提倡总是使用大括号:
 
<?php
if (date('d M') == '21 May') {
$birthdays = array('Al Franken',
'Chris Shiflett',
'Chris Wallace',
'Lawrence Tureaud');
party(TRUE);
}
?>


你天天聚会是没关系的,但要保证这是经过思考的,还有,请一定要邀请我们!
5. 尽量用str_replace() 而不是 ereg_replace() 和 preg_replace()
我们讨厌听到的否认的话,但是(原文)这个用于演示误用的小技巧导致了它试图避免的同样的滥用问题。(
We hate to sound disparaging, but this tip demonstrates the sort of misunderstanding that leads to the same misuse it's trying to prevent.)
很明显字符串函数比正则表达式函数在字符匹配方面更快速高效,但是作者糟糕地试图从失败中得出一个推论:
(FIX ME: It's an obvious truth that string functions are faster at string matching than regular expression functions, but the author's attempt to draw a corollary from this fails miserably:)
If you're using regular expressions, then ereg_replace() and preg_replace() will be much faster than str_replace().
Because str_replace() does not support pattern matching, this statement makes no sense. The choice between string functions and regular expression functions comes down to which is fit for purpose, not which is faster. If you need to match a pattern, use a regular expression function. If you need to match a string, use a string function.
6. 使用三重运算符
三元运算符的好处是值得讨论的. 下面是一行从最近我们进行的审计的代码中取出的:
 
<?php
$host = strlen($host) > 0 ? $host : htmlentities($host);
?>


啊,作者的真实意愿是如果字符串的长度大于0 就转义 $host , 但是却意外地做了相反的事情。很容易犯的错误是吧?也许吧。在代码审计过程中很容易错过?当然。简洁并不一定能使代码变得很好。
三重运算符对于单行,原型,和模板也行是适合的,但是我们相信一个普通的条件语句总是更好的。PHP是描述性的和详细的,我们认为代码也应该是。
7. Memcached
磁盘访问是慢速的,网络访问也是慢的,数据库通常使用这二者。
内存是很快的。使用本地缓存可以避免网络和磁盘访问的开销。结合这些道理,然后,你想到了memcached,一个“分布式内存对象缓存系统”,最初为基于Perl的博客平台LiveJournal开发的。
如果你的程序不是分布在多个服务器上,你可能并不需要memcached。单的缓存方法――序列化数据然后将它保存在一个临时文件中。例如 C 对每个请求可以消除很多多余的工作。事实上,这是我们考虑帮助我们的客户优化他们的应用程序时,低挂水果的类型。
什么是low-hanging fruit:
A fruit-bearing tree often contains some branches low enough for animals and humans to reach without much effort. The fruit contained on these lower branches may be not be as ripe or attractive as the fruit on higher limbs, but it is usually more abundant and easier to harvest. From this we get the popular expression “low hanging fruit”, which generally means selecting the easiest targets with the least amount of effort.
一种最简易且最通用的将数据缓存在内存的方式是使用APC中的共享类型辅助方法,APC是一个最初由我们的同事George Schlossnagle开发的缓存系统,考虑如下例子:
 
<?php
$feed = apc_fetch('news');
if ($feed === FALSE) {
$feed = file_get_contents('http://example.org/news.xml');
// Store this data in shared memory for five minutes.
apc_store('news', $feed, 300);
}
// Do something with $feed.
?>

使用这种类型的缓存,你不必在每一次请求时等待远程服务器发送Feed数据。一些延迟产生了 C 在这个例子中上限是五分钟,但可以根据您的应用程序需要调整到接近实时。
8. 使用框架
所有决定都会有结果的,我们喜欢框架――事实上,CakePHP 和 Solar 的主要开发者和我们一起在 OmniTI 工作―― 但是使用一个框架并不会奇迹般地使你在做的东西变得更好。
在十月份,我们的同事Paul Jones为HP Advent写一了篇文章,叫做The Framework as Franchise ,在文章中他将框架与商业专营权相比较。他引用 Michael Gerber “电子神话再现”(”The E-Myth Revisited”) 一书中的建议:
  格柏指出,运行一个成功的企业,企业家需要像他将要卖掉他的企业作为一个特许经营权的原型一样行动。这是企业拥有者可以不亲自参与每一项决策使企业运营的唯一方法。
( Gerber notes that to run a successful business, the entrepreneur needs to act as if he is going to sell his business as a franchise prototype. It is the only way the business owner can make the business operate without him being personally involved in every decision.)
这是一个好的建议。无论你是打算使用框架或者定义你自己的标签和惯例,从未来开发者的角度来看价值是很重要的。
虽然我们很乐意给你一个放之四海而皆准的真理,延伸这个想法来表明一个框架总是合适的,并不是我们想做的事情。
如果你问我们是否应该使用一个框架,我们可以给出的最好的答案是,“这要看情况。”
9. 正确的使用错误抑制操作符
总是试着避免使用错误抑制操作符号。在前面的文章,作者表明:
  @ 操作符是相当的慢的并且如果你需要写高性能的代码的话它会使得开销很大。
错误抑制慢是因为在执行抑制语句前,PHP动态的改变error_reporting等级到0 ,然后然后立即将其还原。这是要开销的。
更糟糕的是,使用错误抑制符使追踪问题的根本原因很困难。
先前的文章使用如下例子来支持通过引用来给一个变量赋值的做法。。。(这句怎么翻译?我晕~~~ )
The previous article uses the following example to support the practice of assigning a variable by reference when it is unknown if $albus is set:
 
<?php
$albert =& $albus;
?>


尽管这样是工作的――对于现在――依靠奇怪的,未定义的行为,而对于为什么这样会工作有一个很好的理解是一个产生BUG的好方法。
因为 $albert 是引用了$albus的,后期对于$albus的修改将会同样影响到$albert .
一个更好的解决方案是使用isset(),加上大括号:
 
<?php
if (!isset($albus)) {
$albert = NULL;
}
?>


给$albert 赋值NULL和给它赋一个不存在的引用的效果是相同的,但是更加明确了,大大提高了代码的清晰度和避免的两个变量之间的引用关系。
If you inherit code that uses the error suppression operator excessively, we've got a bonus tip for you. There is a new PECL extension called Scream that disables error suppression.
10. 使用 isset() 而不是 strlen()
这实际上是一个巧妙的方法,虽然前面的文章完全没有解释这个。下面是补充的例子:
 
<?php
if (isset($username[5])) {
// The username is at least six characters long.
}
?>

当你把字符串当作一个数组时(荒野无灯:事实上,在C语言里面,字符中通常以数组形式存在),字符串里的每一个字符都是数组的一个元素。通过检测一个特定元素的存在与否,你可以检测这个字符串是否至少有那么多的字符存在。(注意第一个字符是元素0,因此 $username[5] 是 $username中的第6个字符。)
这样使用isset 比strlen稍快的原因是复杂的。简单的解释是,strlen() 是一个函数,而 isset() 是一个语法结构。通常来说,
调用一个函数是比使用语言结构的代价更为昂贵的。


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