标签归档:PHP

Magento 2.x 安装

Magento 2.x的安装,官方提供了三个方式:
1 直接下载完整的压缩包
2 通过composer安装,并制定使用官方提供的仓库(需要配置)
3 克隆Github上的仓库,然后通过composer安装

第一和第三种方式,本质上没有多大差别。第二种方式中需要指定一个仓库,这个主要Magento本身的扩展市场生态,如果要使用到官方提供的扩展,第二种方式是唯一选择。

第三种安装方式:(https://github.com/magento/magento2)

#Git 克隆
yum install git
git clone https://github.com/magento/magento2.git
cd magento2
git checkout 2.2.3

composer install

#直接下载压缩包(先选中版本)
cd magento2

composer install

Nginx配置:

upstream fastcgi_backend {
   server  127.0.0.1:9001;
}
server {
   listen 80;
   server_name magento2.dev;
   set $MAGE_ROOT /var/www/magento/magento2;
   include /var/www/magento/magento2/nginx.conf.sample;
}

Composer中replace属性的作用

原始解释:
“Lists packages that are replaced by this package. This allows you to fork a package, publish it under a different name with its own version numbers, while packages requiring the original package continue to work with your fork because it replaces the original package.”

列出被当前包替换的包。这允许你fork一个包,并以不同的名称和它自己的版本号进行发布,由于替换了原始的包,当包依赖原始包时可以使用fork包继续工作。

实际上,第一句话已经解释清楚了,就是当前包替换哪个(或哪些)包。比如:

{
“name": "b/b",
"replace": {
    "a/a": "x.y.z"
}
}

就是b/b这个包替换了a/a这个对应了x.y.z版本的包。如果b/b包中的replace列出了多个包,那么它将替换多个包。在Composer解决依赖的过程中,如果遇到了replace,那么原始的包会被移除,所以它的应用场景非常清晰,就是原始包停止维护了,但是你的项目或包直接或间接依赖一个原始的包,为了可以修复原始包的bug,你可以fork这个包,并在这个包中声明要替换原始包,然后在你的应用或包中依赖这个你fork的包,这样就可以全局替换原始包(原始包被移除)。

由于是包替换,很明显也可能引入安全问题,比如某个包被替换,替换的包中包含恶意代码。

关于replace的第二段解释:
”This is also useful for packages that contain sub-packages, for example the main symfony/symfony package contains all the Symfony Components which are also available as individual packages. If you require the main package it will automatically fulfill any requirement of one of the individual components, since it replaces them.“

这里描述的就是现代框架的组织方式。一个框架(或一个大组件),是很多子包组成的,每个子包都可以单独使用,通常每个子包都会以只读的方式fork到另一个包名,当单独使用时,可以依赖这个只读的包,当依赖整个框架时,框架中声明了替换,则可以移除单独依赖时下载的包。

比如laravel/framework是由很多包组成的,每个包都以只读的方式fork到另一个包名称:

// https://github.com/laravel/framework
"replace": {
        "illuminate/auth": "self.version",
        "illuminate/broadcasting": "self.version",
        "illuminate/bus": "self.version",
        "illuminate/cache": "self.version",
        "illuminate/config": "self.version",
        "illuminate/console": "self.version",
        "illuminate/container": "self.version"
}

这个包包括了子包illuminate/container,而它被只读方式fork了一份到https://github.com/illuminate/container,所以当要仅用这个组件时,可以composer require illuminate/container即可。在依赖了illuminate/container后,如何由依赖laravel/framework,这个时候会引起命名冲突,但是laravel/framework中声明了illuminate/container这个包被laravel/framework替换,所以在依赖laravel/framework后,illuminate/container独立的下载将被移除,从而解决了名称冲突。

第一个用法,提供了一个全局包替换的机会。第二个用法,提供了一个现代化的框架组织方式。

Laravel 容器源码研究

<?php
//error_reporting(0);

include "vendor/autoload.php";

class A
{
    public $b = null;

    public $p = 0;

    public function __construct($b, $p)
    {
        $this->b = $b;
        $this->p = $p;
    }

    public function test()
    {
        echo "A-------\n";
        var_dump($this->p);
        $this->b->test();
    }
}

class B
{
    public $c = null;

    public $o = 0;

    public function __construct(C $c, $o = 0)
    {
        $this->c = $c;
        $this->o = $o;
    }

    public function test()
    {
        echo "B-------\n";
        if ($this->o) {
            echo 'Great 0-----';
        }
        $this->c->test();
    }
}

class C
{
    public function test()
    {
        echo "C-------\n";
    }
}

$container = new \Illuminate\Container\Container();
$p = 10101000;

// A类构造函数依赖形参$p(没有默认值),需要传递: 通过when()方法指定
$b = $container->make(B::class, ['o' => 1]);
$container->when(A::class)->needs('$p')->give($p);
$container->when(A::class)->needs('$b')->give($b);
//$container->when(A::class)->needs(B::class)->give(function () use ($b) {
//    return $b;
//});
$a = $container->make(A::class);

// A类构造函数依赖形参$p(没有默认值),需要传递:通过make()方法的第二参数传递
//$a = $container->make(A::class, ['p' => $p]);
$a->test();

dd((array)$container);

一、对象构建:

    public function make($abstract, array $parameters = [])
    {
        return $this->resolve($abstract, $parameters);
    }

方法make只是resolve()方法的包装而已(注意:make方法是公共的,而resolve是受保护的):

    protected function resolve($abstract, $parameters = [])
    {
	// 如果是别名,取回原来的名称:比如aa是a的别名,传递aa进来,返回a
	// 传递a进来,直接返回a
        $abstract = $this->getAlias($abstract);

        $needsContextualBuild = ! empty($parameters) || ! is_null(
            $this->getContextualConcrete($abstract)
        );

        // 在instances中,并且没有上下文对象,直接返回
        if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
            return $this->instances[$abstract];
        }

	// 把传递进来的参数进行临时保存(入栈)
        $this->with[] = $parameters;

        $concrete = $this->getConcrete($abstract);

        // 递归构建对象
        if ($this->isBuildable($concrete, $abstract)) {
            $object = $this->build($concrete);
        } else {
            $object = $this->make($concrete);
        }

        // 应用对象装饰
        foreach ($this->getExtenders($abstract) as $extender) {
            $object = $extender($object, $this);
        }

        // 如果是share类型,并且没有上下文依赖,就存入instances:并不是设置为share就一定进入instances
        if ($this->isShared($abstract) && ! $needsContextualBuild) {
            $this->instances[$abstract] = $object;
        }

	// 回调
        $this->fireResolvingCallbacks($abstract, $object);

        // 标记已经取回
        $this->resolved[$abstract] = true;

	// 出栈
        array_pop($this->with);

        return $object;
    }

类目传递了A,这时$abstract是A, $parameters是[‘p’ => $p],$abstract经过getAlias后没有发送变化(不是别名),由于参数不是空,$needsContextualBuild为true; $this->getConcrete($abstract)后得到的$concrete是$abstract本身(都是A),然后判断是否可以构建:

    protected function isBuildable($concrete, $abstract)
    {
        return $concrete === $abstract || $concrete instanceof Closure;
    }

是否可以构建,条件是$concrete和$abstract相等,或者$concrete是一个闭包函数。所以这里会进入build()方法:

    public function build($concrete)
    {
        // 如果是闭包,调用闭包函数:第一参数是容器,第二参数是外部传递进来的参数
        if ($concrete instanceof Closure) {
            return $concrete($this, $this->getLastParameterOverride());
        }
	// 不是闭包就利用反射:$concrete这里应该是类名(或类对象)
        $reflector = new ReflectionClass($concrete);

        // 如果不可实例化,抛异常
        if (! $reflector->isInstantiable()) {
            return $this->notInstantiable($concrete);
        }

	// 由于可能会依赖其它对象,这里先把当前类目入栈
        $this->buildStack[] = $concrete;
	// 取回构造函数
        $constructor = $reflector->getConstructor();

        // 构造函数为空,直接实例化
        if (is_null($constructor)) {
            array_pop($this->buildStack);

            return new $concrete;
        }
	// 否则就是指定了构造函数,那么解析这个构造函数,取回参数
        $dependencies = $constructor->getParameters();

        // 根据构造函数的参数,实例化依赖,这里可能会产生递归
	// 另外,如果外部有传递参数进来,会优先使用
        $instances = $this->resolveDependencies(
            $dependencies
        );
	// 出栈
        array_pop($this->buildStack);

	// 把实例对象传递构造函数生成对象
        return $reflector->newInstanceArgs($instances);
    }

这个构建的过程,利用反射,如果依赖其它对象,则递归构建依赖。 这里面用到了两个栈,with是参数堆栈,每次构建从其中取出参数,填充到构造函数,另一个是buildStack。这里可以看到A传递进来,它不是闭包,那么就开始了下面的反射构建过程,A是可以实例化的,有构造函数,那么会进入resolveDependencies()递归解决依赖:

    protected function resolveDependencies(array $dependencies)
    {
        $results = [];

        foreach ($dependencies as $dependency) {
            if ($this->hasParameterOverride($dependency)) {
                $results[] = $this->getParameterOverride($dependency);

                continue;
            }

            $results[] = is_null($dependency->getClass())
                            ? $this->resolvePrimitive($dependency)
                            : $this->resolveClass($dependency);
        }

        return $results;
    }

这里看明白如下几个函数:

    protected function hasParameterOverride($dependency)
    {
        return array_key_exists(
            $dependency->name, $this->getLastParameterOverride()
        );
    }

    protected function getParameterOverride($dependency)
    {
        return $this->getLastParameterOverride()[$dependency->name];
    }

    protected function getLastParameterOverride()
    {
        return count($this->with) ? end($this->with) : [];
    }

方法getLastParameterOverride取回with堆栈的头元素,就是传递进来的参数,方法hasParameterOverride就是判断传递进来的参数有没有对应名称的变量,方法getParameterOverride就是取到传递进来的值。在这,传递进来的参数是[‘p’ => $p], 那么在构建A时,取到形参p,p出现在了[‘p’ => $p]中,所以A构造函数的$p就等于传递进来的变量$p。如果没有对应的参数,就检查是否有指定上下文参数,有则使用,没有,如果是类就去实例类(可能递归),如果是原始变量,就使用默认值。

由于A构造函数需要B对象,而B对象又需要C对象,所以会进入递归,最终返回的对象传递到A构造函数。从这个过程可见,容器的make方法,可以自动构建对象。如果没有构造函数,或构造函数没有依赖其它对象,直接new即可。到这里,还有问题问解决,当构建A时,会去构建B,如果希望使用一个现成的B对象,这个需要依靠上下文绑定来解决。

二、上下文绑定:

//当解析A::class,如果需要B::class类型对象,那么对应的对象是$b。
$container->when(A::class)->needs(B::class)->give(function () use ($b) {
    return $b;
});
//当解析A::class,如果需要形参是$b时,那么对应的值是$b。
$container->when(A::class)->needs('$b')->give($b);

注意语法,当需要的是类类型时,需要给一个闭包函数,否则直接指定值。另外,如果构造函数依赖的是B $b,这个时候直接给值的语法可能是无作用的,因为如果有类型限定,并且类型是类类型,那么总是根据类来实例化对象,这个过程会取回闭包执行返回对象。

这个用法实际产生如下数组:

$this->contextual['A']['B'] = function () use ($b) {
    return $b;
};
$this->contextual['A']['$b'] = $b;

当A实例化是,遇到了需要实例化B,那么首先判断是否存在$this->contextual[‘A’][‘B’],如果存在直接用,否则就去构造。当遇到形参$b时,就去判断$this->contextual[‘A’][‘$b’]是否存在,存在直接用,否则利用反射API去使用默认值。

容器的when()方法不过是一个语法糖,最终会调用addContextualBinding()方法来设置上下绑定:

    public function addContextualBinding($concrete, $abstract, $implementation)
    {
        $this->contextual[$concrete][$this->getAlias($abstract)] = $implementation;
    }

可以这样用:

$container->addContextualBinding(A::class, B::class, function () use ($b) {
    return $b;
});
$container->addContextualBinding(A::class, '$b', $b);

这个上下文绑定,跟make()时传递的第二参数作用非常类似,不过make时传递的参数需要和构造函数的形参名称相同,并且总是优先使用的,比如:

$container->addContextualBinding(A::class, '$b', $b);

$container->make(A::class, ['$b' => $b]);

这里的设置了上下文,也传递了第二参数,那么make时传递的总是优先被使用。一旦设置了上下文,或make时传递了参数,那么生成的对象都是独立(不共享,不会压入$this->instances数组),哪怕设置的绑定是共享的,因为跟上下文相关的对象,是特定的。

上下文设置或构建时的参数传递严格意义上说并不是容器需要实现的内容。这里显然是为了能便利的构建依赖而引入的便利方法。

三、对象构建时的回调:
对象构建之后,可以设置回调,可以全局设置回调(每构建一个对象都调用),或者设置在构建某个类型的对象时才执行回调:

$container = new \Illuminate\Container\Container();
$callBack = function () {
    echo "Callback -------\n";
};

$globalAfterCallBack = function () {
    echo "Global After Callback -------\n";
};

$afterCallBack = function () {
    echo "After Callback -------\n";
};
$container->resolving($globalCallBack);
$container->resolving('a', $callBack);
$container->afterResolving($globalAfterCallBack);
$container->afterResolving('a', $afterCallBack);

四、对象装饰器:
对象构建后,需要执行对象的某个方法,或者运行某个逻辑来扩展一下这个对象,这个就是对象的装饰。在对象生成后,可以使用extend来装饰:

$container->extend(Service::class, function($service) {
    return new DecoratedService($service);
});

五、绑定
在理解了以上基本内容后,再来看看绑定,应该就很好理解了。

// 直接绑定一个类,可以这样做,除了类实例化会延后外,没有额外好处,一般应该避免这个用法
// 在需要是直接make即可
$container->bind(A::class);

// 设置服务,当make(‘a’)时,会实时生成一个A对象
$container->bind(‘a’, A::class);

// 绑定接口,AI实现了I的接口
$container->bind(‘I’, AI::class);

// 设置服务,指定闭包函数,多见
$container->bind(‘events’, function () {});

主要到以上用法尽管都是合法的,但是绑定闭包这种用法比较多见。另外,bing的第三参数控制这个bind是否是共享的,也就是生成实例是否是单例的,bind方法默认是不共享的,有一个快捷方法singleton()可以直接设置共享绑定。

如果绑定是共享的,生成的实例会压入instances数组,表示这些是共享对象,所以有一个方法,可以往这个数组中直接压入内容进行全局共享:instance($key, $instance)。

接下来看看具体过程:

    public function bind($abstract, $concrete = null, $shared = false)
    {
        $this->dropStaleInstances($abstract);

        if (is_null($concrete)) {
            $concrete = $abstract;
        }

        if (! $concrete instanceof Closure) {
            $concrete = $this->getClosure($abstract, $concrete);
        }

        $this->bindings[$abstract] = compact('concrete', 'shared');

        if ($this->resolved($abstract)) {
            $this->rebound($abstract);
        }
    }

简单来说就是对一个类型设置绑定,第二参数是要绑定的内容,第三参数设置是否共享,它决定了对象是否是单例对象。首先调用的$this->dropStaleInstances($abstract),对应的方法实际就是清空该类型对应的绑定和对应的实例:

    protected function dropStaleInstances($abstract)
    {
        unset($this->instances[$abstract], $this->aliases[$abstract]);
    }

如果第二参数是null(没有传递需要绑定的内容),那么绑定的内容就是类型本身。如果绑定的内容如果不是一个闭包函数,那么就调用getClosure()把内容变成一个闭包函数:

    protected function getClosure($abstract, $concrete)
    {
        return function ($container, $parameters = []) use ($abstract, $concrete) {
            if ($abstract == $concrete) {
                return $container->build($concrete);
            }

            return $container->make($concrete, $parameters);
        };
    }

这个getClosure()返回的是一个闭包函数,返回的这个闭包函数接受两个参数($container, $parameters = []),如果类型和内容一样,就直接build,否则就是make一个对象,总之现在知道其返回的是function ($container, $parameters = [])即可。

接下来按照一个统一的格式设置绑定:

$this->bindings[$abstract] = compact('concrete', 'shared');

if ($this->resolved($abstract)) {
    $this->rebound($abstract);
}

// 举例来说
$this->bindings[‘a’] = [
    ‘concrete’ => function ($container, $parameters = []) {},
    ‘shared’ => false
];

那么在a实例被生成时,concrete对应的闭包函数会被执行,其中会根据shared来决定是否设置共享实例。resolved()方法用来判断绑定是否已经生成了实例,如果生成了实例,调用rebound()方法重新生成一次(实际是调用make方法,不过,在重写生成对象时,可以对特定类型设置回调,这个方法会执行回调)。

绑定的其它情况分析:
第二参数只要不是闭包函数,那么总会调用getClosure()方法封装为一个闭包函数:
1 当第二参数为空时,把第一参数赋值给第二参数,调用getClosure()方法

class A {}

$container->bind(A::class);
// $abstract = $concrete = A::class
$container->bind['A'] = function ($container, $parameters = []) use ($abstract, $concrete) {
     if ($abstract == $concrete) {
         return $container->build($concrete);
     }

     return $container->make($concrete, $parameters);
};
// 此时获取该服务跟直接new基本一样
// 不一样的是实例生成时,如果构造函数依赖容器对象,可以自动注入

2 当是一个对象时,调用getClosure()方法

class A {}

$container->bind('aa', new A());
// $abstract = 'aa'; $concrete = new A();
$container->bind['aa'] = function ($container, $parameters = []) use ($abstract, $concrete) {
     if ($abstract == $concrete) {
         return $container->build($concrete);
     }

     return $container->make($concrete, $parameters);
};
// 在实例化时会运行$container->make('aa', []), ‘aa’对应的闭包函数被运行,该函数内再次运行$container->make($concrete, $parameters);
// 此时的$concrete是一个对象,PHP的isset($this->aliases[$concrete])会报警告,但不会影响程序运行
// 第二次运行的make最终会到达build函数,这是的build不过是根据$concrete(此时是一个对象)并应用反射,重新生成一个对象而已

所以这两种用法都应该避免。相反,在使用bind时,应该总是使用闭包。

另外,第二参数是闭包时,这个闭包函数在定义参数时注意:

$container->bind('aa', function ($a, $b, $c) {});

如果调用make时,将会报错。因为闭包调用时总是如此格式:

return $concrete($this, $this->getLastParameterOverride());

闭包可以不定义参数,以上的用法不会发生错误。如果定义了参数,第一个参数总是代表容器实例本身,第二个参数是一个数组(可不定义)用来从外传入数据到闭包中。如果多于2个参数就发生错误。如果定义了第二参数,那么在make对象时,就可以传递数据进去(注:如果是单例实例,一旦生成后不会再改变,除非强制rebund,所以不能期望通过传递参数改变单例对象的状态)

最终提炼一下公共方法:

public function when($concrete)

// 服务是否已经设置
public function bound($abstract)

// 方法bound的别名
public function has($id)

// 服务是否已经实例化
public function resolved($abstract)

// 服务是否是共享的(单例)
public function isShared($abstract)

// 名称是否是别名
public function isAlias($name)

// 服务绑定
public function bind($abstract, $concrete = null, $shared = false)

public function hasMethodBinding($method)
public function bindMethod($method, $callback)
public function callMethodBinding($method, $instance)

// 上下文绑定
public function addContextualBinding($concrete, $abstract, $implementation)

// 条件绑定,bind方法的再封装
public function bindIf($abstract, $concrete = null, $shared = false)

// 单例绑定
public function singleton($abstract, $concrete = null)

// 服务器装饰器
public function extend($abstract, Closure $closure)

// 直接注入内容到容器
public function instance($abstract, $instance)

public function tag($abstracts, $tags)
public function tagged($tag)

// 服务别名设置
public function alias($abstract, $alias)

// 绑定回调设置
public function rebinding($abstract, Closure $callback)

public function refresh($abstract, $target, $method)
public function wrap(Closure $callback, array $parameters = [])
public function call($callback, array $parameters = [], $defaultMethod = null)

// 用一个闭包封装make
public function factory($abstract)

// 方法make的另一种形式
public function makeWith($abstract, array $parameters = [])

// 构建服务
public function make($abstract, array $parameters = [])
// 
public function get($id)

// 构建服务
public function build($concrete)

public function resolving($abstract, Closure $callback = null)
public function afterResolving($abstract, Closure $callback = null)

public function getBindings()
public function getAlias($abstract)
public function forgetExtenders($abstract)
public function forgetInstance($abstract)
public function forgetInstances()
public function flush()

实际常用的就是几个方法。

PHP 反射

PHP对反射提供了完整的支持。http://php.net/manual/zh/intro.reflection.php

类图简单结构:

命令行工具:

php —help
  --rf <name>      Show information about function <name>.
  --rc <name>      Show information about class <name>.
  --re <name>      Show information about extension <name>.
  --rz <name>      Show information about Zend extension <name>.
  --ri <name>      Show configuration for extension <name>.

查看一个实例:

class B {
    public function test()
    {
        echo date("Y-m-d H:i:s") . "\n";
    }
}

class A {
    public $b = null;

    public $try = 0;

    public function __construct(B $b, $try = 111)
    {
        $this->b = $b;
        $this->try = $try;
    }

    public function test()
    {
        echo "B-------\n";
        $this->b->test();
        echo "A--------\n";
        echo date("Y-m-d H:i:s") . "\n";
        var_dump($this->try);
        echo "\n";
    }
}

// 反射类
$reflector = new ReflectionClass(A::class);
// 是否可实例化
$instantiable = $reflector->isInstantiable();
if ($instantiable) {
    // 取到构造函数
    $constructor = $reflector->getConstructor();
    // 取到构造函数的参数
    $dependencies = $constructor->getParameters();
    print_r($constructor);
    print_r($dependencies);
    $instances = [];
    // 根据构造函数的参数,确定依赖
    foreach ($dependencies as $idx => $dependency) {
        // $dependency是一个ReflectionParameter对象实例
        // getClass()取回参数对应的类原型定义(如果不是类则返回null)
        $class = $dependency->getClass();
        if (is_null($class)) {
           echo $dependency->getName() . " - Class is Null \n";
           // 确定参数是否可选,如果可选则使用默认值
           if ($dependency->isOptional()) {
               $instances[$idx] = $dependency->getDefaultValue();
           } else {
               throw new Exception();
           }
        } else {
            echo $dependency->getName() . " - Class is $class->name \n";
            try {
                // 构建对象
                $className = $class->name;
                $instances[$idx] = new $className();
            } catch (Exception $e) {
                if ($dependency->isOptional()) {
                    $instances[$idx] = $dependency->getDefaultValue();
                }
                throw $e;
            }
        }
    }
    // 把取回的依赖注入构造函数
    $a = $reflector->newInstanceArgs($instances);
    $a->test();
}

这个实例中,A类的构造函数需要注入B类对象。利用反射的支持,先取回A类的构造函数,然后分析构造函数参数,根据参数自动实例化依赖,最后生成对象。这里有一个问题,如果B类实例化时需要依赖其它类对象注入该怎么办? 实际这个就是一个递归过程而已。如果需要构建一个对象,对象的依赖自动解决(依赖对象自动生成),这个只有利用反射能实现。

反射也是实现依赖注入容器的基础。

Mac 开发环境搭建

进入Mac的默认Shell终端,安装Homebrew工具:

Homebrew是一个包管理器,用于在Mac上安装一些OS X没有的UNIX工具(比如著名的wget)。 Homebrew将这些工具统统安装到了/usr/local/Cellar目录并在/usr/local/bin中创建符号链接。

官方网站:
http://brew.sh/index_zh-cn.html

安装:

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

#检查安装:
brew -v

Homebrew 常用命令

brew install git

brew uninstall git

brew search git

brew list

brew update

#更新某具体软件
brew upgrade git

#查看软件信息
brew [info | home] [FORMULA...]

#和upgrade一样,单个软件删除 和 所有程序删除。清理所有已安装软件包的历史老版本
brew cleanup git 
brew cleanup

#查看哪些已安装的程序需要更新
brew outdated

Homebrew 卸载

    cd `brew --prefix`
    rm -rf Cellar
    brew prune 
    rm `git ls-files` 
    rm -rf Library .git .gitignore bin/brew
    rm -rf README.md share/man/man1/brew
    rm -rf Library/Homebrew Library/Aliases 
    rm -rf Library/Formula Library/Contributions
    rm -rf ~/Library/Caches/Homebrew

一 安装PHP

#搜索,会出现几个分之,比如PHP56 PHP71
brew search php
#过滤,只要71分支,提供了非常多扩展包
brew search php71
#安装(选择需要的扩展包)
brew install homebrew/php/php71 homebrew/php/php71-apcu homebrew/php/php71-redis homebrew/php/php71-mongodb homebrew/php/php71-opcache omebrew/php/php71-swoole

大部分PHP的模块,都包含在了homebrew/php/php71中,是编译到内核的(非动态模块),上面的apcu,redis,mogondb,swoole是动态模块,模块安装位置:/usr/local/opt/,比如:/usr/local/opt/php71-apcu/apcu.so。配置文件自然是/usr/local/etc/php/7.1/php.ini,扩展的配置放在/usr/local/etc/php/7.1/conf.d/*.ini。

php -v
php -m

编译到内核的模块确实是大而全,然后还需要调整一下php.ini的配置(才能符合开发环境要求):

#设置时区
date.timezone = Asia/Shanghai
 
#CGI相关参数,实际上建议修改的是force_redirect,其它均保留默认值
cgi.force_redirect = 0   #默认为1,改为0
cgi.fix_pathinfo = 1     #默认是1,保留
fastcgi.impersonate = 1  #默认是1,保留
cgi.rfc2616_headers = 0  #默认是0,保留

#其它参数调整,根据实际情况调整
upload_max_filesize = 64M
max_execution_time = 1200
max_input_time = 600
max_input_nesting_level = 128
max_input_vars = 2048
memory_limit = 1024M
 
post_max_size = 64M

如果要启动PHPFPM,FPM主配置文件/usr/local/etc/php/7.1/php-fpm.conf,池配置在/usr/local/etc/php/7.1/php-fpm.d中,需要注意的是,池配置中,默认的运行用户是和用户组均为_www,所以需要检查文件的权限,保证对_www具有读和执行(默认是符合的),如果要写入,那么还需要保证对应的文件夹有被写入的权限。

启动PHPFPM,由于php-fpm这个命令放入到了/usr/local/sbin中,默认shell并不搜索这个路径,所以要想添加环境变量:

#设置环境变量
export PATH="/usr/local/sbin:$PATH"  
echo 'export PATH="/usr/local/sbin:$PATH"' >> ~/.bash_profile

#确认命令能找到
which php-fpm
which php71-fpm

#手动启动,PHPFPM可以不使用root身份启动(user和group指令无用),会使用当前用户运行
sudo php71-fpm start
sudo php71-fpm stop

对于开发环境,PHPFPM可以不用启动,直接使用PHP内置的HTTP服务器也可以。

二 安装Nginx

brew install --with-http2 nginx  

如果要绑定到80端口,那么Nginx就必须以root身份运行。默认的server配置位于(可改):/usr/local/etc/nginx/servers。可以往里面方式配置:

server {
    listen 80;
    #listen 443 ssl http2;
    server_name test.app;
    root "/Users/xx/www/test/public";
 
    index index.html index.htm index.php;
 
    charset utf-8;
 
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }
 
    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }
 
    access_log off;
 
    sendfile off;
 
    location ~ \.php$ {
        client_max_body_size 64M;
        fastcgi_intercept_errors off;
        fastcgi_connect_timeout 300;
        fastcgi_send_timeout 300;
        fastcgi_read_timeout 300;
        fastcgi_buffer_size 32k;
        fastcgi_buffers 64 32k;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
 
    location ~ /\.ht {
        deny all;
    }
 
    #ssl_certificate     /etc/nginx/ssl/test.app.crt;
    #ssl_certificate_key /etc/nginx/ssl/test.app.key;
}

Nginx的主配置文件设置的启动user一般应该和PHPFPM相同,或者需要保证Nginx对文件具备读和执行的权限。如果是文件上传,还需要确保Nginx对临时中间文件夹具备写入权限。

启动关闭等:

sudo nginx -t
sudo nginx -s start
sudo nginx -s stop

三 安装MySQL

#安装最新版本(5.7.xx)
brew install mysql

#确定搜索路径:
which mysqld
mysqld —verbose —help | grep -A 1 ‘Default options’

/etc/my.cnf  /etc/mysql/my.cnf  /usr/local/etc/my.cnf  ~/.my.cnf

#
mysql.server start
mysql_secure_installation

# 停止
mysql.server stop

MySQL不需要以root身份启动。

四、安装Tomcat

brew search tomcat
==> Searching local taps...
tomcat ✔            tomcat-native       tomcat@6            tomcat@7            tomcat@8
==> Searching taps on GitHub...
==> Searching blacklisted, migrated and deleted formulae...

# 安装最新版本
brew install tomcat

#安装指定版本
brew install tomcat@8

启动关闭:

catalina --help
Using CATALINA_BASE:   /usr/local/Cellar/tomcat/9.0.6/libexec
Using CATALINA_HOME:   /usr/local/Cellar/tomcat/9.0.6/libexec
Using CATALINA_TMPDIR: /usr/local/Cellar/tomcat/9.0.6/libexec/temp
Using JRE_HOME:        /Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home
Using CLASSPATH:       /usr/local/Cellar/tomcat/9.0.6/libexec/bin/bootstrap.jar:/usr/local/Cellar/tomcat/9.0.6/libexec/bin/tomcat-juli.jar
Usage: catalina.sh ( commands ... )
commands:
  debug             Start Catalina in a debugger
  debug -security   Debug Catalina with a security manager
  jpda start        Start Catalina under JPDA debugger
  run               Start Catalina in the current window
  run -security     Start in the current window with security manager
  start             Start Catalina in a separate window
  start -security   Start in a separate window with security manager
  stop              Stop Catalina, waiting up to 5 seconds for the process to end
  stop n            Stop Catalina, waiting up to n seconds for the process to end
  stop -force       Stop Catalina, wait up to 5 seconds and then use kill -KILL if still running
  stop n -force     Stop Catalina, wait up to n seconds and then use kill -KILL if still running
  configtest        Run a basic syntax check on server.xml - check exit code for result
  version           What version of tomcat are you running?
Note: Waiting for the process to end and use of the -force option require that $CATALINA_PID is defined

关于启动问题:
在Mac下,如果要开机启动,可以参考如下配置(一般不需要):

#Nginx
cp /usr/local/opt/nginx/homebrew.mxcl.nginx.plist ~/Library/LaunchAgents/
launchctl load ~/Library/LaunchAgents/homebrew.mxcl.nginx.plist  

# PHP-FPM
cp /usr/local/opt/php70/homebrew.mxcl.php71.plist ~/Library/LaunchAgents/  
launchctl load ~/Library/LaunchAgents/homebrew.mxcl.php71.plist  

# MySQL
cp /usr/local/opt/mysql/homebrew.mxcl.mysql.plist ~/Library/LaunchAgents/  
launchctl load ~/Library/LaunchAgents/homebrew.mxcl.mysql.plist

## 卸载
launchctl unload ~/Library/LaunchAgents/homebrew.mxcl.nginx.plist  
launchctl unload ~/Library/LaunchAgents/homebrew.mxcl.php71.plist  
launchctl unload ~/Library/LaunchAgents/homebrew.mxcl.mysql.plist  
rm ~/Library/LaunchAgents/homebrew.mxcl.nginx.plist  
rm ~/Library/LaunchAgents/homebrew.mxcl.php71.plist  
rm ~/Library/LaunchAgents/homebrew.mxcl.mysql.plist

更加实际的方法:进入操作系统后,启动Nginx和PHPFPM(因为要sudo,需要输入密码),MySQL则在需要时启动,比如本地一般链接远程数据库。所以可以这个别名:(往.bash_profile中写入)

alias servers.start='sudo nginx && php-fpm --fpm-config /usr/local/etc/php/7.1/php-fpm.conf -D'
alias servers.stop='sudo bash -c "killall -9 php-fpm && nginx -s stop"'                       
alias nginx.logs='tail -f /usr/local/opt/nginx/access.log'
alias nginx.errors='tail -f /usr/local/opt/nginx/error.log'

遇到问题:
1 Nginx启动提示

nginx: [emerg] getgrnam("

提示大体就是找不到用户组的意思。在Nginx配置中,user如果只指定了用户名,默认会去寻找同名的用户组,在Mac中,用户不一定对应一个同名的用户组,所以出现这种情况就是需要明确指定存在的用户组,可以通过如下方式来确定用户和用户组:

#当前登录的用户名
whoami
www

#确认用户组(可见www的uid是502,对应的组id是20,名称是staff)
id
uid=502(www) gid=20(staff) groups=20(staff),12(everyone)

把www和staff对应填入,错误提示消失。

Hack PHP: 黑你没商量

PHP7和它之前的版本比较,有了巨大的性能提升。对于一门具有20多年历史的语言,还能有如此大的性能提升,确实不容易。不过这也间接说明PHP7之前的版本有点烂吧。PHP7之后的PHP7.1和PHP7.2,都没有加入JIT,说好的JIT在PHP7中被跳票了。PHP的历史包袱是很重的,需要多方面兼顾。说PHP7接近HHVM运行PHP的性能,这其实是需要打问号的。从原理上来说,一个没有JIT的运行引擎会比一个具备JIT的运行引擎更快应该不可能,否则PHP还搞什么JIT。

PHP是弱类型的,这个对JIT来说不太友好。在运行时,需要类型推断,而且需要推断正确才能发挥JIT的作用。对于强类型语言,JIT就好做的多,于是出现了Hack(Hack PHP一把的意思),它引入了类型系统,用Hack写的PHP,HHVM的JIT可以充分发挥,从这个角度来说,HHVM是向JVM看齐的(它的多线程架构也和JAVA类似)。

所以,当前的PHP7和HHVM下的PHP相比,差的何止一点点。PHP7.0引入了类型系统(默认关闭),PHP7.1引入了类型推断优化opcode,PHP7.2还是看不到JIT的影子。这个大概就是PHP自身坚持弱类型,但是又要打造一个实用的JIT之间的矛盾。如果没有HHVM的出现,估计PHP压根没有打造自己的JIT的打算,这事本身就极具悲剧色彩,说HHVM拯救了PHP不为过。

从纯计算的角度,C/C++比Java快,Java比Node.js快,Node.js比PHP快。Java比PHP快一个数量级,不奇怪。Node.js携带的V8引擎自带JIT,可能已经到达极限,瓶颈在弱类型。目前Node.js在吞噬PHP的市场,它的生态虽然很火爆,但是工程化比PHP还是差很多。Java虽然运行很快,但是由于其臃肿的体积,在Web领域,无法撼动PHP的市场。多语言并存,相互协作已是常态。

PHP引入JIT还是非常值得期待的,从现有思路来看,官方希望在opcode上进行透明操作,或者提供一个开关也是一个不错的做法。比如对应新的项目,开启类型系统,开启JIT。

PHP 拟合函数计算趋势线

    // 拟合算法(最小二乘法)
    // 输入: $values = [2, 3, 5, 4, 7, 8, 3, 2, 6];
    // 输出: $values = [3.578, 3.794, 4.011, 4.228, 4.444, 4.661, 4.878, 5.094, 5.311];
    // 斜率(正切值): k = y / x;   =>   5.311 - 3.578 / 9 = 0.1925,最终结果是负无穷 -> 0 -> 正无穷
    // k=0 说明无斜率,与X轴平行, k=1说明斜度为45度,大于1说明斜度大于45度(分正负)
    // atan(k)获取弧度角,弧度换算为角度:atan(k) * (180 / M_PI);
    public function trendLine(array $values)
    {
        $n = count($values);
        $sumX = 0;
        $sumY = 0.0;
        $sumXX = 0;
        $sumXY = 0.0;
        for($i = 1; $i <= $n; $i++) {
            $sumX += $i;
            $sumY += $values[$i - 1];
            $sumXX += $i * $i;
            $sumXY += $i * $values[$i - 1];
        }
        // 求a,b
        $b = ($n * $sumXY - $sumX * $sumY) / ($n * $sumXX - $sumX * $sumX);
        $a = ($sumY - $b * $sumX) / $n;
        // 返回趋势线y值
        $ys = [];
        for($i = 1; $i <= $n; $i++) {
            $ys[$i - 1] = round($b * $i + $a, 3);
        }
        // 斜率(正切值)
        $slope = ($ys[$n -1] - $ys[0]) / $n;
        // 弧度
        $radians = atan($slope);
        // 角度(1度 = 180 / M_PI = 57.297弧度)
        $degree = $radians * 57.297;

        return [
            'slope' => $slope,
            'radians' => $radians,
            'degree' => $degree,
            'values' => $ys
        ];
    }

至于最小二乘法的公式和原理可以搜索一下。根据这个计算的结果,可以得到一条直线的趋势线,通常我们顺便计算一下斜率以及夹角大小(弧度角和角度)。

Zephir用类似PHP的语法写PHP C扩展

Zephir是一个非常有意思的项目。它是专门针对编写PHP C扩展而创建的,是PHP C框架Phalcon的实现语言。Zephir提供了类似PHP的语法,然后转换成PHP C扩展代码,然后就是正常的C扩展编译和安装。

官网:http://zephir-lang.com/

Zephir依赖re2c来进行语法分析(PHP本身也使用re2c来进行语法分析),需要把Zephir代码转换为PHP C扩展的C代码,所以依赖PHP的头文件,然后需要编译,那么就必须有编译器(gcc):(https://docs.zephir-lang.com/en/latest/install.html)

# 安装PHP的头文件,如果是RPM安装,需要安装devel包
php70w-devel-7.0.13-1.w7.x86_64

# 安装re2c:http://re2c.org/
yum install re2c

# 安装编译器
yum install gcc make autoconf git

#安装
git clone https://github.com/phalcon/zephir
cd zehpir
./install -c   #拷贝zephir/bin/zephir 到  /usr/local/bin/zephir

zephir help

编译实例:

git clone --depth=1 https://github.com/phalcongelist/beanspeak.git
cd beanspeak
zephir build
# Or, for PHP 7 use
zephir build --backend=ZendEngine3

参考例子:

#https://github.com/phalcongelist/beanspeak/blob/master/beanspeak/client.zep
namespace Beanspeak;

/**
 * Beanspeak\Client
 *
 * Class to access the beanstalk queue service.
 *
 * Implements the beanstalk protocol spec 1.10.
 *
 * <code>
 * use Beanspeak\Client;
 *
 * // Each connection parameter is optional
 * $queue = new Client([
 *     'host'       => '127.0.0.1', // The beanstalk server hostname or IP address to connect to
 *     'port'       => 11300,       // The port of the server to connect to
 *     'timeout'    => 60,          // Timeout in seconds when establishing the connection
 *     'persistent' => true,        // Whether to make the connection persistent or not
 *     'wretries'   => 8,           // Write retries
 * ]);
 * </code>
 *
 * @link https://github.com/kr/beanstalkd/
 */
class Client
{
	/**
	 * The current socket connection.
	 * @var resource
	 */
	protected socket;

	/**
	 * The current connection options.
	 * @var array
	 */
	protected options = [];

	/**
	 * The current used tube.
	 * @var string
	 */
	protected usedTube = "default";

	/**
	 * The current watched tubes.
	 * @var array
	 */
	protected watchedTubes = [ "default" : true ];

	/**
	 * Beanspeak\Client constructor
	 */
	public function __construct(array options = null)
	{
		array defaults = [
			"host"       : "127.0.0.1",
			"port"       : "11300",
			"persistent" : true,
			"timeout"    : "60",
			"wretries"   : 8
		];

		if typeof options != "array" {
			let this->{"options"} = defaults;
		} else {
			let this->{"options"} = options + defaults;
		}
	}

	/**
	 * Makes a connection to the Beanstalk server.
	 *
	 * The resulting stream will not have any timeout set on it.
	 * Which means it can wait an unlimited amount of time until a packet
	 * becomes available.
	 *
	 * @throws Exception
	 */
	public function connect() -> resource
	{
		var e, options, socket, usedTube, tube;

		if this->isConnected() {
			this->disconnect();
		}

		let options = this->options;

		try {
			if options["persistent"] {
				let socket = pfsockopen(options["host"], options["port"], null, null, options["timeout"]);
			} else {
				let socket = fsockopen(options["host"], options["port"], null, null, options["timeout"]);
			}

			if typeof socket != "resource" {
				throw new Exception("Can't connect to Beanstalk server.");
			}
		} catch  \Exception, e {
			throw new Exception(e->getMessage());
		}

		stream_set_timeout(socket, -1, null);

		let this->{"socket"} = socket,
			usedTube = this->usedTube;

		if usedTube != "default" {
			this->useTube(usedTube);
		}

		for tube, _ in this->watchedTubes {
			if tube != "default" {
				unset(this->watchedTubes[tube]);
				this->watch(tube);
			}
		}

		if !isset this->watchedTubes["default"] {
			this->ignore("default");
		}

		return socket;
	}

	/**
	 * Closes the connection to the Beanstalk server.
	 *
	 * Will throw an exception if closing the connection fails, to allow
	 * handling the then undefined state.
	 *
	 * @throws Exception
	 */
	public function disconnect() -> boolean
	{
		var socket, status;

		if !this->isConnected() {
			return false;
		}

		let socket = this->socket,
			status = fclose(socket);

		if !status {
			throw new Exception("Failed to close connection.");
		}

		let this->{"socket"}       = null,
			this->{"usedTube"}     = "default",
			this->{"watchedTubes"} = [ "default" : true ];

		return true;
	}

	/**
	 * Whether the connection is established or not.
	 */
	public function isConnected() -> boolean
	{
		return typeof this->socket == "resource";
	}

	/**
	 * Inserts a job into the client's currently used tube.
	 *
	 * <code>
	 * $task = [
	 *     'recipient' => 'user@mail.com',
	 *     'subject'   => 'Welcome',
	 *     'content'   => $content,
	 * ];
	 *
	 * $put = $queue->pit($task, 999, 60 * 60, 3600);
	 * </code>
	 */
	public function put(var data, int priority = 1024, int delay = 0, int ttr = 86400) -> int|boolean
	{
		var status, response, serialized, length;

		// Data is automatically serialized before be sent to the server
		let serialized = serialize(data),
			length     = strlen(serialized);

		this->write("put " . priority . " " . delay . " " . ttr . " " . length . "\r\n" . serialized);

		let response = this->readStatus();

		if isset response[1] {
			let status = response[0];

			if status == "INSERTED" || status == "BURIED" {
				return intval(response[1]);
			}
		}

		return false;
	}

	/**
	 * Inserts a job into the desired tube.
	 *
	 * <code>
	 * $task = [
	 *     'recipient' => 'user@mail.com',
	 *     'subject'   => 'Welcome',
	 *     'content'   => $content,
	 * ];
	 *
	 * $queue->putInTube('tube-name', $task, 999, 60 * 60, 3600);
	 * </code>
	 */
	public function putInTube(string! tube, var data, int priority = 1024, int delay = 0, int ttr = 86400) -> boolean|int
	{
		var  response;

		let response = this->useTube(tube);
		if typeof response == "object" {
			return this->put(data, priority, delay, ttr);
		}

		return false;
	}

	/**
	 * Change the active tube.
	 *
	 * The "use" command is for producers. Subsequent put commands will put jobs
	 * into the tube specified by this command. If no use command has been issued,
	 * jobs will be put into the tube named "default".
	 *
	 * <code>
	 * $queue->useTube('mail-queue');
	 * </code>
	 *
	 * @throws Exception
	 */
	public function useTube(string! tube) -> <Client>
	{
		var response, status, used;

		let used = this->usedTube;
		if used == tube {
			return this;
		}

		this->write("use " . tube);

		let response = this->readStatus();

		if isset response[1] && response[0] == "USING" {
			let status = response[0];

			if status == "USING" {
				let this->{"usedTube"} = tube;
				return this;
			}
		}

		throw new Exception(
			"Unable to change the active tube. Server response: ". join(" ", response)
		);
	}

	/**
	 * Lets the client inspect a job in the system.
	 *
	 * <code>
	 * $peekJob = $queue->peek($jobId);
	 * </code>
	 */
	public function peekJob(int id) -> boolean|<Job>
	{
		var response;

		this->write("peek " . id);

		let response = this->readStatus();
		if isset response[2] && response[0] == "FOUND" {
			return new Job(this, response[1], unserialize(this->read(response[2])));
		}

		return false;
	}

	/**
	 * Return the delayed job with the shortest delay left.
	 *
	 * <code>
	 * $queue->peekDelayed();
	 * </code>
	 */
	public function peekDelayed() -> boolean|<Job>
	{
		var response;

		this->write("peek-delayed");

		let response = this->readStatus();
		if isset response[2] && response[0] == "FOUND" {
			return new Job(this, response[1], unserialize(this->read(response[2])));
		}

		return false;
	}

	/**
	 * Return the next job in the list of buried jobs.
	 *
	 * <code>
	 * $queue->peekBuried();
	 * </code>
	 */
	public function peekBuried() -> boolean|<Job>
	{
		var response;

		this->write("peek-buried");

		let response = this->readStatus();
		if isset response[2] && response[0] == "FOUND" {
			return new Job(this, response[1], unserialize(this->read(response[2])));
		}

		return false;
	}

	/**
	 * Inspect the next ready job.
	 *
	 * <code>
	 * $queue->peekReady();
	 * </code>
	 */
	public function peekReady() -> boolean|<Job>
	{
		var response;

		this->write("peek-ready");

		let response = this->readStatus();
		if isset response[2] && response[0] == "FOUND" {
			return new Job(this, response[1], unserialize(this->read(response[2])));
		}

		return false;
	}

	/**
	 * Moves jobs into the ready queue.
	 * The Kick command applies only to the currently used tube.
	 *
	 * <code>
	 * $queue->kick(3);
	 * </code>
	 */
	public function kick(int bound) -> boolean|int
	{
		var response;

		this->write("kick " . bound);

		let response = this->readStatus();
		if isset response[1] && response[0] == "KICKED" {
			return intval(response[1]);
		}

		return false;
	}

	/**
	 * Adds the named tube to the watch list, to reserve jobs from.
	 *
	 * <code>
	 * $count = $queue->watch($tube);
	 * </code>
	 *
	 * @throws Exception
	 */
	public function watch(string! tube) -> <Client>
	{
		var response, watchedTubes;

		let watchedTubes = this->watchedTubes;
		if !isset watchedTubes[tube] {
			this->write("watch " . tube);

			let response = this->readStatus();
			if !isset response[1] || response[0] != "WATCHING" {
				throw new Exception("Unhandled response: " . join(" ", response));
			}

			let this->watchedTubes[tube] = true;
		}

		return this;
	}

	/**
	* Adds the named tube to the watch list, to reserve jobs from, and
	* ignores any other tubes remaining on the watchlist.
	*
	* <code>
	* $count = $queue->watchOnly($tube);
	* </code>
	*
	* @throws Exception
	*/
	public function watchOnly(string! tube) -> <Client>
	{
		var watchedTubes, watchedTube;

		this->watch(tube);

		let watchedTubes = this->watchedTubes;
		for watchedTube, _ in watchedTubes {
			if watchedTube == tube {
				continue;
			}

			this->ignore(watchedTube);
		}

		return this;
	}

	/**
	 * Reserves/locks a ready job from the specified tube.
	 *
	 * <code>
	 * $job = $queue->reserve();
	 * </code>
	 *
	 * @throws Exception
	 */
	public function reserve(int timeout = -1) -> boolean|<Job>
	{
		var response;
		string command;

		if timeout >= 0 {
			let command = "reserve-with-timeout " . timeout;
		} else {
			let command = "reserve";
		}

		this->write(command);

		let response = this->readStatus();

		if response[0] != "RESERVED" || !isset response[2] {
			return false;
		}

		return new Job(this, response[1], unserialize(this->read(response[2])));
	}

	/**
	 * Reserves/locks a ready job from the specified tube.
	 *
	 * <code>
	 * $job = $queue->reserve();
	 * </code>
	 *
	 * @throws Exception
	 */
	public function reserveFromTube(string tube, int timeout = -1) -> boolean|<Job>
	{
		this->watch(tube);

		return this->reserve(timeout);
	}

	/**
	 * Removes the named tube from the watch list for the current connection.
	 *
	 * <code>
	 * $count = $queue->ignore('tube-name);
	 * </code>
	 *
	 * @throws Exception
	 */
	public function ignore(string! tube) -> <Client>
	{
		var response, watchedTubes;

		let watchedTubes = this->watchedTubes;
		if typeof watchedTubes != "array" {
			return this;
		}

		if isset watchedTubes[tube] {
			this->write("ignore " . tube);

			let response = this->readStatus();
			if response[0] == "NOT_IGNORED" {
				throw new Exception("Cannot ignore last tube in watchlist.");
			}

			if !isset response[1] || response[0] != "WATCHING" {
				throw new Exception("Unhandled response: " . join(" ", response));
			}

			unset(watchedTubes[tube]);
			let this->watchedTubes = watchedTubes;
		}

		return this;
	}

	/**
	 * Gives statistical information about the system as a whole.
	 *
	 * <code>
	 * $queue->stats();
	 * </code>
	 */
	public function stats() -> boolean|array
	{
		var response;

		this->write("stats");

		let response = this->readYaml();
		if response[0] != "OK" {
			return false;
		}

		return response[2];
	}

	/**
	 * Gives statistical information about the specified tube if it exists.
	 *
	 * <code>
	 * $stats = $queue->statsTube('process-bitcoin');
	 * </code>
	 */
	public function statsTube(string! tube) -> boolean|array
	{
		var response;

		this->write("stats-tube " . tube);

		let response = this->readYaml();
		if response[0] != "OK" {
			return false;
		}

		return response[2];
	}

	/**
	 * Returns a list of all existing tubes.
	 *
	 * <code>
	 * $tubes = $queue->listTubes();
	 * </code>
	 */
	public function listTubes() -> boolean|array
	{
		var response;

		this->write("list-tubes");

		let response = this->readYaml();
		if response[0] != "OK" {
			return false;
		}

		return response[2];
	}

	/**
	 * Returns the tube currently being used by the client.
	 *
	 * <code>
	 * $tube = $queue->listTubeUsed(); // local cache
	 * $tube = $queue->listTubeUsed(); // ask server
	 * </code>
	 *
	 * @throws Exception
	 */
	public function listTubeUsed(boolean ask = false) -> string
	{
		var response;

		if !ask {
			return this->usedTube;
		}

		this->write("list-tube-used");

		let response = this->readStatus();

		if isset response[1] && response[0] == "USING" {
			let this->{"usedTube"} = response[1];
			return response[1];
		}

		throw new Exception("Unhandled response form beanstalkd server: " . join(" ", response));
	}

	/**
	 * Returns a list tubes currently being watched by the client.
	 *
	 * <code>
	 * $tubes = $queue->listTubesWatched(); // local cache
	 * $tubes = $queue->listTubesWatched(true); // ask server
	 * </code>
	 *
	 * @throws Exception
	 */
	public function listTubesWatched(boolean ask = false) -> array
	{
		var response;

		if !ask {
			return array_keys(this->watchedTubes);
		}

		this->write("list-tubes-watched");

		let response = this->readYaml();
		if response[0] != "OK" {
			throw new Exception("Unhandled response form beanstalkd server: " . join(" ", response));
		}

		let this->{"watchedTubes"} = array_fill_keys(response[2], true);

		return this->watchedTubes;
	}

	/**
	 * Can delay any new job being reserved for a given time.
	 *
	 * <code>
	 * $queue->pauseTube('process-video', 60 * 60);
	 * </code>
	 */
	public function pauseTube(string! tube, int delay) -> boolean
	{
		var response;

		this->write("pause-tube " . tube . " " . delay);

		let response = this->readStatus();
		if !isset response[0] || response[0] != "PAUSED" {
			return false;
		}

		return true;
	}

	/**
	 * Resume the tube.
	 *
	 * <code>
	 * $queue->resumeTube('process-video');
	 * </code>
	 */
	public function resumeTube(string! tube) -> boolean
	{
		return this->pauseTube(tube, 0);
	}

	/**
	 * Simply closes the connection.
	 *
	 * <code>
	 * $queue->quit();
	 * </code>
	 */
	public function quit() -> boolean
	{
		this->write("quit");
		this->disconnect();

		return typeof this->socket != "resource";
	}

	/**
	 * Writes data to the socket.
	 * Performs a connection if none is available.
	 * @throws Exception
	 */
	public function write(string data) -> int
	{
		var socket, retries, written, step, length;

		if !this->isConnected() {
			this->connect();

			if !this->isConnected() {
				throw new Exception("Unable to establish connection with beanstalkd server.");
			}
		}

		let retries = this->options["wretries"],
			socket  = this->socket,
			data   .= "\r\n",
			step    = 0,
			written = 0;

		let length = strlen(data);

		while written < length {
			let step++;

			if step >= retries && !written {
				throw new Exception("Failed to write data to socket after " . retries . " tries.");
			}

			let written += fwrite(socket, substr(data, written));
		}

		return written;
	}

	/**
	 * Reads a packet from the socket.
	 * Performs a connection if none is available.
	 * @throws Exception
	 */
	public function read(int length = 0) -> boolean|string
	{
		var socket, data, meta;

		if !this->isConnected() {
			this->connect();

			if !this->isConnected() {
				return false;
			}
		}

		let socket = this->socket;

		if length {
			if feof(socket) {
				throw new Exception("Failed to read data from socket (EOF).");
			}

			let data = stream_get_line(socket, length + 2),
				meta = stream_get_meta_data(socket);

			if meta["timed_out"] {
				throw new Exception("Connection timed out upon attempt to read data from socket.");
			}

			if false === data {
				throw new Exception("Failed to read data from socket.");
			}

			let data = rtrim(data, "\r\n");
		} else {
			let data = stream_get_line(socket, 16384, "\r\n");
		}

		array errors = [
			"UNKNOWN_COMMAND" : "Unnown command.",
			"JOB_TOO_BIG"     : "Job data exceeds server-enforced limit.",
			"BAD_FORMAT"      : "Bad command format.",
			"OUT_OF_MEMORY"   : "Out of memory."
		];

		if isset errors[data] {
			throw new Exception(errors[data]);
		}

		return data;
	}

	/**
	 * Fetch a YAML payload from the Beanstalkd server.
	 */
	final public function readYaml() -> array
	{
		var response, status, data = [], bytes = 0;

		let response = this->readStatus();

		if isset response[0] {
			let status = response[0];
		} else {
			let status = "UNKNOWN";
		}

		if isset response[1] {
			let bytes = response[1],
				data  = this->yamlParse();
		}

		return [
			status,
			bytes,
			data
		];
	}

	/**
	 * Reads the latest status from the Beanstalkd server.
	 */
	final public function readStatus() -> array
	{
		var status;
		let status = this->read();
		if false === status {
			return [];
		}

		return explode(" ", status);
	}

	private function yamlParse() -> array
	{
		var data, lines, key, value, values, tmp, response = [];

		let data = this->read();

		if typeof data != "string" || empty(data) {
			return [];
		}

		if function_exists("yaml_parse") {
			let response = yaml_parse(data);

			return response;
		}

		let data  = rtrim(data),
			lines = preg_split("#[\r\n]+#", rtrim(data));

		if isset lines[0] && lines[0] == "---" {
			array_shift(lines);
		}

		if typeof lines != "array" || empty(lines) {
			trigger_error("YAML parse error.", E_USER_WARNING);
			return [];
		}

		for key, value in lines {
			if starts_with(value, "-") {
				let value = ltrim(value, "- ");
			} elseif strpos(value, ":") !== false {
				let values = explode(":", value);

				if !isset values[1] {
					trigger_error("YAML parse error for line: \"" . value . "\"", E_USER_WARNING);
				} else {
					let key   = values[0],
						value = ltrim(values[1], " ");
				}
			}

			if is_numeric(value) {
				let tmp = intval(value);

				if tmp == value {
					let value = tmp;
				} else {
					let value = doubleval(value);
				}
			}

			let response[key] = value;
		}

		return response;
	}
}

PHP7.0.x编译

PHP安装根据启用的PHP模块,对系统有不同的库文件依赖,一般情况如下:

gd gd-devel			--with-gd --enable-gd-native-ttf
libjpeg libjpeg-devel		--with-jpeg-dir
libpng libpng-devel		--with-png-dir
freetype freetype-devel		--with-freetype-dir

libxml2 libxml2-devel		--enable-xml

libmcrypt libmcrypt-devel	--with-mcrypt
libmhash libmhash-devel		--with-mhash

curl curl-devel			--with-curl

zlib zlib-devel			--with-zlib
openssl openssl-devel		--with-openssl
pcre pcre-devel			PHP的正则功能依赖

安装:

#基本	
yum install libtool autoconf automake gcc gcc-c++ make

#依赖库
yum install gd gd-devel libjpeg libjpeg-devel libpng libpng-devel freetype freetype-devel libxml2 libxml2-devel libmcrypt libmcrypt-devel libmhash libmhash-devel curl curl-devel zlib zlib-devel openssl openssl-devel pcre pcre-devel

#编译参考
./configure \
--prefix=/usr/local/php7 \
--exec-prefix=/usr/local/php7 \
--bindir=/usr/local/php7/bin \
--sbindir=/usr/local/php7/sbin \
--includedir=/usr/local/php7/include \
--libdir=/usr/local/php7/lib/php \
--mandir=/usr/local/php7/php/man \
--with-config-file-path=/usr/local/php7/etc \
--with-mysql-sock=/var/lib/mysql/mysql.sock \
--with-mcrypt=/usr/include \
--with-mhash \
--with-openssl \
--with-mysqli=shared,mysqlnd \
--with-pdo-mysql=shared,mysqlnd \
--with-gd \
--with-iconv \
--with-zlib \
--enable-zip \
--enable-inline-optimization \
--disable-debug \
--disable-rpath \
--enable-shared \
--enable-xml \
--enable-bcmath \
--enable-shmop \
--enable-sysvsem \
--enable-mbregex \
--enable-mbstring \
--enable-ftp \
--enable-gd-native-ttf \
--enable-pcntl \
--enable-sockets \
--with-xmlrpc \
--enable-soap \
--without-pear \
--with-gettext \
--enable-session \
--with-curl \
--with-jpeg-dir \
--with-freetype-dir \
--enable-opcache \
--enable-fpm \
--disable-cgi \
--with-fpm-user=www \
--with-fpm-group=www \
--without-gdbm \
--disable-fileinfo

安装:

Installing shared extensions:     /usr/local/php7/lib/php/extensions/no-debug-non-zts-20151012/
Installing PHP CLI binary:        /usr/local/php7/bin/
Installing PHP CLI man page:      /usr/local/php7/php/man/man1/
Installing PHP FPM binary:        /usr/local/php7/sbin/
Installing PHP FPM config:        /usr/local/php7/etc/
Installing PHP FPM man page:      /usr/local/php7/php/man/man8/
Installing PHP FPM status page:   /usr/local/php7/php/php/fpm/
Installing phpdbg binary:         /usr/local/php7/bin/
Installing phpdbg man page:       /usr/local/php7/php/man/man1/
Installing build environment:     /usr/local/php7/lib/php/build/
Installing header files:           /usr/local/php7/include/php/
Installing helper programs:       /usr/local/php7/bin/
  program: phpize
  program: php-config
Installing man pages:             /usr/local/php7/php/man/man1/
  page: phpize.1
  page: php-config.1
/usr/local/php7/src/build/shtool install -c ext/phar/phar.phar /usr/local/php7/bin
ln -s -f phar.phar /usr/local/php7/bin/phar
Installing PDO headers:           /usr/local/php7/include/php/ext/pdo/

Window 7 下部署Nginx PHP7环境

在Window下做开发,一般会选择使用集成安装包来安装环境,比如wamp, xamp。 由于仅仅是一个开发环境,又希望和生产环境保持一致(Nginx+PHP),在Windows下可以手动配置,实际上不会比一键安装包来得的更复杂。

#下载Nginx  http://nginx.org/
#下载PHP http://windows.php.net/download

如果下载的是PHP 7.x,需要安装VC14,下载地址:http://www.microsoft.com/zh-CN/download/details.aspx?id=48145。注意:这里提供的两个包,分别针对32为和64位的机器。

把下载的Nginx和PHP压缩释放到一个目录(E:/wnmp):

E:\wnmp\nginx
E:\wnmp\php

Nginx配置:

# 1
E:\wnmp\nginx\conf\nginx.conf

#user  nobody;
worker_processes  2;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    ##配置一个默认虚拟主机
    server {
        listen       80;
        server_name  localhost;

	root E:/var/www/default;
    	index index.html index.htm index.php;

        location ~ \.php$ {
            root           E:/var/www/default;
            fastcgi_pass   127.0.0.1:9000;
            fastcgi_index  index.php;
            fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
            include        fastcgi_params;
        }
    }

    ##把其它的虚拟主机放入conf/vhosts中
    include vhosts/*.conf;
}


###############################################
#vhosts/*.conf模子
server {
    listen 80;

    server_name php.dev;

    root D:\wamp\www\php.dev\public;
    index index.html index.htm index.php;

    if (!-e $request_filename) {
        rewrite ^/(.+)$ /index.php last;
        break;
    }

    location / {
	root D:\wamp\www\php.dev\public;
	try_files $uri $uri/ /index.php?$query_string;
    }

    location ~* ^.+\.(js|css|jpeg|jpg|gif|png|ico|eot|ttf|woff|svg)$ {
        expires 5d;
    }

    location ~ \.php$ {
	client_max_body_size 	100M;
        fastcgi_connect_timeout 300;
        fastcgi_send_timeout 	300;
        fastcgi_read_timeout 	300;
        fastcgi_buffer_size 	32k;
        fastcgi_buffers 	256 32k;

        fastcgi_pass   		127.0.0.1:9000;
        fastcgi_index  		index.php;

        fastcgi_param  		HTTPS $https if_not_empty;

        fastcgi_param  		SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        include        		fastcgi_params;
   }
}
###############################################


# 2 测试配置文件正确
E:\wnmp\nginx\nginx.exe -t

# 3 启动
E:\wnmp\nginx\nginx.exe

Nginx启动关闭脚本:

# 启动
nginx_start.bat

@echo off
set NGINX_HOME=E:\wnmp\nginx
start /D %NGINX_HOME%\ %NGINX_HOME%\nginx.exe
pause

# 关闭
nginx_stop.bat

@echo off
set NGINX_HOME=E:\wnmp\nginx
cd %NGINX_HOME%
nginx.exe -s quit
pause

PHP配置:

# 对php.ini文件做一些修改
# 拷贝E:\wnmp\php\php.ini-development为php.ini
# 修改或确认如下参数:

#设置时区
date.timezone = Asia/Shanghai

#允许用户在运行时加载PHP扩展
enable_dl = On           #默认为Off 

#CGI相关参数,实际上建议修改的是force_redirect,其它均保留默认值
cgi.force_redirect = 0   #默认为1,改为0
cgi.fix_pathinfo = 1     #默认是1,保留
fastcgi.impersonate = 1  #默认是1,保留
cgi.rfc2616_headers = 0  #默认是0,保留

#扩展目录
extension_dir = "ext"

#加载扩展 根据需要调整
extension=php_bz2.dll
extension=php_curl.dll
extension=php_fileinfo.dll
extension=php_gd2.dll
extension=php_gettext.dll
extension=php_intl.dll
extension=php_mbstring.dll
extension=php_mysqli.dll
extension=php_openssl.dll
extension=php_pdo_mysql.dll

#扩展opcache是否加载,开发环境可以忽略
[opcache]
zend_extension=php_opcache.dll
opcache.enable=1
opcache.enable_cli=1

#其它参数调整,根据实际情况调整
upload_max_filesize = 64M
max_execution_time = 1200
max_input_time = 600
max_input_nesting_level = 128
max_input_vars = 2048
memory_limit = 1024M

post_max_size = 64M

PHP启动关闭脚本:

#启动x个php-cgi进行(注意,这里没有PHP-FPM管理器,无法动态管理CGI进程,开发环境就无所谓了)
php_start.vbs

createobject("wscript.shell").run "E:\wnmp\php\php-cgi.exe -b 127.0.0.1:9000",0,false
createobject("wscript.shell").run "E:\wnmp\php\php-cgi.exe -b 127.0.0.1:9000",0,false
createobject("wscript.shell").run "E:\wnmp\php\php-cgi.exe -b 127.0.0.1:9000",0,false

#关闭php-cgi进行
php_stop.bat

@echo off
taskkill /fi "imagename eq php-cgi.exe"
pause

启动时,点击nginx_start.bat和php_start.vbs,关闭时点击nginx_stop.bat和php_stop.bat。如果要切换到不同版本的PHP,很简单,直接替换就可以,或者建立多个启动脚本,比如:

start_php5.5.vbs
start_php5.6.vbs
start_php7.0.vbs
start_php7.1.vbs

关闭脚本就不需要建立多个了。

然后修改一下path环境变量:
winpath

phpenv

windowphp

在Linux下,PHP的fpm模块是可用的,但是在Windows下不支持,看起来也没有支持的计划。目前在Windows下也看到有类似实现的,不过一般是监控php-cgi进程数量,死掉重启,如此而已,一般认为没有什么价值。开发环境,直接运行一个脚本,启动几个php-cgi进程即可。

在widows下,可以配置为开启运行这些脚本,避免每次都要点击:
cmd-gpedit-msc

gpedit-window

这样设置就可以开机启动了。

另外,在开发环境下,如果觉得引入Nginx太麻烦,还有更加简便的方法,PHP 5.4以后,引入了一个内置的CLI Http Server,启动方式:

D:\wnmp\php-7.0.11\php.exe -S 127.0.0.1:6060 -t D:\var\www\xx\public

可以监听同一个端口,根据不同的名称来区分不同的项目。也使用IP地址,使用不同端口来区分不同的项目。(相同端口和相同主机名,可以启动多次的)
[sehll]
#基于端口,IP一样,端口不一样
D:\wnmp\php-7.0.11\php.exe -S 127.0.0.1:6060 -t D:\var\www\xx\public
D:\wnmp\php-7.0.11\php.exe -S 127.0.0.1:6061 -t D:\var\www\yy\public

#基于主机名(先做hosts绑定),端口一样,主机名不一样
D:\wnmp\php-7.0.11\php.exe -S vfeelit.local:6060 -t D:\var\www\xx\public
D:\wnmp\php-7.0.11\php.exe -S ifeeline.local:6060 -t D:\var\www\yy\public
[/shell]

为了方便,可以对不同项目编写独立的VBS文件,需要哪个点哪个,这也是一个不错的办法:

createobject("wscript.shell").run "D:\wnmp\php-7.0.11\php.exe -S ifeeline.local:6060 -t D:\var\www\yy\public",0,false