PHPは簡単だと言われていますが、使いこなすのは簡単ではありません。それを使用できることに加えて、その根本的な動作原理を知る必要もあります。
PHP は Web 開発に適した動的言語です。具体的には、C言語を使用して多数のコンポーネントを実装するソフトウェアフレームワークです。より狭義には、強力な UI フレームワークと考えることができます。
PHP の基礎となる実装を理解する目的は何ですか?動的言語をうまく使用するには、まずメモリ管理とフレームワーク モデルを理解する必要があります。拡張された開発を通じて、より強力な機能を実現し、プログラムのパフォーマンスを最適化できます。
PHP のコアアーキテクチャは以下のとおりです:
写真からわかるように、PHP は下から上への 4 層システムです:
PHP が車だとすると、車のフレームは PHP そのものであり、Zend は車のエンジンであり、Ext の下にあるさまざまなコンポーネントは車の車輪とみなすことができ、車はそう考えることができます。さまざまな種類の道路を走行し、PHP プログラムを実行することで車が道路を走行します。したがって、高性能エンジン + 適切なホイール + 適切なトラックが必要です。
前述したように、Sapi を使用すると、外部アプリケーションが一連のインターフェイスを通じて PHP とデータを交換し、さまざまなアプリケーションの特性に応じて特定の処理メソッドを実装できるようになります。
図からわかるように、PHP は典型的な動的言語の実行プロセスを実装しています。コードの一部を取得した後、字句解析、構文解析、その他の段階を経て、ソース プログラムが命令 (オペコード) に変換され、その後 ZEND が実行されます。仮想マシンはこれらの命令を順番に実行して操作を完了します。 PHP 自体は C で実装されているため、最終的に呼び出される関数はすべて C の関数です。実際には、PHP は C で開発されたソフトウェアと考えることができます。
PHP 実行の中核は、オペコードである翻訳された命令です。
オペコードは、PHP プログラム実行の最も基本的な単位です。オペコードは 2 つのパラメータ (op1、op2)、戻り値、および処理関数で構成されます。 PHP プログラムは最終的に、一連のオペコード処理関数の順次実行に変換されます。
いくつかの一般的な処理関数:
リーリーHashTable は zend のコア データ構造であり、PHP のほぼすべての一般的な関数を実装するために使用されます。また、関数シンボル テーブルやグローバル変数など、zend 内での一般的なアプリケーションも使用されます。また、ハッシュ テーブルに基づいて実現します。
PHP のハッシュ テーブルには次の機能があります:
Zend ハッシュ テーブルは、典型的なハッシュ テーブルのハッシュ構造を実装し、同時に二重リンク リストを付加することによって配列の順方向および逆方向の走査機能を提供します。その構造は以下に示すとおりです:
ご覧のとおり、ハッシュ テーブルにはキー -> 値の形式のハッシュ構造と二重リンク リスト モードの両方があり、高速検索と線形トラバーサルをサポートするのに非常に便利です。
PHP は型付けが弱い言語であり、変数の型を厳密に区別しません。 PHP では、変数を宣言するときに型を指定する必要はありません。 PHP は、プログラムの実行中に変数の型の暗黙的な変換を実行する場合があります。他の厳密に型指定された言語と同様に、プログラム内で明示的な型変換を実行することもできます。 PHP 変数は、単純型 (int、string、bool)、コレクション型 (配列リソース オブジェクト)、および定数 (const) に分類できます。上記の変数はすべて、内部では同じ構造 zval を持ちます。
Zval は Zend のもう 1 つの非常に重要なデータ構造であり、PHP 変数の識別と実装に使用されます。そのデータ構造は次のとおりです。
Zval は主に 3 つの部分で構成されます:
PHP 変数の型と実際のストレージの対応関係は次のとおりです:
リーリー
参照カウントは、メモリのリサイクル、文字列操作などで広く使用されています。 PHP の変数は、参照カウントの典型的なアプリケーションです。 Zval の参照カウントは、メンバー変数 is_ref および ref_count によって実装されます。参照カウントを通じて、複数の変数が同じデータを共有できます。頻繁なコピーによる大量の消費を避けてください。代入操作を実行するとき、zend は変数を同じ zval と ref_count++ に指し、設定解除操作中は対応する ref_count-1 を指します。破棄操作は、ref_count が 0 に減少した場合にのみ実行されます。参照割り当ての場合、zend は is_ref を 1 に変更します。
PHP变量通过引用计数实现变量共享数据,那如果改变其中一个变量值呢?当试图写入一个变量时,Zend若发现该变量指向的zval被多个变量共享,则为其复制一份ref_count为1的zval,并递减原zval的refcount,这个过程称为“zval分离”。可见,只有在有写操作发生时zend才进行拷贝操作,因此也叫copy-on-write(写时拷贝)
对于引用型变量,其要求和非引用型相反,引用赋值的变量间必须是捆绑的,修改一个变量就修改了所有捆绑变量。
整数、浮点数是PHP中的基础类型之一,也是一个简单型变量。对于整数和浮点数,在zvalue中直接存储对应的值。其类型分别是long和double。
从zvalue结构中可以看出,对于整数类型,和c等强类型语言不同,PHP是不区分int、unsigned int、long、long long等类型的,对它来说,整数只有一种类型也就是long。由此,可以看出,在PHP里面,整数的取值范围是由编译器位数来决定而不是固定不变的。
对于浮点数,类似整数,它也不区分float和double而是统一只有double一种类型。
在PHP中,如果整数范围越界了怎么办?这种情况下会自动转换为double类型,这个一定要小心,很多trick都是由此产生。
和整数一样,字符变量也是PHP中的基础类型和简单型变量。通过zvalue结构可以看出,在PHP中,字符串是由由指向实际数据的指针和长度结构体组成,这点和c++中的string比较类似。由于通过一个实际变量表示长度,和c不同,它的字符串可以是2进制数据(包含\0),同时在PHP中,求字符串长度strlen是O(1)操作。
在新增、修改、追加字符串操作时,PHP都会重新分配内存生成新的字符串。最后,出于安全考虑,PHP在生成一个字符串时末尾仍然会添加\0
常见的字符串拼接方式及速度比较:
假设有如下4个变量:$strA=‘123’; $strB = ‘456’; $intA=123; intB=456;
现在对如下的几种字符串拼接方式做一个比较和说明:
$res = $strA.$strB和$res = “$strA$strB” 这种情况下,zend会重新malloc一块内存并进行相应处理,其速度一般 $strA = $strA.$strB 这种是速度最快的,zend会在当前strA基础上直接relloc,避免重复拷贝 $res = $intA.$intB 这种速度较慢,因为需要做隐式的格式转换,实际编写程序中也应该注意尽量避免 $strA = sprintf (“%s%s”,$strA.$strB); 这会是最慢的一种方式,因为sprintf在PHP中并不是一个语言结构,本身对于格式识别和处理就需要耗费比较多时间,另外本身机制也是malloc。不过sprintf的方式最具可读性,实际中可以根据具体情况灵活选择。
PHP的数组通过Zend HashTable来天然实现。
foreach操作如何实现?对一个数组的foreach就是通过遍历hashtable中的双向链表完成。对于索引数组,通过foreach遍历效率比for高很多,省去了key->value的查找。count操作直接调用HashTable->NumOfElements,O(1)操作。对于’123’这样的字符串,zend会转换为其整数形式。$arr[‘123’]和$arr[123]是等价的
资源类型变量是PHP中最复杂的一种变量,也是一种复合型结构。
PHP的zval可以表示广泛的数据类型,但是对于自定义的数据类型却很难充分描述。由于没有有效的方式描绘这些复合结构,因此也没有办法对它们使用传统的操作符。要解决这个问题,只需要通过一个本质上任意的标识符(label)引用指针,这种方式被称为资源。
在zval中,对于resource,lval作为指针来使用,直接指向资源所在的地址。Resource可以是任意的复合结构,我们熟悉的mysqli、fsock、memcached等都是资源。
如何使用资源:
资源可以长期驻留,不只是在所有引用它的变量超出作用域之后,甚至是在一个请求结束了并且新的请求产生之后。这些资源称为持久资源,因为它们贯通SAPI的整个生命周期持续存在,除非特意销毁。很多情况下,持久化资源可以在一定程度上提高性能。比如我们常见的mysql_pconnect ,持久化资源通过pemalloc分配内存,这样在请求结束的时候不会释放。 对zend来说,对两者本身并不区分。
ローカル変数とグローバル変数は PHP でどのように実装されますか?リクエストの場合、PHP はいつでも 2 つのシンボル テーブル (symbol_table と active_symbol_table) を参照でき、前者はグローバル変数を維持するために使用されます。後者は、現在アクティブな変数シンボル テーブルを指すポインターです。プログラムが関数に入ると、zend はシンボル テーブル x をそれに割り当て、active_symbol_table を a に指します。このようにして、グローバル変数とローカル変数の区別が行われます。
変数値の取得: PHP のシンボル テーブルは hash_table を通じて実装され、取得時に対応する zval がテーブルから検索され、識別子に従って返されます。
関数でグローバル変数を使用する: 関数では、明示的に global を宣言することでグローバル変数を使用できます。 active_symbol_tableのsymbol_tableに同じ名前の変数への参照を作成します。symbol_tableに同じ名前の変数が存在しない場合は、それが最初に作成されます。