Mac安装体验Zephir

安装

系统要求

  • gcc >= 4.x/clang >= 3.x
  • re2c 0.13 or later
  • Zephir Parser >= 1.1.0
  • gnu make 3.81 or later
  • autoconf 2.31 or later
  • automake 1.14 or later
  • libpcre3
  • php development headers and tools

特别要注意的一点,对PHP的要求>5.5

使用brew安装 re2cjson-c

brew install re2c json-c

安装zephir

注意因为Zephir命令后续依赖源码包里的文件,意味着不能删除,所以把源码包放在安全的目录下。

mkdie -p ~/.tools
cd ~/.tools
git clone https://github.com/phalcon/zephir
cd zephir
./install -c

安装完之后执行zephir help,会看到以下内容

 _____              __    _
/__  /  ___  ____  / /_  (_)____
  / /  / _ \/ __ \/ __ \/ / ___/
 / /__/  __/ /_/ / / / / / /
/____/\___/ .___/_/ /_/_/_/
         /_/

Zephir version 0.10.7-8059e66568

Usage:
        command [options]

Available commands:
        clean               Cleans any object files created by the extension
        install             Installs the extension in the extension directory (may require root password)
        compile             Compile a Zephir extension
        stubs               Generates stubs that can be used in a PHP IDE
        help                Displays this help and exit
        version             Shows the Zephir version
        build               Generates/Builds/Installs a Zephir extension
        fullclean           Cleans any object files created by the extension (including files generated by phpize)
        generate            Generates C code from the Zephir code without compiling it
        builddev            Generates/Builds/Installs a Zephir extension in development mode
        api                 Generates a HTML API based on the classes exposed in the extension
        init                Initializes a Zephir extension

Options:
        --help|-h           Displays command help and exit
        -f([a-z0-9\-]+)     Enables compiler optimizations
        -fno-([a-z0-9\-]+)  Disables compiler optimizations
        -w([a-z0-9\-]+)     Turns a warning on
        -W([a-z0-9\-]+)     Turns a warning off

安装zephir-parser扩展

因为Zephir编译的扩展要依赖自身的解析器,所以得在运行的PHP版本编译该扩展。

git clone https://github.com/phalcon/php-zephir-parser
cd php-zephir-parser
./install

配置php.ini

[Zephir Parser]
extension=zephir_parser.so

检查是否安装成功

php -m|grep "Zephir Parser"

编写一个简单的扩展

初始化项目

mkdir -p ~/phpexts
cd ~/phpexts
zephir init greeting
cd greeting

使用ls命令能看到以config.json,ext,greeting ,分别介绍一下

config.json 设定档,里面应该会写着 namepsace 是什么

ext/ 最后生成的 PHP Extension 会在这个资料夹找到

greeting/ 放置 .zep 档案的目录(类似 PSR-0 的 Class 目录规则,不过都是小写)

编写.zep代码

cd greeting
vim greeting.zep

源码如下

namespace Greeting;

class Greeting
{
    public static function say()
    {
        echo "Hello world!";
    }
}

编译

zephir build

看到以下提示表示编译成功

Preparing for PHP compilation...
Preparing configuration file...
Compiling...
Installing...
Password:
Extension installed!
Add extension=greeting.so to your php.ini
Don't forget to restart your web server

配置php.ini

[Greeting]
extension=greeting.so

检查是否安装成功

php -m|grep -i greeting

测试代码

php -r "\Greeting\Greeting::say();"

输出结果为

Hello world!

从一个坑反看defer的使用规则

先看一段代码

package main

import (
"fmt"
)

func c() (i int) {
defer func() { i++ }()
return 1
}

func main() {
fmt.Println(c())
}

这段代码的输出结果不是应该是 1 吗?那么我们运行看看,实际结果输出是 2 !

了解golang的人都知道,在golang当中,defer代码块会在函数调用链表中增加一个函数调用。这个函数调用不是普通的函数调用,而是会在函数正常返回,也就是return之后添加一个函数调用。

因此,defer通常用来释放函数内部变量。

那么上面的例子为什么结果是 2 而不是 1 呢,我们通过defer的规则来分析到底坑在哪。

规则一 当defer被声明时,其参数就会被实时解析
还是通过代码来理解这个规则

package main

import (
"fmt"
)

func a() {
i := 0
defer fmt.Println(i)
i++
return
}

func main() {
a()
}

上面我们说过,defer函数会在return之后被调用。那么这段函数执行完之后,是不用应该输出 1 呢?

No!最后的输出结果是 0 !

这是因为虽然我们在defer后面定义的是一个带变量的函数: fmt.Println(i),但这个变量 i 在defer被声明的时候,就已经确定其确定的值了。 换言之,上面的代码等同于下面的代码:

package main

import (
"fmt"
)

func a() {
i := 0
defer fmt.Println(i) // 因为i=0,所以此时就明确告诉golang在程序退出时,执行输出0的操作
i++
return
}

func main() {
a()
}

为了更为明确的说明这个问题,我们继续定义一个defer:

package main

import (
"fmt"
)

func a() {
i := 0
defer fmt.Println(i) //输出0,因为i此时就是0
i++
defer fmt.Println(i) //输出1,因为i此时就是1
return
}

func main() {
a()
}

通过运行结果,可以看到defer输出的值,就是定义时的值。而不是defer真正执行时的变量值。

但为什么是先输出 1,在输出 0 呢? 看下面的规则二。

规则二 defer执行顺序为先进后出
还是先看一段代码:

package main

import (
"fmt"
)

func b() {
for i := 0; i < 4; i++ {
defer fmt.Print(i)
}
}

func main() {
b()
}

输出的结果并不是大家设想的 0123,而是 4321。

从结果看的出,当同时定义了多个defer代码块时,golang安装先定义后执行的顺序依次调用defer。

规则三 defer可以读取有名返回值
这个规则是前文提到坑之所在了!

回顾一下前面的代码

package main

import (
"fmt"
)

func c() (i int) {
defer func() {
i++
}() // (2) i++ 之后 i = 2
return 1 // (1) i = 1
}

func main() {
fmt.Println(c())
}

在开头的时候,我们说过defer是在return调用之后才执行的。

这里需要明确的是defer代码块的作用域仍然在函数之内,结合上面的函数也就是说,defer的作用域仍然在c函数之内。

因此defer仍然可以读取c函数内的变量。

当执行 return 1 之后,i 的值就是 1。此时此刻,defer代码块开始执行,对i进行自增操作。

因此输出 2。

结合代码,我们理解了defer的三个使用规则,可帮助我们再日后使用更好的避开它的坑。

浏览器user-agent简史

很多人都知道浏览器的user-agent字符串,服务器端通过这个字符串进行客户端的浏览器、操作系统、加密等级、浏览器语言、渲染引擎和版本信息的识别。从1993年NCSA发布首款浏览器Mosaic以来,这个字符串经历了纷繁复杂的变化,以下是user-agent字符串的演变简史。

1993年,NCSA公司发布了首款浏览器Mosaic,使用“NCSA_Mosaic/2.0(Windows 3.1)”作为user-agent字符串。

继续阅读浏览器user-agent简史

Linux查找所有非UTF-8编码的文件或转换成UTF-8

一行Linux命令查找所有非UTF-8编码的文件

find . ! -iregex '.*\.svn.*' -type f -name '*.php' -exec bash -c "enca -L zh_CN {}|grep GB2312 > /dev/null && echo {}" \;

一条命令将他们都转换成UTF8编码

find . ! -iregex '.*\.svn.*' -type f -name '*.php' -exec bash -c "enca -L zh_CN {}  | grep GB2312 >/dev/null && enconv -L zh_CN -x UTF-8 {}" \;

通过javascript判断浏览器是否支持webp

function check_webp_feature(feature, callback) {
    var kTestImages = {
        lossy: "UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA",
        lossless: "UklGRhoAAABXRUJQVlA4TA0AAAAvAAAAEAcQERGIiP4HAA==",
        alpha: "UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAARBxAR/Q9ERP8DAABWUDggGAAAABQBAJ0BKgEAAQAAAP4AAA3AAP7mtQAAAA==",
        animation: "UklGRlIAAABXRUJQVlA4WAoAAAASAAAAAAAAAAAAQU5JTQYAAAD/////AABBTk1GJgAAAAAAAAAAAAAAAAAAAGQAAABWUDhMDQAAAC8AAAAQBxAREYiI/gcA"
    };
    var img = new Image();
    img.onload = function () {
        var result = (img.width > 0) && (img.height > 0);
        callback(feature, result);
    };
    img.onerror = function () {
        callback(feature, false);
    };
    img.src = "data:image/webp;base64," + kTestImages[feature];
}
check_webp_feature('lossy', function(f, r){ console.log(f, r);});

原文发址:https://developers.google.com/speed/webp/faq#how_can_i_detect_browser_support_using_javascript

sublime text ConvertToUTF8乱码

大家都知道,sublime 对不支持,需要依赖插件才能支持,比较推荐的是 ConvertToUTF8,安装后GBK能正常显示。但是有时候会遇到某个文件打开显示乱码之后,怎么刷新、重新打开都还是一样,估计插件有缓存机制。
打开插件配置,果不其然!

"max_cache_size" : 100,

将这个值改为0即可解决问题了。

为 PHP 内置 WebServer 提供目录和文件索引

PHP 5.x 开始提供了一个简单的内置 WebServer,方便大家在没有安装 Apache 的情况下调试 PHP 代码。

要启动这个内置 WebServer 很简单,但它不能显示目录和文件索引,稍微有点不方便。本文提供了一个简单的实现。

使用方法:

php -S localhost:8080 webindex.php

然后打开浏览器访问 localhost:8080 即可以当前目录为根目录,显示目录列表和文件列表,并且可以在子目录中跳转。

实际运行效果:
参考来源:https://github.com/JBlond/php-built-in-webserver-router-script
朋友编写的版本:https://gist.github.com/dualface/70b62fc30b076026d0a8c87385eedebe

<?php
date_default_timezone_set('UTC');
function getfilesize($path) {
    $size = ceil(filesize($path)) . '';
    return str_repeat(' ', 8 - strlen($size)) . $size . ' KB';
}
function printlog($status = 200) {
    $time = date('D M j H:i:s Y');
    $addr = $_SERVER['REMOTE_ADDR'];
    $port = $_SERVER['REMOTE_PORT'];
    $uri = $_SERVER['REQUEST_URI'];
    $log = sprintf("[%s] %s:%s [%s]: %s\n", $time, $addr, $port, $status, $uri);
    file_put_contents('php://stdout', $log);
}
function echo_h($t) {
    echo htmlspecialchars($t);
}
function dumpfile($path) {
    $name = htmlspecialchars(pathinfo($path, PATHINFO_BASENAME));
    echo <<<EOT
<!DOCTYPE html>
<html>
<head>
<title>{$name}</title>
<meta charset="utf-8" />
<style>
pre {
    font-size: 14px;
    width: 80em;
    white-space: pre-wrap;
    white-space: -moz-pre-wrap;
    white-space: -pre-wrap;
    white-space: -o-pre-wrap;
    word-wrap: break-word;
}
</style>
</head>
<body>
<pre>
EOT;
echo_h(file_get_contents($path));
echo <<<EOT
</pre>
</body>
</html>
EOT;
}
// start
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$path = $_SERVER['DOCUMENT_ROOT'] . $uri;
if (!file_exists($path)) {
    printlog(404);
    http_response_code(404);
    return false;
}
$dump_exts = array(
    'txt', 'text', 'md', 'mdown', 'markdown', 'json',
    'c', 'cpp', 'h', 'hpp', 'm', 'mm', 'lua', 'py'
);
if (!is_dir($path)) {
    $ext = strtolower(pathinfo($path, PATHINFO_EXTENSION));
    if ($ext == '' || in_array($ext, $dump_exts)) {
        printlog();
        dumpfile($path);
        return true;
    } else {
        // let server handle file
        return false;
    }
}
$this_dir = substr($_SERVER['PHP_SELF'], 0, strrpos($_SERVER['PHP_SELF'], '/') + 1);
$dir = $_SERVER['DOCUMENT_ROOT'] . $this_dir;
if (!is_dir($dir)) {
    printlog(404);
    http_response_code(404);
    return false;
}
$folder = opendir($dir);
if (!readdir($folder)) {
    printlog(404);
    http_response_code(404);
    return false;
}
$files = array();
while ($file = readdir($folder)) {
    $base = $this_dir == '/' ? '' : $this_dir;
    $ext = pathinfo($file, PATHINFO_EXTENSION);
    $path = $dir . DIRECTORY_SEPARATOR . $file;
    $filesize = getfilesize($path);
    $time = date('d-M-Y H:i:s', filemtime($path));
    $is_dir = is_dir($path);
    if (substr($file, 0, 1) == '.') continue;
    $files[] = array(
        'name' => $is_dir ? $file . '/' : $file,
        'ext' => $ext,
        'size' => $filesize,
        'time' => $time,
        'is_dir' => $is_dir
    );
}
usort($files, function($a, $b) {
    if ($a['is_dir']) {
        if ($b['is_dir']) {
            return $a['name'] < $b['name'] ? -1 : 1;
        } else {
            return -1;
        }
    }
    if ($b['is_dir']) {
        return 1;
    }
    return $a['name'] < $b['name'] ? -1 : 1;
});
$name_len = 50;
?>
<!DOCTYPE html>
<html>
<head>
<title><?php echo_h($this_dir); ?></title>
<meta charset="utf-8" />
</head>
<body>
<h1>Index of <?php echo_h($this_dir); ?></h1>
<pre>
    Last modified               Size  Name
<hr /><?php if ($this_dir != '/'): ?>
[D] <a href="..">Parent Directory</a>
<?php
endif;
foreach ($files as $file):
    $dirflag = $file['is_dir'] ? '[D]' : '   ';
    $prefix = sprintf('%s %s %s', $dirflag, $file['time'], $file['size']);
    $name = $file['name'];
?>
<?php echo $prefix; ?>  <a href="<?php echo_h($name); ?>"><?php echo_h($name); ?></a>
<?php endforeach; ?>
</pre>
</body>
</html>

简化PHP命令行下接收参数

命令行下执行某个PHP脚本已是家常菜,大家也知道使用getopt接收参数(例子参考手册),但是它有一个缺点,必须先定义好需要接收哪些参数,并不能像$_GET/$_POST这么简单。
花了一些时间,写了一个类,让命令行参数像$_GET一样简单,随传随用。

class Options
{
    public static function get(string $name = '', $default = null)
    {
        static $argv = null;
        if (is_null($argv)) {
            [$shortopts, $longopts] = self::parse();
            $argv                   = getopt($shortopts, $longopts);
        }
        if ('' == $name) {
            return $argv;
        }
        return $argv[$name] ?? $default;
    }

    private static function parse(): array
    {
        if (empty($_SERVER['argv'])) {
            return [];
        }
        $opts = ['', []];
        foreach ($_SERVER['argv'] as $argv) {
            if (preg_match('/^\-\-([\w\-]+)/', $argv, $matches)) {
                $opts[1][] = $matches[1] . '::';
            } elseif (preg_match('/^\-([a-z])/', $argv, $matches)) {
                $opts[0] .= $matches[1] . '::';
            }
        }
        return $opts;
    }
}
php test.php --a=1 --b=2 -c3 -d4 -d5

全部接收

$options = Options::get();
var_dump($options);
/**
array(4) {
  ["a"]=>
  string(1) "1"
  ["b"]=>
  string(1) "2"
  ["c"]=>
  string(1) "3"
  ["d"]=>
  array(2) {
    [0]=>
    string(1) "4"
    [1]=>
    string(1) "5"
  }
}
*/

单个接收

echo Options::get('a');
// 1

使用默认值

echo Options::get('e', 1);
// 1

———————– 割 ———————–
在群里抛砖引玉,果然大牛们放出其他不错的方案。

【老王】环境变量的思路

AAAA=1111 php test.php
// 可得到 $_SERVER['AAAA'] = 1111

【supermoon】

php test.php "a=1&b=2"
// 结合parse_str()

nginx中配置跨域支持功能

在nginx.conf中配置

http {
  ......
  add_header Access-Control-Allow-Origin *;
  add_header Access-Control-Allow-Headers X-Requested-With;
  add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
  ......
}

这样就可以实现GET,POST,OPTIONS的跨域请求的支持
也可以

add_header Access-Control-Allow-Origin http://test.51testing.com;

指定允许的url。
或者动态指定

if ($http_origin ~* ".*\.example\.com") {
    add_header Access-Control-Allow-Origin $http_origin;
    add_header Access-Control-Allow-Credentials true;
}

配置项详细解释:w3c-Cross-Origin Resource Sharing