《Modern PHP》不完全读书笔记

2015-11-01

在以往代码即用性高于一切的时期,我对PHP的理解非常肤浅与片面。为了让以后的学习有点深度,我得做一些准备工作。以下是关于《Modern PHP》的一些笔记,笔记中可能记载的更多是比较基础的用法,便于后期翻阅。而进阶的知识,限于作者半残废的理解力可能无法悟懂。请自行翻阅书本。

本文章中,唯一对读者有帮助的东西:

《OReilly.Modern.PHP.2015.2》pdf 传送门

《OReilly.Modern.PHP.2015.2》epub 传送门

编者只等帮你到这了!

Chapter 2

Namespace

Why We Use Namespaces

  • 便于代码复用
  • 代码结构的清晰
  • 避免类、接口、函数和常量的命名冲突
  • 适用于 Class、Function、Constants 等

Import and alias

以下是Class引用方法

Example 2-1. Namespace without alias

<?php
$response = new \Symfony\Component\HttpFoundation\Response('Oops', 400);
$response->send();

Example 2-2. Namespace with default alias

<?php
use Symfony\Component\HttpFoundation\Response;
$response = new Response('Oops', 400);
$response->send();

Example 2-3. Namespace with custom alias

<?php
use Symfony\Component\HttpFoundation\Response as Res;

$r = new Res('Oops', 400);
$r->send();”

Helpful Tips

以下是function和constants引用方法

Using Functions

<?php
use func Namespace\functionName;
functionName();

Using Constants

<?php
use constant Namespace\CONST_NAME;
echo CONST_NAME;

Multiple imports

<?php
use Symfony\Component\HttpFoundation\Request,
    Symfony\Component\HttpFoundation\Response,
    Symfony\Component\HttpFoundation\Cookie;

Multiple namespaces in one file

<?php
namespace Foo {
    // Declare classes, interfaces, functions, and constants here
}

namespace Bar {
    // Declare classes, interfaces, functions, and constants here
}

Tips: 使用Namespace之后,代码中出现抛出异常,正确的做法(对比错误的用法)

<?php
namespace My\App;

class Foo
{
    public function doSomething()
    {
        throw new \Exception();
        // Don's forget the prefix \ before "Exception"
        // Or it will call \My\App\Exception class that does not exists
        // throw new Exception();
    }
}

Code to an Interface

什么是接口

  • 熟练的引用第三方库,需要对 Interface 有很好的理解
  • 接口是抽象的,不依赖具体的对象,不关注实现
  • 接口编程是优雅的(编者注)

一个好的例子

你原本想要写一个存储文档性质的对象,你完成了如下的代码,并且期望接受一个 HTMLDocument,也就是通过HTTP获取的文档对象

class DocumentStore
{
    protected $data = [];

    public function addDocument(Documentable $document)
    {
        $key = $document->getId();
        $value = $document->getContent();
        $this->data[$key] = $value;
    }

    public function getDocuments()
    {
        return $this->data;
    }
}

这个时候你就要去实现你的 Documentable 类,这个类代表 HTML 文档类。这个时候,这本书作者的一个词让你觉得你是被上天选中的天才,那就是 Interface!你突然意识到,这个即将成为 Documentable 的类,其实并没有大能耐。它顶多只能算个 HTMLDocument。那还有什么呢?聪明的你想到了 StreamDocument 和 CommandOutputDocument。看看作者是怎么举起这个栗子的。

Interface Documentable

interface Documentable
{
    public function getId();

    public function getContent();
}

Class HtmlDocument

class HtmlDocument implements Documentable
{
    protected $url;

    public function __construct($url)
    {
        $this->url = $url;
    }

    public function getId()
    {
        return $this->url;
    }

    public function getContent()
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $this->url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
        curl_setopt($ch, CURLOPT_MAXREDIRS, 3);
        $html = curl_exec($ch);
        curl_close($ch);

        return $html;
    }
}

Class StreamDocument

class StreamDocument implements Documentable
{
    protected $resource;
    protected $buffer;

    public function __construct($resource, $buffer = 4096)
    {
        $this->resource = $resource;
        $this->buffer = $buffer;
    }

    public function getId()
    {
        return 'resource-' . (int)$this->resource;
    }

    public function getContent()
    {
        $streamContent = '';
        rewind($this->resource);
        while (feof($this->resource) === false) {
            $streamContent .= fread($this->resource, $this->buffer);
        }

        return $streamContent;
    }
}

Class CommandOutputDocument

class CommandOutputDocument implements Documentable
{
    protected $command;

    public function __construct($command)
    {
        $this->command = $command;
    }

    public function getId()
    {
        return $this->command;
    }

    public function getContent()
    {
        return shell_exec($this->command);
    }
}

DocumentStore Usage

Example 2-11. DocumentStore

<?php
$documentStore = new DocumentStore();

// Add HTML document
$htmlDoc = new HtmlDocument('https://php.net');
$documentStore->addDocument($htmlDoc);

// Add stream document
$streamDoc = new StreamDocument(fopen('stream.txt', 'rb'));
$documentStore->addDocument($streamDoc);

// Add terminal command document
$cmdDoc = new CommandOutputDocument('cat /etc/hosts');
$documentStore->addDocument($cmdDoc);

print_r($documentStore->getDocuments());

附上不保证正确的UML类图,使用GenMyModel工具

Traits

Why We Use Traits

  1. 两个类之间存在共同点,但不足以构成继承的关系
  2. 用于注入在不同的类中

example. RetailStoreCar 类都是 geocodable 的,可以通过它的经度和纬度在地图上显示。(小编注:可以理解为变量latitude, longitude和方法displayOnMap)

对比以下三个方法

  1. 第一(bad)反应就是创建一个Geocodable类,实现相应的方法,让RetialStoreCar类都继承这个类。这是一个bad的解决方案,因为这会让无相关的两个类构成继承关系,扰乱了原来的继承层级关系。
  2. 第二(better)反应就是创建一个接口,让两个类都去实现这个接口。这样的坏处是两个类中都会有相同的实现这个接口的代码,这违反了DRY(Do not repeat yourself)原则。
  3. 第三(best)反应就是实现一个Trait,在CarRetailStore类都是用这个 trait 的方法。

How to Create a Trait

在代码上

<?php
trait MyTrait {
    // Trait implementation goes here
}

How to Use a Trait

在类中使用Trait

<?php
class MyClass
{
    use MyTrait;

    // Class implementation goes here
}

20151130

Generator

PHP中产生器不需要你实现Iterator接口的函数。相反,产生器只有在需要的时候进行计算和产生迭代结果。想想看,标准的PHP迭代器通常是在内存中进行迭代,在使用数据集之前先计算好整个数组,这样效率就非常让人担忧,特别是在计算集非常大的时候。

Create a Generator

Example 2-15. Simple generator

function myGenerator() {
    yield 'value1';
    yield 'value2';
    yield 'value3';
}

当你调用generator函数的时候,返回的对象是Generator类的实例。这个对象可以在foreach函数中进行迭代。在每次迭代中,PHP会让Generator对象计算和产生下一个迭代结果进行返回。每一次计算完毕,产生器会进入暂停状态,等待下一次调用的时候继续计算,直到函数定义了一个空的return。

<?php
foreach (myGenerator() as $yieldedValue) {
    echo $yieldedValue, PHP_EOL;
}

输出为

value1
value2
value3

Use a Generator

一个简单的例子,通过实现range()函数来对比Generator的优势。

Example 2-16. Range generator (bad)

<?php
function makeRange($length) {
    $dataset = [];
    for ($i = 0; $i < $length; $i++) {
        $dataset[] = $i;
    }

    return $dataset;
}

$customRange = makeRange(1000000);
foreach ($customRange as $i) {
    echo $i, PHP_EOL;
}

上面的方法需要分配1000000个整型来预分配数组。而实用产生器,每次迭代只需要一个整型变量的内存就可以实现。

Example 2-17. Range generator (good)

<?php
function makeRange($length) {
    for ($i = 0; $i < $length; $i++) {
        yield $i;
    }
}

foreach (makeRange(1000000) as $i) {
    echo $i, PHP_EOL;
}

另外一个例子是实现一个CSV文件的读取函数。假如你需要读取一个4G大小的文件,而你的服务器只有1G的内存,这种情况下将文件全部读取进来是不可能的。这个时候,产生器的优势就在于它可以每次只读取CSV文件的一行的内存占用即可。

Example 2-18. CSV generator

<?php
function getRows($file) {
    $handle = fopen($file, 'rb');
    if ($handle === false) {
        throw new Exception();
    }
    while (feof($handle) === false) {
        yield fgetcsv($handle);
    }
    fclose($handle);
}

foreach (getRows('data.csv') as $row) {
    print_r($row);
}

Closures

Closures and anonymous functions were introduced in PHP 5.3.0 概念上理解,闭包是一个描述了它创建时周围的状态的函数。周围的状态存储在闭包里面,即使之后调用的时候它的环境已经发生改变,它依旧保持最初赋予的状态。 在PHP中,匿名函数和闭包的表现是一样的,尽管他们在概念上其实是不一样的两种东西。

Create

Example 2-19. Simple closure

<?php
$closure = function ($name) {
    return sprintf('Hello %s', $name);
};

这个例子中创建了一个闭包对象,赋予了给$closure变量,他看起来像是标准的PHP函数,它和函数有着相同的语法,需要接受参数,也会返回值。但是它没有函数名。

从内部来看,我们可以调用$closure变量是因为它的值是一个闭包,并且这个闭包对象实现了\__invoke()这个魔术函数。当我们在一个变量名后面加上()的时候,PHP会参照__infoke()方法,并且调用它。

作者经常使用PHP闭包对象作为函数和方法的callback。许多PHP函数都会接受一个回调函数,如array_map()preg_replace_callback(),这是一个非常棒的机会去尝试PHP的匿名函数。

我们来举一个例子

<?php
$numbersPlusOne = array_map(function ($number) {
    return $number + 1;
}, [1,2,3]);
print_r($numbersPlusOne);
// Outputs --> [2,3,4]”

数组[1,2,3]中每一个元素都要经过匿名回调函数的处理,然后返回。这样输出的值就是都加上1的了。

在以前没有匿名函数的时候,PHP开发者需要额外创建一个函数,并且将函数名赋予给需要回调函数中。老式的PHP开发者是这样写的

<?php
// Named callback implementation
function incrementNumber ($number) {
    return $number + 1;
}

// Named callback usage
$numbersPlusOne = array_map('incrementNumber', [1,2,3]);
print_r($numbersPlusOne);

这个代码是可以运行的,但它不够简明和整洁。我们原本不需要一个额外的incrementNumber()函数,如果这个回调函数只会被利用一次的话。并且这样写也不够清晰。

Attach State

PHP中通过bindTo()use关键字来进行贴附动作。我们来看例子

Example 2-21. Attaching closure state with use keyword

<?php
function enclosePerson($name) {
    return function ($doCommand) use ($name) {
        return sprintf('%s, %s', $name, $doCommand);
    };
}

// Enclose "Clay" string in closure
$clay = enclosePerson('Clay');
“// Invoke closure with command
echo $clay('get me sweet tea!');
// Outputs --> "Clay, get me sweet tea!

对于Javascript开发者来说,这个贴附状态的动作会非常怪异,因为在Javascript中闭包是不需要贴附状态的。对比没有贴附的代码

小编补充

function Person($name) {
    return function ($doCommand) {
        # $name is null here, you must attach state
        return sprintf('%s, %s', $name, $doCommand);
    };
}

$clay_ = Person('clay');
echo $clay_('get me sweet tea!');
// Outputs --> ", get me sweet tea!
?>

Example 2-22. Attaching closure state with the bindTo method

<?php
class App
{
    protected $routes = array();
    protected $responseStatus = '200 OK';
    protected $responseContentType = 'text/html';
    protected $responseBody = 'Hello world';
    public function addRoute($routePath, $routeCallback)
    {
        $this->routes[$routePath] = $routeCallback->bindTo($this, __CLASS__);
    }
    public function dispatch($currentPath)
    {
        foreach ($this->routes as $routePath => $callback) {
            if ($routePath === $currentPath) {
                $callback();
            }
        }
        header('HTTP/1.1 ' . $this->responseStatus);
        header('Content-type: ' . $this->responseContentType);
        header('Content-length: ' . mb_strlen($this->responseBody));
        echo $this->responseBody;
    }
}

Tags: Modern_PHP 笔记

关于内容(信息、资料、图像等)的转载

本网站所有内容仅供学习交流,均禁止转载至营利、非营利网站或内联网。如需帮助,请联系作者 admin#put.im。