Tag: php

  • Slim 4初步研究

    Slim是一个我很喜欢用的轻框架,我用它为我的任氏有无轩站点提供API服务。这几天趁着放假,想重新“折腾”一下我的站点,于是就开了一个虚拟机,装好了必要的软件,准备开发。

    然后我发现,Slim这个框架已经升级到了4,有了重大的变化。

    参照Slim官方说明创建项目后,目录结构如下(请忽略其中的nbproject目录):

    • app目录:它包含了对于整个应用来说最基本的一些文件。具体说明如下。

    • dependencies.php:它主要是创建应用全局的容器。应用安装时,会生成logger这个实例。数据库链接的实例也是在这里生成的:

    return function (ContainerBuilder $containerBuilder) {
        $containerBuilder->addDefinitions([
            ...
            PDO::class => function (ContainerInterface $c) {
                $settings=$c->get('settings')['db'];
                $host=$settings['host'];
                $user=$settings['user'];
                $pass=$settings['pass'];
                $db=$settings['db'];
    
                $dsn="mysql:host=$host;dbname=$db";
                $conn=new PDO($dsn, $user, $pass);
                $conn->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
                return $conn;
            },
        ]);
    };
    

    注意,我在这里设置了数据库PDO链接的一些属性。以后在应用的任何地方,只要用到PDO这个类的声明,Slim都会在容器中去寻找PDO的实例,从而保证所有的数据库链接都是统一的。

    • middleware.php:中间件我目前在API中还不会用到。按照Slim的说明,中间件可以在应用运行,修改相应的RequestResponse对象。
    • repositories.php:这里注册所有可以成为“仓库”的类。这是Slim 4新增的文件,用来统一管理MVC中的M;但Slim 4抛弃了M,而改用更轻量的仓库。这也是一个容器。如:
    declare(strict_types=1);
    
    use App\Domain\Book\BookRepository;
    use App\Infrastructure\Persistence\Book\DBBookRepository;
    
    use DI\ContainerBuilder;
    
    return function (ContainerBuilder $containerBuilder) {
        $containerBuilder->addDefinitions([
            BookRepository::class => \DI\autowire(DBBookRepository::class),
        ]);
    };

    注:这里用到的一些类会在后续得到说明。不过可以提醒一下,Slim 4用一个接口(BookRepository)定义所有该仓库支持的操作,而用一个实例类(DBBookRepository)对这个接口进行实现。你可以把它们的这种关系类比成C++中的“hpp/cpp”的关系。

    • routes.php:这是常规的路由配置。请注意,Slim4放弃了一个controller中多个action的做法,而是一个action对应一个controller,如下例所示:
    declare(strict_types=1);
    
    use Psr\Http\Message\ResponseInterface as Response;
    use Psr\Http\Message\ServerRequestInterface as Request;
    use Slim\App;
    use Slim\Interfaces\RouteCollectorProxyInterface as Group;
    
    use App\Application\Actions\Index\IndexAction;
    use App\Application\Actions\Book;
    
    return function (App $app) {
        $app->get('/', IndexAction::class);
        $app->group('/summary', function(Group $group) {
            $group->get('', Book\SummaryAction::class);
        });
    };

    于是,/summary这个路由就会调用Book\SummaryAction::class中制定的action函数。

    • settings.php:全局配置文件。数据库的配置也在此处出现:
    return function (ContainerBuilder $containerBuilder) {
        // Global Settings Object
        $containerBuilder->addDefinitions([
            'settings' => [
                'displayErrorDetails' => true, // Should be set to false in production
                'logger' => [
                    'name' => 'slim-app',
                    'path' => isset($_ENV['docker']) ? 'php://stdout' : __DIR__ . '/../logs/app.log',
                    'level' => Logger::DEBUG,
                ],
                //Database connection
                'db' => [
                    'host'  => 'localhost',
                    'user'  => '****',
                    'pass'  => '****',
                    'db'    => '****',
                ],
            ],
        ]);
    };

    总结:app目录中的,都是对应用全局产生影响的文件。

    • public目录:这里是应用的入口文件。一般情况下,对于一个API应用来说,这里没有什么需要修改的。
    • src/Application目录:这里有四个目录。我们重点要关注的是src/Application/Actions目录。

    根据我的理解,针对API要成立的各类实体,开发者可以进行逻辑抽象分类,形成各个action。在具体实现的时候,一般是针对每个实体实现一个抽象的类,然后对每个针对这个实体的操作定义一个action

    比如我的数据库中有“书籍(book)”这个实体,于是我就暂时定义了两个文件:BookAction.phpSummaryAction.php

    //BookAction.php
    
    declare(strict_types=1);
    
    namespace App\Application\Actions\Book;
    
    use App\Application\Actions\Action;
    use App\Domain\Book\BookRepository;
    
    use Psr\Log\LoggerInterface;
    use Psr\Http\Message\ResponseInterface as Response;
    
    abstract class BookAction extends Action
    {
        /**
         * {@inheritdoc}
         */
        protected $repo;
    
        public function __construct(BookRepository $repo, LoggerInterface $logger) {
            parent::__construct($logger);
            $this->repo=$repo;
        }
    }

    注意到,这是一个抽象类,主要目的是为本类(以及后续子类)引入各种dependency。这里我引入了BookRepositoryLoggerInterface

    //SummaryAction.php
    declare(strict_types=1);
    
    namespace App\Application\Actions\Book;
    
    use Psr\Http\Message\ResponseInterface as Response;
    
    class SummaryAction extends BookAction
    {
        /**
         * {@inheritdoc}
         */
        protected function action(): Response
        {
            $res=$this->repo->summary();
            return $this->respondWithData($res);
        }
    }

    SummaryAction继承了BookAction,完成了action方法。而在action方法中,通过调用repo中对应的方法而获取了数据并返回。Slim4很贴心地可以直接返回json数据,不用开发者再转换。调用的例子如下:

    我们再来看src/Domain目录。

    这个目录中包括用来定义各类仓库的文件。比如我们之前看到的BookRepository

    // /src/Domain/Book/BookRepository.php
    declare(strict_types=1);
    
    namespace App\Domain\Book;
    
    interface BookRepository
    {
        public function summary(): array;
        public function detail($id):array;
    }

    如前所述,这是一个接口,只是定义了各类接口,也就是这个仓库能做什么的定义。

    最后来看看src/Infrastructure/Persistence目录。这个目录对上面提到的接口进行实现。

    // /src/Infrastructure/Persistence/Book/DBBookRepository.php
    declare(strict_types=1);
    
    namespace App\Infrastructure\Persistence\Book;
    
    use App\Domain\Book\BookRepository;
    
    class DBBookRepository implements BookRepository
    {
        private $conn;
    
        public function __construct(\PDO $conn) {
            $this->conn=$conn;
        }
    
        public function summary(): array {
            $sql='select count(*) bc, sum(kword) wc, sum(page) pc from book_book';
            $res=$this->conn->query($sql)->fetch();
    
            return $res;
        }
    
        public function detail($id): array {
    
        }
    
    }

    请注意,这个类的构造函数中引入了\PDO,因此上文提到的dependency就起作用了。

    总体感觉,Slim 4的结构还是很清晰的,耦合度也合适,确实是可以一用的轻量级框架。

    本文推送到[go4pro.org]

  • PHP 7已经来临(一)

    PHP界最近最热门的消息就是:PHP 7已经来临!Sitepoint的Bruno适时地写了一篇文章,列出了一些资源和回顾。 我用百度搜索了一下国内的相关内容,发现相关的讨论还是非常非常少。所以觉得有必要结合我的实践,写点东西,以求推动国内的PHP7的落地。

    安装

    PHP创始人Rasmus Lerdorf弄了一个PHP 7的Vagrant盒子,供大家下载尝鲜使用PHP 7。其Github的地址:https://github.com/rlerdorf/php7dev。安装过程应该说很简单的,大家可以参照仓库里的指引。

    有一个需要提示的地方。我相信很多人和我一样,是用Vagrant+VirtualBox的方式来安装虚拟机的。如果vagrant up这个PHP 7的虚拟机时出现问题,可以通过在VirtualBox中直接启动这个盒子的手段帮助解决。VB中的启动会给出更多、更详细的出错信息。我也是通过这个方法解决了这个盒子无法启动的问题:需要禁用这个虚拟机的USB 2.0功能。

    安装完成并启动后,可以通过Putty登录到虚拟机中,进行一次apt-get的更新,然后用php -v检验:

    6-1

    或者用浏览器访问这个虚拟机,会得到一个标准的PHP信息页面:

    6-2

    在这篇名为What to Expect的文章中,作者Davey Shafik列出了一些PHP7的重要特点。

    首先提到的是抽象语法树(Abstract Syntax Tree,简称AST),它将取代目前直接从解析器里产生操作代码的做法。

    将解析器和编译器分离将允许我们取消众多黑客手段,而且允许实现仅靠一次过编译(single-pass compilation)无法实现的语法特性。

    第一,解析器和编译器将更易于维护。

    第二,将语法确定与技术问题分开。在目前的处理中,有些语法元素非常难以实现——如果不是不可能的话。比如对yield表达式的限制: $result = yield fn(); //不合法$result = (yield fn()); //合法

    另外,当前的编译器结构不允许我们实现某些类型的语法。比如:

    • 数组解构:[$a, $b, $c]=$array,而不需要使用一个特定的list()语法。
    • 结果表达式在前的列表推导和生成子表达式,例如Python中的[x * x for x in list]。目前的PHP中,只支持反过来的语法:foreach ($list as $x) yield $x*$x
    • C#类型的表达式树和LINQ。

    AST对运行时的性能或者内存占用只有很少的影响。但是确实影响了编译过程中的性能和内存使用。但需要强调的是,这样的差别只在不使用opcode cache的时候才存在。

    PHP的官方Wiki中给出了一个对照表,针对不同大小的PHP文件各编译1000次的时间差异:

    文件 7之前 7 提升
    小(100行) 0.180s 0.160s -12.5%
    中(700行) 1.492s 1.268s -17.7%
    大(2800行) 6.703s 5.736s -16.9%

    和峰值内存使用:

    文件 7之前 7 增加
    小(100行) 378K 414K +9.5%
    中(700行) 507K 643K +26.8%
    大(2800行) 1084K 1857K +71.3%

    还有一个编译PhpParser项目的实例。总体而言,AST要快10%-15%,但需要更多的内存。多用多少内存取决于文件大小。小文件只要多用10%,而大文件需要多用70%以上。在比较实际的情形中,内存差异在5%左右。

    最后谈到了语法和行为的改变。

    yield不用括号了。以下语法都是对的:

    $result = yield;
    $result = yield $v;
    $result = yield $k => $v;
    

    括号不影响行为:一个问题是($foo)['bar']='baz'$foo['bar']='baz'表现出的行为不同。与之类似的还有byRef(func())byRef((func()))现在都会抛出一个严格标准下的需注意情况(如果byRef引用其参数,而func并不返回引用变量)。对list()的改变目前list()赋值是从右到左。在AST下,将是从左到右。

    list($array[], $array[], $array[]) = [1, 2, 3];
    var_dump($array);
    // OLD: $array = [3, 2, 1]
    // NEW: $array = [1, 2, 3]
    

    另一个赋值顺序有关系的例子是,列表赋值的左边和右边是不是用一个变量:

    $a = [1, 2];
    list($a, $b) = $a;
    // OLD: $a = 1, $b = 2
    // NEW: $a = 1, $b = null + Undefined index 1
    $b = [1, 2];
    list($a, $b) = $b;
    // OLD: $a = null + Undefined index 0, $b = 2
    // NEW: $a = 1, $b = 2
    

    现在的list()将只存取一个偏移量一次:

    list(list($a, $b)) = $array;
    // OLD:
    $b = $array[0][1];
    $a = $array[0][0];
    // NEW:
    $_tmp = $array[0];
    $a = $_tmp[0];
    $b = $_tmp[1];
    

    空的list()现在不被允许了。

    按引用赋值的自动赋值顺序AST中将调整为从左到右

    $obj = new stdClass;
    $obj->a = &$obj->b;
    $obj->b = 1;
    var_dump($obj);
    // OLD:
    object(stdClass)#1 (2) {
      [b]=>
      &int(1)
      [a]=>
      &int(1)
    }
    // NEW:
    object(stdClass)#1 (2) {
      [a]=>
      &int(1)
      [b]=>
      &int(1)
    }
    

    允许直接调用__clone现在可以进行类似$obj->__clone()这样的调用。(未完待续)

    本文收录于[go4pro.org]

  • PHP开发者经常会犯的另外7个错误

    本文原始链接:7 More Mistakes Commonly Made by PHP Developers。作者:Bruno Skvorc

    Thanks for Sitepoint.com authorization for translation and publication in my blog.

    本文翻译力求忠实原始文档。

    ==========译文开始分割线==============

    在6月的时候,TopTal(自由编程人员市场)发表了一篇文章,题为《PHP程序员最常犯的10个错误》。那个列表当然不是穷尽的,但确实写得不错,并指出了一些非常有趣的、值得每个人关注的陷阱——即便如我个人都不会将这些错误认为是很常见的。

    我希望你对这篇文章加以通读——其中有一些确实很有价值的、你需要知道的信息,特别是前八个要点。几天前,Anna Filina扩展了这个列表,并加入了七个新的项目。尽管这些地方不那么特定也不那么常见,她的观点还是有分量的,需要在开发时加以考虑。

    PHP开发者经常会犯的另外7个错误

    TopPal中某位仁兄请我看看他们的列表,还有没有补充,社交网络上的一些粉丝也表示有兴趣看到这个列表继续。所以,我愿意借此机会在这个列表中加入我自己的一些事项。这些都是我不断需要去警示我的团队或者粉丝们的。

    1. 使用mysql扩展

    这个消息其实很旧了,但是尚未注意这个事实的开发者数量还是大到令人担忧。如果用到SQL数据库,特别是MySQL,有太多的开发者还是倾向于使用mysql扩展。这个扩展已经官方认定过时。它不安全,不可靠,不支持SSL,不支持MySQL中的一些现代特性。它也会产生过时提醒,出现在你的应用的最顶部——但不会中断应用。滑稽的是,这意味着我们可以简单地Google这个关键词,就能找到大量这样还是用着这一不安全设置的站点。因为这个,这些应用所面对的伤害是令人震惊的。

    避免使用mysql,我们可以选择:MySQLi或者PDO。例如,使用MySQLi简单到几乎只要在API调用后加一个“i”即可:

    $c = mysql_connect(host, user, pass);
    mysql_select_db(database);
    $result = mysql_query(SELECT * FROM posts LIMIT 1);
    $row = mysql_fetch_assoc($result);

    对比:

    $mysqli = new mysqli(host, user, pass, database);
    $result = $mysqli->query(SELECT * FROM posts LIMIT 1);
    $row = $result->fetch_assoc();

    使得我们的设置变得更安全,所要做的就是这些。 不过你应该选择PDO。详见第2点。

    1. 不使用PDO

    不要误会,mysqli确实(确实确实)比古老的mysql扩展超出了好几代。它更新及时,安全,可靠,快速。但是,它只适用于MySQL。使用PDO可以让你使用一些美妙而实用的面向对象语法,也能让你做好准备使用另外的SQL数据库(如PostgreSQL,MS SQL以及其它)。另外,PDO可以让你使用命名参数。这一特性非常有用,只要充分利用它带来的好处,就很少有人能想象不这么做。最后,还有这点:你可以直接将获取的数据注入到一个新对象中,对于大项目而言,这能节省时间,而且是令人愉悦的。

    1. 不重写URL

    这是一个常常被忽略却也容易修正的问题。诸如myapp.com/index.php?p=34&g=24这样的URL如今简直就是不能被接受的。由于几乎不可能写出一个好的URL重写规则来涵盖每个服务器和框架,几乎每个框架都有一个指南,告诉我们如何设置干净的URL(Laravel, Phalcon, Symfony, Zend)。如果哪个框架不这么做,那么它就不值得我们使用——这些框架显然不关注现代化的实践。

    1. 抑制错误提示

    我在以前的一篇文章中写过这个问题,但还是有必要再说一说。任何时候你发现自己使用@运算符,请再考虑考虑,尝试从另一个角度更认真地处理这个问题。用我的话来说,在一个应用的功能中包含20行引用的cURL代码也比单单一行前放一个@来的好。

    我个人的经验告诉我,我在原来的帖子中建议的方法是一个好方法:打开所有的提示并转化其为致命错误。要确保在错误日志中没有任何记录,因为确实没有要记录的东西要比用@遮在眼前,装作看不见有错误发生要好。

    最近我们发现了一些Heroku add-ons可以用来开发生产环境下的PHP应用,其中一个Papertrail很棒。这个add-on让你将应用所有的错误推送到后台,从而可以轻松的加以搜索、分组并在日后加以清除。所以,即使真的有错误发生,让它们被记录下来,然后通过修订代码来消除错误。这比抑制它们,愚弄你的用户来的好。

    1. 条件判断中的赋值

    即使再有经验的程序员有时也会手指一滑,写下if ($condition = 'value') {而不是if ($condition == 'value') {。我们的手会打滑,键盘可能没有记录按键,我们从代码的另一部分——那一部分确实进行的是赋值操作——拷贝了代码。这些都有可能,而我们只有在运行应用时才发现问题。

    要完全避免这个问题,有几个方法:

    • 使用一个好的IDE。任何好IDE(比如PhpStorm)会在检测到这一问题时警告说”条件判断中进行了赋值“。
    • 使用“Yoda Conditions”。在众多流行项目,甚至大型框架中,都会看到这个应用。通过交换比较双方(if ('value' = $condition) {),即使比较弱的IDE也能注意到这个问题。有人认为Yoda语法很讨厌而且毫无意义,其信条是绝不使用(“更小心地编码,蠢货!”)。单就个人而言,如果能帮到你的话,我是建议使用的。如果我们都是优秀人物,那么WordPress和Zend Framework就不会存在了。
    • 牢牢记住,每次写的时候都要检查一次。所要的知识练习,但即使对于最好的开发人员而言,这也可能发生。所以前两点会有用。
    1. 太透明了

    我这么说可能会有反对意见,不过我还是要说。除非你对框架的开发者有100%的信心,或者也不运行高利润、高流量的商务关键应用,你应该总是努力隐藏你的后台——不要告诉大家你的应用基于什么框架。这实际上能对防止攻击起到作用——如果发现了该框架的一个安全漏洞的话。例如:

    If you use Symfony2 translator and have a route with a {_locale} parameter upgrade NOW http://t.co/jihXHB8MzT— Jérémy DERUSSÉ (@jderusse) July 15, 2014

    在该Tweet中,有关代码注入的一个严重问题已经变成了广为人知的知识。如果你正在上班,可以立即升级而不用担心开发问题或者让团队停止,那很好。但是对大部分使用Symfony的人和公司而言,情况不是如此。即便Symfony可以通过Composer升级(如Ryan在评论中提到的那样),在大的多层环境的团队中,通常这要花一些时间来获得批准。所有使用这一翻译机制的站点,并且也声明了是Symfony的用户的站点,就会面临该缺陷,直到被修正。

    上面提到的使用Symfony,只是一个例子。多年来,类似的情形发生在无数其它的软件中。我还在使用ZF框架开发商业应用那会,也发生过这样的情况,并因此受到了攻击。WordPress也有其安全性漏洞,而我们也知道世上有多少站点是由它来运行的。会发生这些事情。有时,开源和透明不是最好的方法来处理那些承担公司主要收入来源的应用。

    1. 没有移除开发配置

    最后,我要提一提移除开发配置。最近(声明,我再次提到Symfony纯粹是巧合),Cnet遭受了一次攻击,原因就是没有移除它们的开发配置。

    Uhmmm no: http://t.co/rAQis1ycWq #security #symfony— Marco Pivetta (@Ocramius) July 15, 2014

    Cnet是全球最大的技术新闻站点之一,它基于Symfony。你可能知道,Symfony有两个应用入口:app.php和app_dev.php。你的浏览器访问其中之一,进入的是生产环境,如果访问带有_dev后缀的那个,显然你会进入开发环境,从而有除错器、敏感数据以及诸如此类。这样是好是坏可是很多讨论的话题(再次感谢Ryan指出了这点)。但无可否认的是,它让一些比较笨拙的开发者面临着Cnet面临的同样的错误。另外,通过app_dev访问的任何其它URL也会导向其它的app_dev相关的URL。换句话说,不仅是首页在开发环境下运行,而是整个站点——放在Cnet的案例中,这可是大量的访问。

    如果你跟踪了Twitter上的讨论,情形很快就糟糕到令人尴尬——而更可悲的是,只要几秒钟的工作就可以避免:

    • 开发者应该在生产用服务器中删除app_dev.php
    • 开发者应该将可以访问app_dev.php的IP放入白名单,这也是缺省配置——除非你故意放松了这些限制

    任何一个方法都可以完全避免所有的问题。记住,在发布到生产环境时,要确保你的开发配置要么完全不可访问,要么只有在白名单中的IP可以访问。

    结论

    (本文收录于[go4pro.org])

  • PHP-CPP编写PHP扩展

    (原文发布于2014年3月26日和4月5日,原文链接Getting Started with PHP Extension Development via PHP-CPP,以及PHP Extension Development with PHP-CPP: Object Oriented Code

    本文收录于[go4pro.org]

    这两篇文章利用http://php-cpp.com/提供的PHP-CPP作为基础,讨论了利用C++(严格说是PHP-CPP)编写PHP扩展的方法,讨论了最基础的东西以及一个类(复数类)的基本实现。

  • Symfony 2中的功能测试

    (原文发布于2014年3月4日,原文链接Functional Testing in Symfony 2

    本文收录于[go4pro.org]

    =====

    基于《Symfony 2中的批量数据生成》,我们可以对页面进行有控制的功能测试。所谓“有控制”,意为在测试数据是受控产生的前提下,假定程序逻辑没有问题,那么结果应该是如我们所预期的那样受控。否则程序一定有问题。

  • MySQL存储过程中Cursor的使用

    (原文发布于2014年2月5日,原文链接Cursors in MySQL Stored Procedures

    本文收录于[go4pro.org]

    ====

    这篇文章继续了我之前《存储过程》的讨论,专注在讨论Cursor(光标)。这篇的讨论也很热烈。

  • 大整数和任意精度实数运算

    (原文发布于2014年1月29日,原文链接Arbitrary Precision and Big Numbers in PHP

    本文收录于[go4pro.org]

    ====

    严格的说,这是一篇纯算法的文章,文中讨论了三个PHP库:BC,GMP,php-bignumber,并分别用计算圆周率和进行RSA加密/解密进行了演示。

  • Symfony和Dart的整合——第二部分

    (原文发布于2014年1月20日,原文链接Integrating Polymer/Dart and Symfony – Part 2

    本文收录于[go4pro.org]

    ====

    在第二部分,我继续讨论Symfony和Dart的整合,还是异步数据的获得——但注重在远程数据,并讨论了一种避免JSONP的方法。同时,对编译为JavaScript后的Dart的局限性进行了讨论。

  • Symfony和Dart的整合——第一部分

    (原文发布于2014年1月13日,原文链接Integrating Polymer/Dart and Symfony – Part 1

    本文收录于[go4pro.org]

    ====

    我继续进行着Dart的研究,并在该两部分的文章中讲述Dart和Symfony的结合。

    这一部分,着重在异步数据获得,前台模板(Twig)于Dart/Polymer的整合。

  • 存储过程

    (原文发布于2014年1月3日,原文链接Stored Procedures in MySQL and PHP

    本文收录于[go4pro.org]

    ====

    我继续着MySQL本身的研究,并讲述了存储过程的概念、优劣和应用。反响非常强烈。