ホームページ > バックエンド開発 > PHPチュートリアル > PHP プログラミングで最もよくある 10 の間違い、PHP プログラミング 10_PHP チュートリアル

PHP プログラミングで最もよくある 10 の間違い、PHP プログラミング 10_PHP チュートリアル

WBOY
リリース: 2016-07-13 10:18:26
オリジナル
791 人が閲覧しました

PHP プログラミングで最もよくある 10 の間違い、PHP プログラミング 10

PHP は非常に人気のあるオープン ソースのサーバーサイド スクリプト言語であり、World Wide Web 上で見られる Web サイトのほとんどは PHP を使用して開発されています。この記事では、PHP 開発で最も一般的な 10 の問題を紹介します。あなたの友人に役立つことを願っています。

エラー 1: foreach ループの後にダングリングポインタが残る

foreach ループで、反復される要素を変更する必要がある場合、または効率を向上させる必要がある場合は、参照を使用するのが良い方法です。

1 2 3 4 5 $arr = array(1, 2, 3, 4); foreach ($arr as &$value) { $value = $value * 2; } // $arr is now array(2, 4, 6, 8)

多くの人が混乱するであろう質問があります。ループが終了した後、$value は破棄されません。$value は実際には配列内の最後の要素への参照です。これを知らないと、その後の $value の使用時に不可解なエラーが発生します。以下のコードを見てください:

1 2 3 4 5 6 7 8 $array = [1, 2, 3]; echo implode(',', $array), "n"; foreach ($array as &$value) {}    // by reference echo implode(',', $array), "n"; foreach ($array as $value) {}     // by value (i.e., copy) echo implode(',', $array), "n";

上記のコードを実行した結果は次のとおりです:

1 2 3 1,2,3 1,2,3 1,2,2

正解でしたか?なぜこのような結果になったのでしょうか?

分析してみましょう。最初のループの後、$value は配列内の最後の要素への参照になります。 2 番目のサイクルが始まります:

  • ステップ 1: $arr[0] を $value にコピーします (この時点では $value は $arr[2] への参照であることに注意してください)、配列は [1,2,1] になります
  • ステップ 2: $arr[1] を $value にコピーすると、配列は [1,2,2] になります
  • ステップ 3: $arr[2] を $value にコピーすると、配列は [1,2,2] になります

要約すると、最終結果は 1,2,2 です

このエラーを回避する最善の方法は、ループの直後に unset 関数を使用して変数を破棄することです。

1 2 3 4 5
$arr = array(1, 2, 3, 4); foreach ($arr as &$value) {     $value = $value * 2; } unset($value);   // $value no longer references $arr[3]
間違い 2: isset() 関数の動作についての間違った理解

isset()関数は、変数が存在しない場合はfalseを返し、変数の値がnullの場合はfalseを返します。この動作は人々を簡単に混乱させる可能性があります。 。 。以下のコードを見てください:

1 2 3 4
$data = fetchRecordFromStorage($storage, $identifier); if (!isset($data['keyShouldBeSet']) {     // do something here if 'keyShouldBeSet' is not set }
このコードを書いた人の本来の意図は、$data['keyShouldBeSet'] が設定されていない場合、対応するロジックが実行されるということなのかもしれません。しかし、問題は、$data['keyShouldBeSet'] が設定されていても、設定値が null であっても、対応するロジックが引き続き実行されることです。これは、コードの本来の意図に沿っていません。

別の例を示します:

1 2 3 4 5 6 7 8 9

上記のコードは、$_POST['active'] が true であると仮定しており、その場合は $postData が設定されるはずなので、isset($postData) は true を返します。逆に、上記のコードは、isset($postData) が false を返す唯一の方法は、$_POST['active'] も false を返す場合であると想定しています。

本当にそうなのですか?もちろん違います!

$_POST['active'] が true を返した場合でも、$postData が null に設定される可能性があり、その場合 isset($postData) は false を返します。これはコードの意図に反します。

上記のコードの本来の目的が $_POST['active'] が true かどうかを検出することだけである場合は、次の実装の方が良いでしょう:

if ($_POST['active']) {     $postData = extractSomething($_POST); } // ... if (!isset($postData)) {     echo 'post not active'; }
1 2 3 4 5 6 7 8 9 if ($_POST['active']) {     $postData = extractSomething($_POST); } // ... if ($_POST['active']) {     echo 'post not active'; }

変数が実際に設定されているかどうかを判断するには(値が設定されていない場合とnullに設定されている場合を区別する場合)、array_key_exists()関数の方が良いかもしれません。上記の最初の例を次のようにリファクタリングします。

1 2 3 4 $data = fetchRecordFromStorage($storage, $identifier); if (! array_key_exists('keyShouldBeSet', $data)) {     // do this if 'keyShouldBeSet' isn't set }

さらに、get_define_vars() 関数と組み合わせると、変数が現在のスコープに設定されているかどうかをより確実に検出できます。

1 2 3

エラー 3: 戻り値と戻り参照の混同

次のコードを考えてみましょう:

if (array_key_exists('varShouldBeSet', get_defined_vars())) {     // variable $varShouldBeSet exists in current scope }
1 2 3 4 5 6 7 8 9 10 11 12 13 class Config {     private $values = [];     public function getValues() {         return $this->values;     } } $config = new Config(); $config->getValues()['test'] = 'test'; echo $config->getValues()['test'];

上記のコードを実行すると、次の内容が出力されます:

1 PHP Notice:  Undefined index: test in /path/to/my/script.php on line 21

何が問題なの?問題は、上記のコードが戻り値と戻り参照を混同していることです。 PHP では、戻り参照を明示的に指定しない限り、PHP は配列のコピーである配列の値を返します。したがって、上記のコードが返された配列に値を割り当てる場合、実際には元の配列ではなく、コピーされた配列に値が割り当てられます。

1 2 3 4 5 6 7 // getValues() returns a COPY of the $values array, so this adds a 'test' element // to a COPY of the $values array, but not to the $values array itself. $config->getValues()['test'] = 'test'; // getValues() again returns ANOTHER COPY of the $values array, and THIS copy doesn't // contain a 'test' element (which is why we get the "undefined index" message). echo $config->getValues()['test'];

以下は、元の配列の代わりにコピーされた配列を出力する可能な解決策です:

1 2 3 $vals = $config->getValues(); $vals['test'] = 'test'; echo $vals['test'];

元の配列を変更するだけ、つまり配列参照を返したいだけの場合はどう対処すればよいでしょうか?方法は、指定された戻り参照を表示することです:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Config {     private $values = [];     // return a REFERENCE to the actual $values array     public function &getValues() {         return $this->values;     } } $config = new Config(); $config->getValues()['test'] = 'test'; echo $config->getValues()['test'];

変更後、上記のコードは期待どおりにテストを出力します。

さらに混乱させる別の例を見てみましょう:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Config {     private $values;     // using ArrayObject rather than array     public function __construct() {         $this->values = new ArrayObject();     }     public function getValues() {         return $this->values;     } } $config = new Config(); $config->getValues()['test'] = 'test'; echo $config->getValues()['test'];

上記のように「未定義のインデックス」エラーが出力されると思ったら大間違いです。コードは通常「test」を出力します。その理由は、PHP はデフォルトで値ではなく参照によってオブジェクトを返すためです。

まとめると、関数を使って値を返すとき、それが値の戻りなのか参照の戻りなのかを把握する必要があります。 PHP のオブジェクトの場合、デフォルトでは参照によって返され、配列と組み込みの基本型はデフォルトで値によって返されます。これは他の言語とは区別する必要があります (多くの言語は配列を参照によって渡します)。

Java や C# などの他の言語と同様、クラス プロパティにアクセスまたは設定するにはゲッターまたはセッターを使用する方が良い解決策です。もちろん、PHP はデフォルトではサポートしていないため、自分で実装する必要があります。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
class Config {     private $values = [];     public function setValue($key, $value) {         $this->values[$key] = $value;     }     public function getValue($key) {         return $this->values[$key];     } } $config = new Config(); $config->setValue('testKey', 'testValue'); echo $config->getValue('testKey');    // echos 'testValue'
上記のコードにより、呼び出し元は配列にパブリックアクセスを与えずに、配列内の任意の値にアクセスしたり、値を設定したりすることができます。それはどんな感じですか:)

エラー 4: ループ内で SQL クエリを実行しています

PHP プログラミングで次のようなコードを見つけることは珍しいことではありません:

1 2 3 4 5
$models = []; foreach ($inputValues as $inputValue) {     $models[] = $valueRepository->findByValue($inputValue); }
もちろん上記のコードには何も問題はありません。問題は、$valueRepository->findByValue() が反復プロセス中に毎回 SQL クエリを実行する可能性があることです。

1

10,000回反復されると、それぞれ10,000回のSQLクエリを実行したことになります。このようなスクリプトがマルチスレッド プログラムで呼び出される場合、システムがハングする可能性があります。 。 。

コードを記述するプロセスでは、SQL クエリを実行するタイミングを理解し、1 つの SQL クエリですべてのデータを取得するように努める必要があります。

上記の間違いを犯しやすいビジネスシナリオがあります。フォームが一連の値 (ID であると想定) を送信すると、すべての ID に対応するデータを取得するために、コードは ID を走査し、それぞれの ID に対して SQL クエリを実行します。コードは次のとおりです。 :

$result = $connection->query("SELECT `x`,`y` FROM `values` WHERE `value`=" . $inputValue);
1 2 3 4 5 $data = []; foreach ($ids as $id) {     $result = $connection->query("SELECT `x`, `y` FROM `values` WHERE `id` = " . $id);     $data[] = $result->fetch_row(); }

ただし、同じ目的を SQL でより効率的に達成できます。コードは次のとおりです。

1 2 3 4 5 6 7 $data = []; if (count($ids)) {     $result = $connection->query("SELECT `x`, `y` FROM `values` WHERE `id` IN (" . implode(',', $ids));     while ($row = $result->fetch_row()) {         $data[] = $row;     } }

間違い 5: 非効率なメモリ使用と錯覚

各クエリで 1 つのレコードを取得するよりも、1 つの SQL クエリで複数のレコードを取得する方が確実に効率的ですが、PHP で MySQL 拡張機能を使用している場合、一度に複数のレコードを取得するとメモリ オーバーフローが発生する可能性があります。

実験するコードを書くことができます (テスト環境: 512MB RAM、MySQL、php-cli):

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 // connect to mysql $connection = new mysqli('localhost', 'username', 'password', 'database'); // create table of 400 columns $query = 'CREATE TABLE `test`(`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT'; for ($col = 0; $col < 400; $col++) { $query .= ", `col$col` CHAR(10) NOT NULL"; } $query .= ');'; $connection->query($query); // write 2 million rows for ($row = 0; $row < 2000000; $row++) { $query = "INSERT INTO `test` VALUES ($row"; for ($col = 0; $col < 400; $col++) { $query .= ', ' . mt_rand(1000000000, 9999999999); } $query .= ')'; $connection->query($query); }

次に、リソース消費を見てみましょう:

1 2 3 4 5 6 7 8 9 // connect to mysql $connection = new mysqli('localhost', 'username', 'password', 'database'); echo "Before: " . memory_get_peak_usage() . &amp;quot;n&amp;quot;; $res = $connection->query('SELECT `x`,`y` FROM `test` LIMIT 1'); echo "Limit 1: " . memory_get_peak_usage() . &amp;quot;n&amp;quot;; $res = $connection->query('SELECT `x`,`y` FROM `test` LIMIT 10000'); echo "Limit 10000: " . memory_get_peak_usage() . &amp;quot;n&amp;quot;;

出力結果は次のとおりです:

1 2 3 Before: 224704 Limit 1: 224704 Limit 10000: 224704

メモリ使用量から判断すると、すべて正常のようです。より確実にするには、一度に 100000 レコードを取得しようとすると、プログラムは次の出力を取得します。

1 2
PHP Warning:  mysqli::query(): (HY000/2013):               Lost connection to MySQL server during query in /root/test.php on line 11
何が起こっているのですか?

問題は、PHP の mysql モジュールの動作方法にあります。mysql モジュールは、実際には libmysqlclient のプロキシです。複数のレコードを取得するためにクエリを実行すると、これらのレコードはメモリに直接保存されます。このメモリはPHPのメモリモジュールで管理されていないため、memory_get_peak_usage()関数を呼び出して得られる値が実際のメモリ使用量の値ではないため、上記問題が発生します。

mysql の代わりに mysqlnd を使用できます。Mysqlnd は PHP 独自の拡張機能にコンパイルされ、そのメモリ使用量は PHP メモリ管理モジュールによって制御されます。 mysqlnd を使用して上記のコードを実装すると、メモリ使用量がより現実的に反映されます:

1 2 3

さらに悪いことに、PHP の公式ドキュメントによると、クエリ データを保存するために MySQL 拡張機能が使用するメモリは MySQLnd のメモリの 2 倍であるため、元のコードは上記のメモリの約 2 倍を使用することになります。

このような問題を回避するには、クエリを数回に分けて完了して 1 回のクエリのデータ量を減らすことを検討できます。

Before: 232048 Limit 1: 324952 Limit 10000: 32572912
1 2 3 4 5 6 7 8 $totalNumberToFetch = 10000; $portionSize = 100; for ($i = 0; $i <= ceil($totalNumberToFetch / $portionSize); $i++) { $limitFrom = $portionSize * $i; $res = $connection->query(                          "SELECT `x`,`y` FROM `test` LIMIT $limitFrom, $portionSize"); }

上記のエラー4を踏まえると、実際のコーディングプロセスでは、機能要件を満たすだけでなく、パフォーマンスを確保するためにバランスをとる必要があることがわかります。

間違い 6: Unicode/UTF-8 の問題を無視する

PHP プログラミングでは、非 ASCII 文字を扱うときにいくつかの問題が発生します。慎重に扱わないと、どこでもエラーが発生します。簡単な例として strlen($name) を考えます。$name に非 ASCII 文字が含まれている場合、結果は多少予想外になります。このような問題を回避するための提案をいくつか示します:

  • Unicode と utf-8 についてあまり詳しくない場合でも、少なくともいくつかの基本を理解する必要があります。この記事を読むことをお勧めします。
  • 文字列の処理には mb_* 関数を使用し、古い文字列処理関数の使用を避けることが最善です。 PHP の「マルチバイト」拡張機能がオンになっていることを確認してください。
  • データベースとテーブルには Unicode エンコードを使用するのが最善です。
  • jason_code() 関数は非 ASCII 文字を変換しますが、serialize() 関数は変換しないことに注意してください。
  • php コードのソース ファイルは、BOM なしの UTF-8 形式を使用するのが最適です。

そのような問題をより詳しく紹介しているこちらの記事をお勧めします: UTF-8 Primer for PHP and MySQL

間違い 7: $_POST には常に POST データが含まれていると仮定します

PHPの$_POSTには、フォームPOSTで送信されたデータが必ずしも含まれているわけではありません。 jQuery.ajax() メソッドを介してサーバーに POST リクエストを送信するとします。

1 2 3 4 5 6 7
// js $.ajax({     url: 'http://my.site/some/path',     method: 'post',     data: JSON.stringify({a: 'a', b: 'b'}),     contentType: 'application/json' });
コード内のcontentType: ‘application/json’に注目してください。jsonデータ形式でデータを送信しています。サーバー側では、$_POST 配列を出力するだけです:

1 2

結果が次のようになることに驚くでしょう:

// php var_dump($_POST);
1 array(0) { }

なぜこのような結果になったのでしょうか? json データ {a: ‘a’, b: ‘b’} はどこに行ったのでしょうか?

答えは、PHP は Content-Type が application/x-www-form-urlencoded または multipart/form-data である HTTP リクエストのみを解析するということです。その理由は、歴史的な理由によるもので、PHP が最初に $_POST を実装したとき、上記の 2 つのタイプが最も一般的でした。したがって、現在、一部の型 (application/json など) が非常に人気がありますが、PHP には自動処理がまだ実装されていません。

$_POSTはグローバル変数なので、$_POSTを変更するとグローバルに反映されます。したがって、Content-Type が application/json であるリクエストの場合は、json データを手動で解析して、$_POST 変数を変更する必要があります。

1 2 // php $_POST = json_decode(file_get_contents('php://input'), true);

この時点で、$_POST 変数を出力すると、期待する出力が得られます。

1
array(2) { ["a"]=> string(1) "a" ["b"]=> string(1) "b" }
間違い 8: PHP が文字データ型をサポートしていると考えている

以下のコードを見て、何が出力されるかを推測してください:

1 2 3
for ($c = 'a'; $c <= 'z'; $c++) { echo $c . &amp;quot;n&amp;quot;; }
答えが「a」から「z」を出力することである場合、答えが間違っていることに気づくでしょう。

はい、上記のコードは「a」から「z」を出力しますが、さらに「aa」から「yz」も出力します。なぜこのような結果になったのかを分析してみましょう。

PHPにはcharデータ型はなく、string型のみです。これを理解してから「z」をインクリメントすると、結果は「aa」になります。文字列の大きさの比較については、C を勉強したことがある人なら、'aa' が 'z' より小さいことを知っているはずです。これは、上記の出力結果が得られる理由も説明します。

'a' を 'z' に出力したい場合は、次の実装が良い方法です:

1 2 3

または、これでもOKです:

for ($i = ord('a'); $i <= ord('z'); $i++) { echo chr($i) . &amp;quot;n&amp;quot;; }
1 2 3 4 5 $letters = range('a', 'z'); for ($i = 0; $i < count($letters); $i++) { echo $letters[$i] . &amp;quot;n&amp;quot;; }

間違い9: コーディング標準を無視する

コーディング標準を無視してもエラーやバグが発生することはありませんが、特定のコーディング標準に従うことは依然として重要です。

統一されたコーディング標準がなければ、プロジェクトには多くの問題が発生します。最も明白なことは、プロジェクトのコードに一貫性がないことです。さらに悪いことに、コードのデバッグ、拡張、保守が難しくなります。これは、無意味な作業を多く行うなど、チームの効率が低下することも意味します。

PHP開発者にとっては、比較的幸運です。 PHP コーディング標準勧告 (PSR) があり、次の 5 つの部分で構成されているためです。

    PSR-0: 標準の自動読み込み
  • PSR-1: 基本コーディング標準
  • PSR-2: コーディングスタイルガイド
  • PSR-3: ログインターフェース規格
  • PSR-4: 自動読み込み
PSR はもともと作成され、PHP コミュニティ内のいくつかの大規模なグループがその後に続きました。 Zend、Drupal、Symfony、Joomla およびその他のプラットフォームは、この標準に貢献し、準拠しています。 PEAR でさえ、初期の頃は標準になることを目指していましたが、今では PSR 陣営に加わりました。

場合によっては、コーディング スタイルを使用し、それに固執している限り、どのようなコーディング標準を使用しても問題ありません。ただし、自分で作成する特別な理由がない限り、PSR 標準に従うことは良い考えです。現在、PSR を使用し始めているプロジェクトがますます増えており、ほとんどの PHP 開発者も PSR を使用しています。そのため、PSR を使用すると、チームの新しいメンバーがより早くプロジェクトに慣れ、より快適にコードを作成できるようになります。

エラー 10: empty() 関数の間違った使用

PHP 開発者の中には、empty() 関数を使用して変数や式のブール判定を行うことを好む人もいますが、場合によっては混乱を招く可能性があります。

まず、PHPの配列Arrayと配列オブジェクトArrayObjectを見てみましょう。違いはないようで、どれも同じです。これは本当にそうなのでしょうか?

1 2 3 4 5 6

物事をもう少し複雑にするために、次のコードを見てください:

// PHP 5.0 or later: $array = []; var_dump(empty($array)); // outputs bool(true) $array = new ArrayObject(); var_dump(empty($array)); // outputs bool(false) // why don't these both produce the same output?
1 2 3 4 5 // Prior to PHP 5.0: $array = []; var_dump(empty($array)); // outputs bool(false) $array = new ArrayObject(); var_dump(empty($array)); // outputs bool(false)

残念なことに、上記の方法は非常に人気があります。たとえば、Zend Framework 2 では、TableGateway::select() 結果セットに対して current() メソッドを呼び出してデータ セットを返すときに、ZendDbTableGateway がこれを実行します。開発者は簡単にこの罠に陥る可能性があります。

これらの問題を回避するために、配列が空かどうかを確認する最後の方法は count() 関数を使用することです:

1 2 3 4 5 // Note that this work in ALL versions of PHP (both pre and post 5.0): $array = []; var_dump(count($array)); // outputs int(0) $array = new ArrayObject(); var_dump(count($array)); // outputs int(0)

ちなみに、PHPは値0をブール値falseとみなすため、if条件文の条件判定にcount()関数を直接使用して配列が空かどうかを判定することができます。さらに、 count() 関数の配列の複雑さは O(1) であるため、 count() 関数を使用するのが賢明な選択です。

empty() 関数の使用が危険である別の例を見てみましょう。 empty() 関数をマジック メソッド __get() と組み合わせて使用​​する場合も危険です。それぞれテスト属性を持つ 2 つのクラスを定義しましょう。

まず、test 属性を持つ Regular クラスを定義します。

1 2 3 4

次に、Magic クラスを定義し、__get() マジック メソッドを使用してそのテスト属性にアクセスします。

class Regular { public $test = 'value'; }
1 2 3 4 5 6 7 8 9 10 11
class Magic { private $values = ['test' => 'value'];     public function __get($key)     {         if (isset($this->values[$key])) {             return $this->values[$key];         }     } }
わかりました。各クラスの test 属性にアクセスすると何が起こるかを見てみましょう:

1 2 3 4
$regular = new Regular(); var_dump($regular->test);    // outputs string(4) "value" $magic = new Magic(); var_dump($magic->test);      // outputs string(4) "value"
これまでのところ、すべてが正常であり、混乱を感じることはありません。

しかし、test 属性で empty() 関数を使用するとどうなるでしょうか?

1 2
var_dump(empty($regular->test));    // outputs bool(false) var_dump(empty($magic->test));      // outputs bool(true)
その結果は驚くべきものでしたか?

残念ながら、クラスがマジック __get() 関数を使用してクラス属性の値にアクセスする場合、属性値が空であるか存在しないかを確認する簡単な方法はありません。クラス スコープの外では、 null 値が返されるかどうかのみを確認できますが、キー値は null に設定できるため、対応するキーが設定されていないことを必ずしも意味するわけではありません。

対照的に、 Regular クラスの存在しないプロパティにアクセスすると、次のような通知メッセージが表示されます。

1 2 3 4

したがって、empty() 関数については、慎重に使用する必要があります。そうしないと、予期しない結果が得られ、誤解を招く可能性さえあります。

投稿者: Eddy 翻訳元: 10 Most Common PHP Mistakes

PHP プログラミングの質問: abcdefghij の 10 文字がそれぞれ 10 個の数字 0123456789 を表すと仮定してください

function zh($x){
$num=strlen($x);
$str='abcdefghij';
for($i=0;$i<$num;$i++){
$ arr[]=substr($x,$i,1);
$zhstr.=$str[$arr[$i]];
}
return $zhstr;
}

$str=zh('1563' ); //初期値を設定
print($str);
?>

phpプログラミングの問題、助けてください

これはテーブルのロックに関する問題です。読み取り時にテーブルを直接ロックすると、次のユーザーは一時的に待機する必要があります。しかし、一般的にはこのようには扱われません。これにより、Web サイトのエクスペリエンスが非常に悪くなります。

www.bkjia.comtru​​ehttp://www.bkjia.com/PHPjc/884176.html技術記事 PHP プログラミングで最もよくある 10 の間違い、PHP プログラミング 10 PHP は非常に人気のあるオープン ソースのサーバーサイド スクリプト言語であり、World Wide Web 上で見られる Web サイトのほとんどは PHP を使用して開発されています。この記事は...
Notice: Undefined property: Regular::$nonExistantTest in /path/to/test.php on line 10 Call Stack:     0.0012     234704   1. {main}() /path/to/test.php:0
関連ラベル:
php
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート