├── 1.NameSpace.md ├── 2.Interface.md ├── 3.Generators.md ├── 4.Closures.md ├── 5.ZendOpcache.md ├── 6.Built-in-server.md ├── 7.FIG_and_PSR.md ├── 8.Components.md ├── 9.BestPractices.md ├── README.md ├── X.Hosting.md ├── XI.PHP-FPM.md └── XII.Tuning-Deploy-Test-HHVM.md /1.NameSpace.md: -------------------------------------------------------------------------------- 1 | namespace 定义在 php文件顶部紧挨着 ` Zend/Cloud/DocumentService/Adapter/WindowsAzure/Query.php 23 | ``` 24 | 25 | 这种方式对懒人来说的缺点就是写很多类名时太TM长了。 26 | 27 | namespace提供了 import和 alias来解决这个问题。 28 | 29 | import,alias 在5.3版本下支持类,接口与命名空间导入。5.6开始支持函数与常量导入。 30 | 31 | ```php 32 | # namespace without alias 33 | send(); 36 | $response2 = new \Symfony\Component\HttpFoundation\Response('Success',200); 37 | ``` 38 | 39 | ```php 40 | # namespace with alias 41 | use Symfony\Component\HttpFoundation\Response; 42 | $response = new Response('Oops',400); 43 | $response->send(); 44 | ``` 45 | 46 | ```php 47 | # namespace with custom alias 48 | use Symfony\Component\HttpFoundation\Response as Res; 49 | $response = new Res('Oops',400); 50 | $response->send(); 51 | ``` 52 | 53 | import与 alias的use与namespace的规则一样要在 php文件顶部紧挨着 `getId(); 13 | $value = $document->getContent(); 14 | $this->data[$key] = $value; 15 | } 16 | public function getDocuments() { 17 | return $this->data; 18 | } 19 | } 20 | 21 | interface Documentable { 22 | public function getId(); 23 | public function getContent(); 24 | } 25 | 26 | class HtmlDocument implements Documentable { 27 | protected $url; 28 | public function __construct($url) { 29 | $this->url = $url; 30 | } 31 | public function getId() { 32 | return $this->url; 33 | } 34 | public function getContent() { 35 | $ch = curl_init(); 36 | curl_setopt($ch, CURLOPT_URL, $this->url); 37 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 38 | curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3); 39 | curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); 40 | curl_setopt($ch, CURLOPT_MAXREDIRS, 3); 41 | $html = curl_exec($ch); 42 | curl_close($ch); 43 | return $html; 44 | } 45 | } 46 | 47 | class StreamDocument implements Documentable { 48 | protected $resource; protected $buffer; 49 | public function __construct($resource, $buffer = 4096) { 50 | $this->resource = $resource; 51 | $this->buffer = $buffer; 52 | } 53 | public function getId() { 54 | return 'resource-' . (int)$this->resource; 55 | } 56 | public function getContent() { 57 | $streamContent = ''; rewind($this->resource); 58 | while (feof($this->resource) === false) { 59 | $streamContent .= fread($this->resource, $this->buffer); 60 | } 61 | return $streamContent; 62 | } 63 | } 64 | 65 | class CommandOutputDocument implements Documentable { 66 | protected $command; 67 | public function __construct($command) { 68 | $this->command = $command; 69 | } 70 | public function getId() { 71 | return $this->command; 72 | } 73 | public function getContent() { 74 | return shell_exec($this->command); 75 | } 76 | } 77 | 78 | $documentStore = new DocumentStore(); 79 | // Add HTML document 80 | $htmlDoc = new HtmlDocument('https://php.net'); 81 | $documentStore->addDocument($htmlDoc); 82 | // Add stream document 83 | $streamDoc = new StreamDocument(fopen('stream.txt', 'rb')); 84 | $documentStore->addDocument($streamDoc); 85 | 86 | 87 | // Add terminal command document 88 | $cmdDoc = new CommandOutputDocument('cat /etc/hosts'); 89 | $documentStore->addDocument($cmdDoc); 90 | print_r($documentStore->getDocuments()); 91 | ``` 92 | 93 | 5.4 新加入了 Traits特性,它既不是接口也不是类。主要是为了解决单继承语言的限制。与 Ruby 的 `composable modules` 或 `mixins` 类似。 94 | 95 | 它能被加入到一个或多个已经存在的类中。它声明了类能做什么(表明了其接口特性),同时也包含了具体实现(表明了其类特性) 96 | 97 | Example: 98 | 99 | ```php 100 | geocoder = $geocoder; 110 | } 111 | public function setAddress($address) { 112 | $this->address = $address; 113 | } 114 | public function getLatitude(){ 115 | if (isset($this->geocoderResult) === false) { 116 | $this->geocodeAddress(); 117 | } 118 | return $this->geocoderResult->getLatitude(); 119 | } 120 | public function getLongitude() { 121 | if (isset($this->geocoderResult) === false) { 122 | $this->geocodeAddress(); 123 | } 124 | return $this->geocoderResult->getLongitude(); 125 | } 126 | protected function geocodeAddress() { 127 | $this->geocoderResult = $this->geocoder->geocode($this->address); 128 | return true; 129 | } 130 | } 131 | ``` 132 | 133 | ```php 134 | setAddress('420 9th Avenue, New York, NY 10001 USA'); 148 | $store->setGeocoder($geocoder); 149 | $latitude = $store->getLatitude(); 150 | $longitude = $store->getLongitude(); 151 | echo $latitude, ':', $longitude; 152 | ``` 153 | 154 | PHP解释器将在编译时期将 Traits 的代码拷贝到类中。 155 | 156 | 从基类继承的成员被 traits 插入的成员所覆盖。 157 | 158 | 优先顺序是来自当前类的成员方法覆盖了 trait 的方法,而 trait 的方法则覆盖了被继承的方法。 159 | 160 | 如果两个 trait 都插入了一个同名的方法,如果没有明确解决冲突将会产生一个致命错误。 161 | 162 | 为了解决多个 trait 在同一个类中的命名冲突,可以使用 insteadof 操作符来明确指定使用冲突方法中的哪一个。 163 | 164 | 或者使用 as 操作符可以将其中一个冲突的方法以另一个名称来引入。 165 | 166 | ``` 167 | 201 | ``` 202 | 203 | 使用 as 语法还可以用来调整方法的访问控制权限。 204 | 205 | ```php 206 | 224 | ``` 225 | -------------------------------------------------------------------------------- /3.Generators.md: -------------------------------------------------------------------------------- 1 | php 5.5之后引入了 generators。 2 | 3 | 与 php传统的迭代器不同,generators不仅不需要你实现繁重的迭代器接口,而且还能按需求值,提高性能,特别是在数据量大的时候。 4 | 5 | generators 也并非灵丹妙药。除非你调用,否则它并不知道下一次迭代的值。并且整体只能迭代一次(需要重新迭代则必须重建generators)。 6 | 7 | Example: 8 | 9 | ```php 10 | 46 | ``` 47 | 48 | 闭包和匿名函数(其实就是没定义名字的函数)是不同的东西,但在PHP里他们是一样的。 49 | 50 | 匿名函数赋值给变量后,创建了closure对象(每个闭包都是一个 closure 对象实例), 51 | 52 | 其实现了 invoke 魔术方法,所以可以通过直接调用变量名来调用该匿名函数。 53 | 54 | [js与php闭包对比](http://stackoverflow.com/questions/7417430/javascript-closures-vs-php-closures-whats-the-difference) 55 | 56 | -------------------------------------------------------------------------------- /5.ZendOpcache.md: -------------------------------------------------------------------------------- 1 | opcache 是 php 5.5之后第一个内建的字节码内存缓存工具。类似之前的 APC,eAccelerator... 2 | 3 | opcache 的加速原理是把编译后的 bytecode 存储在内存里面, 避免重复编译 PHP 所造成的资源浪费。 4 | 5 | 安装 & 配置部分省略。。。。。。 6 | 7 | [一个关于Opcache的小分享](http://www.laruence.com/2013/11/11/2928.html) 8 | -------------------------------------------------------------------------------- /6.Built-in-server.md: -------------------------------------------------------------------------------- 1 | php 5.4之后引入了内建的http服务器。 2 | 3 | 使用方式: 4 | 5 | ```php 6 | # 在命令行界面切换到你的项目目录,在此启动该目录将作为服务器的根目录。 7 | # php -S localhost:4000 8 | # 在 localhost 上绑定 4000 端口 9 | # 也可用 0.0.0.0 监听任意ip 10 | # 带自定义配置项的启动 php -S localhsot:8000 -c app/config/php.ini 11 | ``` 12 | 13 | 内建的服务器并不支持 `.htaccess`。php使用 router scripts 来解决这一问题。 14 | 15 | ```php 16 | php -S localhost:8000 router.php 17 | ``` 18 | 19 | 该脚本将在每一次请求前调用。如果脚本return false,与请求相匹配的静态文件将返回,否则将返回router.php返回的内容。 20 | 21 | 检测内建服务器的方式: 22 | 23 | ```php 24 |

'; 26 | echo htmlentities($input, ENT_QUOTES, 'UTF-8'); 27 | ``` 28 | 29 | 过滤html输入,[HTML Purifier](http://htmlpurifier.org/) 是更好的选择。 30 | 31 | 一些php模版引擎也会自动完成过滤工作,如 [Twig](http://twig.sensiolabs.org/) , [Blade](http://daylerees.com/codebright/blade) 。 32 | 33 | 不要自己写正则来过滤,正则又复杂又慢,html输入不一定是规范的,这一方法有很大的安全隐患。 34 | 35 | ### SQL 36 | 37 | 举一个例子 38 | 39 | ```php 40 | $sql = sprintf( 41 | 'UPDATE users SET password = "%s" WHERE id = %s', 42 | $_POST['password'], 43 | $_GET['id'] 44 | ); 45 | ``` 46 | 47 | 直接使用字符串拼接作为sql语句简直就是灾难,直接一个伪造请求就能改掉用户密码。 48 | 49 | ```php 50 | # POST /user?id=1 HTTP/1.1 51 | # Content-Length: 17 52 | # Content-Type: application/x-www-form-urlencoded 53 | # password=abc";-- 54 | ``` 55 | 56 | PS:大部分数据库将 `--` 当作注释而忽略掉后面的内容。 57 | 58 | 更好的方式是使用 [PDO](http://php.net/manual/en/book.pdo.php) 59 | 60 | ### 杂项 61 | 62 | 过滤Email 63 | 64 | ```php 65 | 12] 146 | ); 147 | 148 | if ($passwordHash === false) { 149 | throw new Exception('Password hash failed'); 150 | } 151 | 152 | // Create user account (THIS IS PSUEDO-CODE) 153 | $user = new User(); 154 | $user->email = $email; 155 | $user->password_hash = $passwordHash; 156 | $user->save(); 157 | 158 | // Redirect to login page 159 | header('HTTP/1.1 302 Redirect'); 160 | header('Location: /login.php'); 161 | 162 | } catch (Exception $e) { 163 | // Report error 164 | header('HTTP/1.1 400 Bad request'); 165 | echo $e->getMessage(); 166 | } 167 | ``` 168 | 169 | 密码字段推荐 `varchar(255)` 170 | 171 | ## 用户登陆流程 172 | 173 | 请求: 174 | 175 | ```php 176 | # POST /login.php HTTP/1.1 177 | # Content-Length: 43 178 | # Content-Type: application/x-www-form-urlencoded 179 | # email=john@example.com&password=sekritshhh! 180 | ``` 181 | 182 | ```php 183 | password_hash) === false) { 198 | throw new Exception('Invalid password'); 199 | } 200 | 201 | // Re-hash password if necessary(see not below) 202 | $currentHashAlgorithm = PASSWORD_DEFAULT; 203 | $currentHashOptions = array('cost' => 15); 204 | $passwordNeedsRehash = password_needs_rehash( 205 | $user->password_hash, 206 | $currentHashAlgorithm, 207 | $currentHashOptions 208 | ); 209 | 210 | if ($passwordNeedsRehash === true) { 211 | // Save new password hash (THIS IS PSUEDO-CODE) 212 | 213 | $user->password_hash = password_hash( 214 | $password, 215 | $currentHashAlgorithm, 216 | $currentHashOptions 217 | ); 218 | $user->save(); 219 | } 220 | 221 | // Save login status to session 222 | $_SESSION['user_logged_in'] = 'yes'; 223 | $_SESSION['user_email'] = $email; 224 | 225 | // Redirect to profile page 226 | header('HTTP/1.1 302 Redirect'); 227 | header('Location: /user-profile.php'); 228 | } catch (Exception $e) { 229 | header('HTTP/1.1 401 Unauthorized'); 230 | echo $e->getMessage(); 231 | } 232 | ``` 233 | 234 | # 时间处理 235 | 236 | php 5.2 之后推荐使用 `DateTime`,`DateInterval`,`DateTimeZone`来处理时间相关的操作。 237 | 238 | 第一步,设置时区。两种方式 239 | 240 | ```php 241 | # declare the default time zone in the php.ini file like this:date.timezone = 'America/New_York'; 242 | date_default_timezone_set('America/New_York'); 243 | ``` 244 | 245 | 第二步,实例化 246 | 247 | ```php 248 | # $datetime = new DateTime(); 249 | # Without arguments, the DateTime class constructor creates an instance that repre‐ sents the current date and time. 250 | $datetime = new DateTime('2014-04-27 5:03 AM'); 251 | ``` 252 | 253 | 时间的来源格式不尽相同,使用 `DateTime::createFromFormat()` 静态方法来格式化并创建实例 254 | 255 | ```php 256 | $datetime = DateTime::createFromFormat('M j, Y H:i:s', 'Jan 2, 2014 23:04:12'); 257 | # strtotime ('Jan 2, 2014 23:04:12'); 258 | ``` 259 | 260 | 使用 `DateInterval` ,`DatePeriod` 来处理时间间隔。 261 | 262 | * Y (years) 263 | * M (months) 264 | * D (days) 265 | * W (weeks) 266 | * H (hours) 267 | * M (minutes) 268 | * S (seconds) 269 | 270 | 举例,P2D 表示两天。P2DT5H2M 两天五小时两分钟。 271 | 272 | ```php 273 | add($interval); 280 | echo $datetime->format('Y-m-d H:i:s'); 281 | ``` 282 | 283 | ```php 284 | $dateStart = new \DateTime(); 285 | $dateInterval = \DateInterval::createFromDateString('-1 day'); 286 | $datePeriod = new \DatePeriod($dateStart, $dateInterval, 3); 287 | foreach ($datePeriod as $date) { 288 | echo $date->format('Y-m-d'), PHP_EOL; 289 | } 290 | # This outputs: 291 | # 2014-12-08 292 | # 2014-12-07 293 | # 2014-12-06 294 | # 2014-12-05 295 | ``` 296 | 297 | ```php 298 | format('Y-m-d H:i:s'), PHP_EOL; 304 | } 305 | ``` 306 | 307 | 第三方组件 [Cabon](https://github.com/briannesbitt/Carbon) 308 | 309 | 切换不同时区的话,使用 `DateTimeZone` 310 | 311 | ```php 312 | setTimezone(new DateTimeZone('Asia/Hong_Kong')); 319 | ``` 320 | 321 | # 数据库 322 | 323 | 使用 PDO 来处理数据库操作 324 | 325 | 通过配置 DSN 使得 PDO 将 php 与数据库连接起来 326 | 327 | DSN 配置由数据库驱动名开头 (e.g., mysql or sqlite),每种数据库格式不尽相同,但大体都包含以下信息: 328 | 329 | * Hostname or IP address 330 | * Port number 331 | * Database name 332 | * Character set 333 | 334 | [DSN格式参考](http://php.net/manual/pdo.drivers.php) 335 | 336 | ```php 337 | prepare($sql); 355 | $email = filter_input(INPUT_GET, 'email'); 356 | $statement->bindValue(':email', $email); // default string type 357 | 358 | $sql = 'SELECT email FROM users WHERE id = :id'; 359 | $statement = $pdo->prepare($sql); 360 | $userId = filter_input(INPUT_GET, 'id'); 361 | $statement->bindValue(':id', $userId, PDO::PARAM_INT); 362 | ``` 363 | 364 | * PDO::PARAM_BOOL 365 | * PDO::PARAM_NULL 366 | * PDO::PARAM_INT 367 | * PDO::PARAM_STR (default) 368 | 369 | [一些PDO预定义常量](http://php.net/manual/en/pdo.constants.php) 370 | 371 | ## 获取查询结果 372 | 373 | 通过 execute 执行 SQL 之后,若是非 INSERT,UPDATE或DELETE操作,你还需获取数据库返回的纪录。 374 | 375 | ```php 376 | setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false); 379 | // 缓存结果集到php端将有更多的操作可用,但内存占用也会加大 380 | // 不缓存到php端,则使用连接资源向数据库端每次获取数据,虽然php端内存压力小了,但是会明显加大数据库端的负载。 381 | // 对于同一个连接来说,除非结果集被服务端获取完,否则将无法返回其他查询结果。 382 | 383 | $uresult = $pdo->query("SELECT Name FROM City"); 384 | if ($uresult) { 385 | while ($row = $uresult->fetch(PDO::FETCH_ASSOC)) { 386 | echo $row['Name'] . PHP_EOL; 387 | } 388 | } 389 | ?> 390 | ``` 391 | 392 | [Buffered and Unbuffered queries](http://php.net/manual/zh/mysqlinfo.concepts.buffering.php) 393 | 394 | [其他的FETCH_STYLE](http://php.net/manual/zh/pdostatement.fetch.php) 395 | 396 | ```php 397 | prepare($sql); 401 | $email = filter_input(INPUT_GET, 'email'); 402 | $statement->bindValue(':email', $email, PDO::PARAM_INT); $statement->execute(); 403 | // Iterate results 404 | $results = $statement->fetchAll(PDO::FETCH_ASSOC); 405 | // results 已经包含全部结果集 406 | foreach ($results as $result) { 407 | echo $result['email']; 408 | } 409 | ``` 410 | 411 | 获取指定列结果集 412 | 413 | ```php 414 | prepare($sql); 418 | $email = filter_input(INPUT_GET, 'email'); 419 | $statement->bindValue(':email', $email, PDO::PARAM_INT); 420 | $statement->execute(); 421 | // Iterate results 422 | // The query result column order matches the column order specified in the SQL query. 423 | while (($email = $statement->fetchColumn(1)) !== false) { 424 | echo $email; 425 | } 426 | ``` 427 | 428 | ## 事务 429 | 430 | PDO 同样支持事务 431 | 432 | ```php 433 | prepare(' 455 | UPDATE accounts 456 | SET amount = amount - :amount 457 | WHERE name = :name 458 | '); 459 | $stmtAdd = $pdo->prepare(' 460 | UPDATE accounts 461 | SET amount = amount + :amount 462 | WHERE name = :name 463 | '); 464 | // Start transaction 465 | $pdo->beginTransaction(); 466 | // Withdraw funds from account 1 467 | $fromAccount = 'Checking'; 468 | $withdrawal = 50; 469 | $stmtSubtract->bindParam(':name', $fromAccount); 470 | $stmtSubtract->bindParam(':amount', $withDrawal, PDO::PARAM_INT); 471 | $stmtSubtract->execute(); 472 | // Deposit funds into account 2 473 | $toAccount = 'Savings'; 474 | $deposit = 50; 475 | $stmtAdd->bindParam(':name', $toAccount); 476 | $stmtAdd->bindParam(':amount', $deposit, PDO::PARAM_INT); 477 | $stmtAdd->execute(); 478 | // Commit transaction 479 | $pdo->commit(); 480 | ``` 481 | 482 | # 多字节字符串处理 483 | 484 | 除英文外的大部分语言都无法仅用一个字节来表示。目前主流使用 UTF-8 编码。 485 | 486 | 使用 [mbstring](http://php.net/manual/book.mbstring.php) 来处理多字节字符串。 487 | 488 | 比如说用 mb_strlen 来代替 strlen 统计字符串长度。 489 | 490 | 使用 `mb_detect_encoding()` 与 `mb_convert_encoding()` 函数来检测和转换字符编码。 491 | 492 | ## 输出 UTF-8 编码的数据 493 | 494 | ```php 495 | # set default_charset = "UTF-8"; in php.ini 496 | header('Content-Type: application/json;charset=utf-8'); 497 | # or in html page, 498 | ``` 499 | 500 | # 流(Streams) 501 | 502 | 即使 php 4.3 之后就引入了 streams,但可以说这一特性是使用人数最少的特性之一,但它却非常好用。 503 | 504 | streams 是用来在源与目的之间传输数据的,仅此而已。源可以是文件,进程,网络连接,zip压缩包,标准输入输出等等资源,只要其被 stream wrappers 封装成统一的接口。 505 | 506 | PS:作者将stream比喻为一端流向另一端的水流,我们可以对水流做任何操作,比如加水,拉闸限水等等。。。 507 | 508 | 归纳来说所有的 Stream Wrappers 都是实现了 509 | 510 | * 打开连接 511 | * 读写数据 512 | * 关闭连接 513 | 514 | 几个动作。 515 | 516 | 每个 stream 包含了 scheme 和 target,`://` 517 | 518 | ```php 519 | array( 549 | 'method' => 'POST', 550 | 'header' => "Content-Type: application/json;charset=utf-8;\r\n" . 551 | "Content-Length: " . mb_strlen($requestBody), 552 | 'content' => $requestBody 553 | ) 554 | )); 555 | $response = file_get_contents('https://my-api.com/users', false, $context); 556 | ``` 557 | 558 | ## Stream 过滤器 559 | 560 | ```php 561 | data = str_replace($bad, $good, $bucket->data); 592 | // Increment total data consumed 593 | $consumed += $bucket->datalen; 594 | // Send bucket to downstream brigade 595 | stream_bucket_append($out, $bucket); 596 | } 597 | return PSFS_PASS_ON; 598 | } 599 | } 600 | 601 | stream_filter_register('dirty_words_filter', 'DirtyWordsFilter'); 602 | 603 | $handle = fopen('data.txt', 'rb'); 604 | stream_filter_append($handle, 'dirty_words_filter'); 605 | while (feof($handle) !== true) { 606 | echo fgets($handle); // <-- Outputs censored text 607 | } 608 | fclose($handle); 609 | ``` 610 | 611 | # 错误与异常 612 | 613 | 可以使用 `@` 操作符来抑制错误提示,但这个极度不推荐的。正确的做法是用 `TryCatch` 和 `Throw` 处理和抛出异常。 614 | 615 | ## 异常 616 | 617 | ```php 618 | getCode(); // 100 621 | $message = $exception->getMessage(); // 'Danger...' 622 | 623 | throw new Exception('Something went wrong. Time for lunch!'); // un catch 624 | 625 | try { 626 | $pdo = new PDO('mysql://host=wrong_host;dbname=wrong_name'); 627 | } catch (PDOException $e) { 628 | // Inspect the exception for logging 629 | $code = $e->getCode(); 630 | $message = $e->getMessage(); 631 | // Display a nice message to the user 632 | echo 'Something went wrong. Check back soon, please.'; 633 | exit; 634 | } 635 | 636 | try { 637 | throw new Exception('Not a PDO exception'); 638 | $pdo = new PDO('mysql://host=wrong_host;dbname=wrong_name'); 639 | } catch (PDOException $e) { 640 | // Handle PDO exception 641 | echo "Caught PDO exception"; 642 | } catch (Exception $e) { 643 | // Handle all other exceptions 644 | echo "Caught generic exception"; 645 | } finally { 646 | // Always do this,since php 5.5 647 | echo "Always do this"; 648 | } 649 | 650 | ``` 651 | 652 | php 内置的异常类 653 | 654 | [Exception](http://php.net/manual/en/class.exception.php) 655 | 656 | [ErrorException](http://php.net/manual/en/class.errorexception.php) 657 | 658 | SPL(php标准库)提供了更多的[异常子类](http://php.net/manual/en/spl.exceptions.php) 659 | 660 | 或者设置全局的异常处理函数,如下。 661 | 662 | ```php 663 |