nber1994



php高级用法

October 12, 2017

胡乱探讨了包括autoload,异常处理,多进程,反射使用,trait代码复用,生成器等php语言的高级特性

1.autoload自动加载

自动加载简单实例

<?php
    spl_autoload_register(function ($class_name) {
        require_once $class_name . '.php';
    });
    
    $obj  = new MyClass1();
    $obj2 = new MyClass2();
?>

自动加载抛出异常

<?php
    spl_autoload_register(function ($name) {
        echo "Want to load $name.\n";
        throw new Exception("Unable to load $name.");
    });
    
    try {
        $obj = new NonLoadableClass();
    } catch (Exception $e) {
        echo $e->getMessage(), "\n";
    }
?>

spl_autoload_register

composer的自动加载流程

    <?php
        // autoload.php @generated by Composer
        require_once __DIR__ . '/composer' . '/autoload_real.php'; //会返回一个加载类
        return ComposerAutoloaderInite572cdf763ad8e4d6770ce15b9328edb::getLoader(); 
<?php

    // autoload_real.php @generated by Composer
    
    class ComposerAutoloaderInit85b4dbf6b714d62ec745fcf3dd1a5041
    {
        private static $loader;
    
        // 在实例化ClassLoader时调用该函数
        public static function loadClassLoader($class)
        {   
        // 该函数在getLoader方法里被注册为__autoload的实现,在实例化类时,如果类不存在,会自动调用该方法
        // 如果实例化的函数是Composer\Autoload\ClassLoader 则引入该类
    
            if ('Composer\Autoload\ClassLoader' === $class) {
    
                require __DIR__ . '/ClassLoader.php';
            }
        }
    
        public static function getLoader()
        {
            if (null !== self::$loader) {
                return self::$loader;
            }
    
            // 注册 loadClassLoader函数作为 __autoload 的实现   
            spl_autoload_register(array('ComposerAutoloaderInit85b4dbf6b714d62ec745fcf3dd1a5041', 'loadClassLoader'), true, true);
    
            // 实例化该方法时会自动调用上述方法注册的函数,
            // loadClassLoader函数里引入 ClassLoader类
            self::$loader = $loader = new \Composer\Autoload\ClassLoader();
    
            spl_autoload_unregister(array('ComposerAutoloaderInit85b4dbf6b714d62ec745fcf3dd1a5041', 'loadClassLoader'));
    
            // PSR-0 的规则
            $map = require __DIR__ . '/autoload_namespaces.php';
            foreach ($map as $namespace => $path) {
                $loader->set($namespace, $path);
            }
    
        // PSR-4 的规则
            $map = require __DIR__ . '/autoload_psr4.php';
            foreach ($map as $namespace => $path) {
                $loader->setPsr4($namespace, $path);
            }
    
            $classMap = require __DIR__ . '/autoload_classmap.php';
            if ($classMap) {
                $loader->addClassMap($classMap);
            }
    
        // 注册给定的函数作为 __autoload 的实现,具体参考下文
            // Class-Map部分
            $loader->register(true);
    
        // Files方式 直接加载需要访问的文件
            $includeFiles = require __DIR__ . '/autoload_files.php';
            foreach ($includeFiles as $fileIdentifier => $file) {
                composerRequire85b4dbf6b714d62ec745fcf3dd1a5041($fileIdentifier, $file);
            }
    
            return $loader;
        }
    }
    
    function composerRequire85b4dbf6b714d62ec745fcf3dd1a5041($fileIdentifier, $file)
    {
        if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
            // 加载需要调用的文件
            require $file;
    
            $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
        }
    }

2.异常处理

Fatal error: Uncaught exception 'Exception' 
with message 'Value must be 1 or below' in C:\webfolder\test.php:6 
Stack trace: #0 C:\webfolder\test.php(12): 
checkNum(28) #1 {main} thrown in C:\webfolder\test.php on line 6

3.多进程

<?php
    $ppid = posix_getpid();
    $pid = pcntl_fork();
    if ($pid == -1) {
        throw new Exception('fork子进程失败!');
    } elseif ($pid > 0) {
        cli_set_process_title("我是父进程,我的进程id是{$ppid}.");
       sleep(30); // 保持30秒,确保能被ps查到
    } else {
        $cpid = posix_getpid();
        cli_set_process_title("我是{$ppid}的子进程,我的进程id是{$cpid}.");
        sleep(30);
    }

管理子进程

使用信号来管理子进程

分发信号处理器

<?php
    pcntl_signal (int $signo , callback $handler) 安装一个信号处理器;
    #$signo是待处理的信号常量,callback是其处理函数
    
    pcntl_signal_dispatch () 调用每个等待信号通过pcntl_signal()安装的处理器
        SIGCHLD     子进程退出成为僵尸进程会向父进程发送此信号
        SIGHUP      进程挂起
        SIGTEM      进程终止
        ...         // 其他请在手册中查看

处理子进程

总结

1.使用多进程, 子进程结束以后, 内核会负责回收资源
2.使用多进程,子进程异常退出不会导致整个进程Thread退出. 父进程还有机会重建流程.
3.一个常驻主进程, 只负责任务分发, 逻辑更清楚.

4.反射的使用方法 获取类成员及其属性 ReflectionClass

使用反射

<?php
class HandsonBoy
{
    public $name = 'chenqionghe';
    public $age = 18;
    public function __set($name,$value)
    {
        echo '您正在设置私有属性'.$name.'<br >值为'.$value.'<br>';
        $this->$name = $value;
    }
    public function __get($name)
    {
        if(!isset($this->$name))
        {
            echo '未设置'.$name;
            $this->$name = "正在为你设置默认值".'<br>';
        }
        return $this->$name;
    }
}
$boy = new HandsonBoy();
echo $boy->name.'<br />';
$boy->hair = 'short';

获取对象的方法和属性

<?php
$reflect = new ReflectionObject($boy);
$props = $reflect->getProperties();
//获取属性的名字
foreach($props as $prop)
{
    print $prop->getName().php_EOL;
}
//获取对象方法列表
$methos = $reflect->getMethods();
foreach($methos as $method)
{
    print $method->getName().php_EOL;
}

不用反射API,使用class函数(只针对公开的属性和方法)

<?php
//返回对象属性的关联数组
var_dump(get_object_vars($boy));
//类属性
var_dump(get_class_vars(get_class($boy)));
//返回由类的属性的方法名组成的数组
var_dump(get_class_methods(get_class($boy)));

打印类的代码的实例

<?php
/**
 * @param $classObject 对象或者类名
 */
function getClass($classObject)
{
    $object = new ReflectionClass($classObject);
    $className = $object->getName();
    $Methods = $Properties = array();
    foreach($object->getProperties() as $v)
    {
        $Properties[$v->getName()] = $v;
    }
    foreach($object->getMethods() as $v)
    {
        $Methods[$v->getName()] = $v;
    }
    echo "class {$className}\n{\n";
    is_array($Properties) && ksort($Properties);
    foreach($Properties as $k=>$v)
    {
        echo "\t";
        echo $v->isPublic() ? 'public' : '',$v->isPrivate() ? 'private' :'',$v->isProtected() ? 'protected' : '';
        $v->isStatic() ? 'static' : '';
        echo "\t{$k}\n";
    }
    echo "\n";
    if(is_array($Methods)) ksort($Methods);
    foreach($Methods as $k=>$v)
    {
        echo "\tfunction {$k}()\n";
    }
    echo "}\n";
}

反射的作用


<?php
class mysql
{
    function connect($db)
    {
#        echo "连接到数据库{$db[0]}" . php_EOL;
    }
}

class sqlproxy
{
    private $target;
    public function __construct($tar)
    {
        $this->target[] = new $tar;
    }
    public function __call($name,$args)
    {
        foreach($this->target as $obj)
        {
            $r = new ReflectionClass($obj);
            if($method = $r->getMethod($name))
            {
                if($method->isPublic() && !$method->isAbstract())
                {
                    echo "方法前拦截记录LOG".php_EOL;
                    $method->invoke($obj,$args);
                    echo "方法后拦截".php_EOL;
                }
            }
        }
    }
}
$obj = new sqlproxy('mysql');
$obj->connect('chenqionghe');

弊端

5.trait 代码复用

用法

简单使用

<?php
trait first_trait{
    public function hello(){
        return 'hello';
    }
}
<?php
trait first_trait{
    public function hello(){
        return 'hello';
    }
}

trait second_trait{
    public function world(){
        return 'world';
    }
}

class first_class{
    use first_trait,second_trait;
}
$obj=new first_class();
echo $obj->hello();
echo $obj->world();
<?php
trait first_trait{
    public function hello(){
        echo 'hello';
    }
}

trait second_trait{
    //trait之间的嵌套
    use first_trait;
    public function world(){
        echo 'world';
    }
}

class first_class{
    use second_trait;
}
$obj=new first_class();
echo $obj->hello();
echo $obj->world();

冲突的解决

<?php
trait A {
    public function smallTalk() {
        echo 'a';
    }
    public function bigTalk() {
        echo 'A';
    }
}

trait B {
    public function smallTalk() {
        echo 'b';
    }
    public function bigTalk() {
        echo 'B';
    }
}

class Talker {
    use A, B {
        B::smallTalk insteadof A; //trait B 的smallTalk方法会代替 trait A 的smallTalk方法
        A::bigTalk insteadof B;  //trait A 的bigTalk方法会代替 trait B 的bigTalk方法
    }
}

class Aliased_Talker {
    use A, B {
        B::smallTalk insteadof A;//trait B 的smallTalk方法会代替 trait A 的smallTalk方法
        A::bigTalk insteadof B;//trait A 的bigTalk方法会代替 trait B 的bigTalk方法
        B::bigTalk as talk; //使用 as 操作符来定义了 talk方法 来作为 B 的 bigTalk方法 的别名
    }
}

$obj=new Talker();
$obj->smallTalk();
$obj->bigTalk();
//结果会输出 bA
$obj2=new Aliased_Talker();
$obj2->talk();//会输出B

修改访问控制

<?php
trait HelloWorld {
    public function sayHello() {
        echo 'Hello World!';
    }
}

// 修改 sayHello 的访问控制
class MyClass1 {
    use HelloWorld { sayHello as protected; }
}

// 给方法一个改变了访问控制的别名
// 原版 sayHello 的访问控制则没有发生变化
class MyClass2 {
    use HelloWorld { sayHello as private myPrivateHello; }
}

Trait 同样可以定义属性

6.php的生成器、yield和协程

迭代生成器

(迭代)生成器也是一个函数,不同的是这个函数的返回值是依次返回,而不是只返回一个单独的值.或者,换句话说,生成器使你能更方便的实现了迭代器接口.下面通过实现一个xrange函数来简单说明:

<?php
function xrange($start, $end, $step = 1) {
    for ($i = $start; $i <= $end; $i += $step) {
        yield $i;
    }
}
 
foreach (xrange(1, 1000000) as $num) {
    echo $num, "\n";
}

优点

生成器为可中断的函数

协程

<?php
function logger($fileName) {
    $fileHandle = fopen($fileName, 'a');
    while (true) {
        fwrite($fileHandle, yield . "\n");
    }
}
 
$logger = logger(__DIR__ . '/log');
$logger->send('Foo');
$logger->send('Bar')
?>

yield没有作为一个语句来使用, 而是用作一个表达式, 即它能被演化成一个值. 这个值就是调用者传递给send()方法的值. 在这个例子里, yield表达式将首先被”Foo”替代写入Log, 然后被”Bar”替代写入Log.

<?php
function gen() {
    $ret = (yield 'yield1');
    var_dump($ret);
    $ret = (yield 'yield2');
    var_dump($ret);
}

$gen = gen();
var_dump($gen->current());    // string(6) "yield1"
var_dump($gen->send('ret1')); // string(4) "ret1"   (the first var_dump in gen)
                              // string(6) "yield2" (the var_dump of the ->send() return value)
var_dump($gen->send('ret2')); // string(4) "ret2"   (again from within gen)
                              // NULL               (the return value of ->send())
?>

多任务协作