zoukankan      html  css  js  c++  java
  • 现代PHP-组件

    什么是组件

    组件是一组打包的代码,是一系列相关的类、接口和Trait,用于帮助我们解决PHP应用中某个具体问题。例如,你的PHP应用需要收发HTTP请求,可以使用现成的组件如guzzle/guzzle实现。我们使用组件不是为了重新实现已经实现的功能,而是把更多时间花在实现项目的长远目标上。

    优秀的PHP组件具备以下特性:

    • 作用单一:专注于解决一个问题,而且使用简单的接口封装功能
    • 小型:小巧玲珑,只包含解决某个问题所需的最少代码
    • 合作:PHP组件之间可以良好合作,组合在一起实现大型项目
    • 测试良好:本身提供测试,而且有充足的测试覆盖度
    • 文档完善:应该提供完善的文档,能让开发者轻易安装、理解和使用

    组件 vs 框架

    我们选择框架时,要为这个框架的工具投入很多,框架通常会提供大量工具,但却没有提供我们所需的某个工具时,痛苦就转嫁到我们头上,我们要寻找并集成自定义的PHP库。把第三方代码集成到框架中是件难事,因为第三方代码和框架可能没有使用相同的接口。

    选择框架时,我们看中的是框架的未来,但是谁又能保证某个框架始终是完成某项工作最好的工具呢?存在多年的大型项目必须有好的表现,而且要时刻做好调整,如果选错了PHP框架,可能无法做到这一点。较旧的PHP框架可能由于缺乏社区支持而变慢或过时,这些旧框架通常使用过程式代码编写,而没有使用新式的面向对象代码以及PHP的一些新特性,总之,决定是否使用PHP框架时,要考虑的事情很多。

    庆幸的是,Laravel在这些担忧方面表现良好,因此才能在众多PHP框架中脱颖而出,从某种意义上来说,Laravel也是个基于组件开发的框架(核心组件是自身的Illuminate库,功能实现上则大量依赖第三方组件),相比Symfony而言,上手又比较简单,所以兼具了扩展性和易用性。但是,Laravel也存在一些不足,比如Laravel自身的组件不能轻易解耦,用于Laravel框架之外(但是相信这种状况会有好转,比如其数据库和队列组件就可以解耦出去)。综合来看,Laravel仍是一个出色的框架,能帮组我们快速创建强大的应用。

    那我们应该使用组件还是框架呢?答案是,使用正确的工具做正确的事,如果能通过一些PHP组件快速实现小型项目,那就使用组件,如果有多个团队成员开发大型项目,而且能从框架提供的约定准则和结构中受益,那就使用框架(如果是在纠结使用什么框架,那么选择Laravel吧,它不会让你失望),使用框架能够引导并加速项目的开发。

    PS:这句话也适用于语言之争,用正确的语言做正确的事,没事少BB,这是学院君对语言之争的态度和立场。

    使用组件

    Packagist

    我们在Packagist(https://packagist.org)中查找PHP组件,这个网站用于收集PHP组件,最好的PHP组件在Packagist中都能找到。

    比如我们想使用一个http组件用于收发HTTP消息,在搜索框中搜索http,得到的第一个结果就是Guzzle,就用它吧。

    Composer

    Packagist是查找PHP组件的社区,Composer则是安装PHP组件的工具。Composer是PHP的依赖管理器,运行在命令行中,你告诉Composer需要哪些组件,Composer会下载并把这些组件自动加载到你的项目中,就这么简单。

    Composer和Packagist紧密合作,如果你告诉Composer想要使用guzzlehttp/guzzle组件,Composer会从Packagist中获取guzzlehttp/guzzle组件,找到这个组件的仓库地址,确定要使用哪个版本,还能找出这个组件的依赖,然后把guzzlehttp/guzzle组件及其依赖下载到你的项目中。

    此外,Composer会为项目中的所有PHP组件自动生成符合PSR标准的自动加载器,有效地抽象了依赖管理和自动加载,所以,对PHP社区来说,Composer是最重要的附加工具,没有之一,想想之前我们要使用诸如include、require、spl_autoload_register来手动实现自动加载的痛苦日子,这一点也不为过。

    关于Composer的安装和使用,这里不赘述,请参考Composer中文网(http://www.phpcomposer.com)。

    示例项目

    下面我们通过一个示例项目来演示如何使用Composer和组件来开发一个PHP应用,这个应用的作用是扫描一个CSV文件中的URL,找出死链,该应用会向每个URL发HTTP请求,如果返回的HTTP状态码大于等于400,就把这个死链发给标准输出。这是一个命令行应用,开发好之后,我们会执行这个脚本,传入csv文件的路径,在标准输出中显示死链列表。

    安装组件

    开始之前,先看看哪些任务可以使用现有的PHP组件解决:我们需要一个可以迭代处理csv文件数据的组件,此外还要向csv文件中的每个URL发送HTTP请求,因此还需要一个可以发送HTTP请求并检查HTTP响应的组件。

    浏览Packagist后,我们找到guzzlehttp/guzzleleague/csv两个组件,前者用于处理HTTP消息,后者用于处理CSV数据。下面我们在项目最顶层运行如下命令:

    composer require guzzlehttp/guzzle
    composer require league/csv
    

    Composer会将依赖安装到根目录的vendor目录下,安装完成后,会在根目录下生成composer.jsoncomposer.lock文件:

    composer.lock文件中会列出项目使用的所有PHP组件,以及组件的具体版本号,这其实是锁定了项目,让项目只能使用具体版本的PHP组件。这样的好处是,composer会下载这个文件中列出的具体版本,而不管Packagist中可用的最新版本是多少,你应该把composer.lock文件纳入版本控制,这样让团队成员使用的PHP版本和你一样,如果本地开发和服务器使用的PHP组件版本相同,可以尽量降低由组件版本不同导致的bug。

    如果确实要下载最新版本的组件并更新composer.lock,可以使用composer update命令。

    自动加载

    接下来我们来编写应用代码,在根目录下创建一个scan.php文件,然后在该文件顶部使用require导入Composer创建的自动加载器:

    <?php
    require 'vendor/autoload.php';
    

    Composer创建的自动加载器其实就是个名为autoload.php的文件,保存在vendor目录中,Composer下载各个PHP组件时,会检查每个组件的composer.json文件,确定如何加载该组件,得到这个信息后,Composer会在本地为该组件创建一个符合PSR标准的自动加载器。这样我们就可以实例化项目中的任何PHP组件,这些组件按需自动加载。

    编写代码

    下面我们正式使用Guzzle和CSV组件编写scan.php代码:

    <?php
    //使用composer自动加载器
    require 'vendor/autoload.php';
    
    //实例Guzzle Http客户端
    $client = new GuzzleHttpClient();
    
    //打开并迭代处理CSV
    $csv = LeagueCsvReader::createFromPath($argv[1]);
    foreach ($csv as $csvRow) {
        try {
            //发送HTTP GET请求
            $httpResponse = $client->get($csvRow[0]);
    
            //检查HTTP响应的状态码
            if($httpResponse->getStatusCode() >= 400) {
                throw new Exception();
            }
        } catch (Exception $e) {
                //把死链发给标准输出
                echo $csvRow[0] . PHP_EOL;
        }
    }
    

    下面我们在urls.csv中添加一些URL,一行一个,而且至少有一个是死链:

    然后打开终端,执行scan.php脚本:

    php scan.php urls.csv
    

    我们传入了两个参数,第一个是脚本文件scan.php的路径,另一个是CSV文件的路径。输出如下:

    私有组件

    大多数时候我们使用的都是公开可用的开源组件,但有时候如果公司使用内部开发的PHP组件,而基于许可证和安全方面的问题不能将其开源,就需要使用私有组件。对Composer而言,这是小菜一碟。

    Composer可用管理放在需要认证的仓库中的私有PHP组件,执行composer installcomposer update命令时,如果组件的仓库需要认证凭据,Composer会提醒你需要输入认证信息,此外,Composer还会询问是否把仓库的认证凭据保存在本地的auth.json文件(和composer.json放在同一级)。下面是auth.json的内容示例:

    {
       "http-basic": {
            "laravelacademy.org": {
                "username": "your-username",
                "password": "your-password"
            }
        }
    }
    

    auth.json最好不要放到版本控制中,我们要让项目的开发者自己创建auth.json文件,保存他们自己的认证凭据。

    如果不想傻傻的等待Composer向你询问认证凭据,可以使用下述命令手动告诉Composer远程设备的认证凭据:

    composer config http-basic.laravelacademy.org your-username your-password
    

    在这个示例中,http-basic告诉Composer我们要为指定的域名添加认证信息。laravelacademy.org是主机名,用于识别存储私有组件仓库的设备,最后两个参数是用户名和密码。默认情况下,这个命令会在当前项目中的auth.json文件里保存凭据。

    你还可以使用--global标记在系统全局中保存认证凭据。使用这个标记设定认证凭据后,Composer会在本地设备中的所有项目里使用这个凭据。

    composer config --global http-basic.laravelacademy.org your-username your-password
    

    全局凭据保存在~/.composer/auth.json中,如果你使用的是Windows系统,相应全局凭据保存在APPDATA%/Composer文件夹中。

    创建组件

    现在你应该知道怎么查找和使用PHP组件了,下面我们来讨论如何创建PHP组件。创建PHP组件是把工作成果分享给PHP社区成员的好方式,PHP社区乐于分享和帮助他人,如果你在应用中使用了开源组件,那么投桃报李,创建有创意的新开源组件是回报社区最好的方式。

    注:别重新编写已经存在的组件,如果是改进现有组件,可以在拉取请求中把改进发送给原本的组件,否则,PHP生态系统将充斥重复的组件。

    命名空间

    在设置命名空间之前,先要确定厂商名称和包名,即形如laravel/framework这样,要确保其全局唯一性,在Packagist中不存在。

    每个组件都有自己的命名空间,说到这里,人们常常误以为组件的命名空间必须与组件的厂商名和包名一致,其实不然,组件使用的命名空间与组件的厂商名和包名无关,厂商名和包名只是为了让Packagist和Composer识别组件,而组件的命名空间是为了在PHP代码中使用组件。

    文件系统结构

    PHP组件的文件系统结构基本上是确定的:

    • src:这个目录用于存放组件的源代码
    • tests:存放组件的测试代码
    • composer.json:Composer配置文件,用于描述组件,声明组件依赖以及自动加载配置等
    • README.md:这个Markdown文件提供关于组件的相关信息、使用文档说明、软件许可证等
    • CONTRIBUTING.md:这个Markdown文件告知别人如何为这个组件做贡献
    • LICENSE:纯文本文件,声明组件的软件许可证
    • CHANGELOG.md:Markdown文件,列出组件在每个版本中引入的改动

    composer.json

    PHP组件中必须包含composer.json文件,而且这个文件的内容必须是有效的JSON,Composer会使用这个文件中的信息查找、安装和自动加载PHP组件。composer.json文件还包含组件在Packagist目录中的信息。

    我们新建一个组件目录(~/Packages/urlscanner),然后在urlscanner目录下通过如下命令生成composer.json文件:

    composer init
    

    然后在终端会让我们按照提示向导一步步填写composer.json内容:

    最后回车,会生成相应的composer.json文件,我们对该文件作如下修改:

    {
        "name": "laravelacademy/urlscanner",
        "description": "Scan URLs FROM A CSV FILE AND REPORT INACCESSIBLE URLs",
        "keywords": ["url", "scanner", "csv"],
        "homepage": "https://xueyuanjun.com",
        "license": "MIT",
        "authors": [
            {
                "name": "sunqiang",
                "email": "yaojinbu@163.com"
            }
        ],
        "support": {
            "email": "yaojinbu@163.com"
        },
        "minimum-stability": "dev",
        "require": {
            "php": ">=5.4.0",
            "guzzlehttp/guzzle": "~5.0"
        },
        "require-dev": {
            "phpunit/phpunit": "~4.3"
        },
        "suggest": {
            "league/csv": "~6.0"
        },
        "autoload": {
            "psr-4": {
                "LaravelAcademy\UrlScanner\": "src/"
            }
        }
    }
    

    我们来仔细研究一下这个文件,看看每个部分究竟是什么意思:
    -name:组件的厂商名和包名,也是Packagist中的组件名

    • description:简要说明组件
    • keywords:描述属性的关键字
    • homepage:组件网站URL
    • license:PHP组件采用的软件许可证(更多软件许可证参考:http://choosealicense.com/)
    • authors:作者信息数组
    • support:组件用户获取技术支持的方式
    • require:组件自身依赖的组件
    • require-dev:开发这个组件所需的依赖
    • suggest:建议安装的组件
    • autoload:告诉Composer自动加载器如何自动加载这个组件

    READEME.md

    通常这个是用户最先阅读的文件,对托管在Github和Bitbucket中的组件来说,更是如此。标准的READEME.md文件至少提供以下信息:

    • 组件的名称和描述
    • 安装说明
    • 使用说明
    • 测试说明
    • 贡献方式
    • 支持资源
    • 作者信息
    • 软件许可证

    实现组件

    开始之前我们使用如下命令安装依赖:

    composer install
    

    该命令会把依赖组件安装到vendor目录并生成自动加载器。

    现在我们要来实现组件的具体功能了。这一步我们要编写组成PHP组件的类、接口和Trait,编写什么类以及编写多少类完全取决于PHP组件的作用。不过组件中的所有类、接口和Trait都要放到src目录下。

    对这个组件来说我只需要创建一个类Scanner,位于子命名空间Url中,这个子命名空间位于composer.json文件中设定的LaravelAcademy/UrlScanner命名空间下,Scanner类保存在src/Url/Scanner.php文件。Scanner类实现的逻辑和上面的URL扫描器示例应用相同,只不过现在我们要把扫描URL的功能封装在一个PHP类中:

    <?php
    namespace LaravelAcademyUrlScannerUrl;
    
    use GuzzleHttpClient;
    
    class Scanner
    {
        protected $urls;
    
        protected $httpClient;
    
        public function __construct(array $urls)
        {
            $this->urls = $urls;
            $this->httpClient = new Client();
        }
    
        /**
         * 获取访问指定URL的HTTP状态码
         *
         * @param $url
         * @return int
         */
        public function getStatusCodeForUrl($url)
        {
            $httpResponse = $this->httpClient->get($url);
            return $httpResponse->getStatusCode();
        }
     
        /**
         * 获取死链
         *
         * @return array
         */
        public function getInvalidUrls()
        {
            $invalidUrls = [];
            foreach ($this->urls as $url) {
                try {
                    $statusCode = $this->getStatusCodeForUrl($url);
                } catch (Exception $e) {
                    $statusCode = 500;
                }
    
                if ($statusCode >= 400) {
                    array_push($invalidUrls, ['url' => $url, 'status' => $statusCode]);
                }
            }
    
            return $invalidUrls;
        }
    
    }
    

    我们没有解析并迭代处理一个CSV文件,而是把一个URL数组传递给Scanner类的构造函数,因为我们要尽量让这个扫描URL的类通用。如果直接处理CSV文件就限制了这个组件的用途。我们把获取URL的来源开放给用户,让他们自己从文件、数组亦或是CSV文件中获取。所以回到上面的composer.json,我们在suggest中声明了league/csv组件,只是建议安装,并不是必须安装。

    提交到Packagist

    我们先将代码提交到GitHub(注意将vendor目录添加到.gitignore)仓库(我的是nonfu/urlscanner):

    涉及到的git命令如下:

    git init
    git remote add origin https://github.com/nonfu/urlscanner.git
    git add .
    git commit -m “urlscanner"
    git pull origin master
    git push origin master
    

    这样就将本地组件提交到GitHub仓库

    然后在Packagist中通过GitHub账户登录,通过https://packagist.org/packages/submit提交组件,在输入框中输入刚刚提交的GitHub仓库地址:

    check成功后点击submit即可将组件提交到Packagist

    其中红色的警告的意思是需要我们通过GitHub Service Hook去GitHub中创建一个钩子,以后每次更新组件的GitHub仓库时通知Packagist。

    使用组件

    至此,我们已经成功将自己的组件提交到Packagist,现在任何人都可以使用Composer安装这个URL扫描器组件,然后在自己的PHP应用中使用。在终端执行如下命令安装这个组件:

    composer require laravelacademy/urlscanner dev-master
    

    然后在我们的代码中使用:

    <?php
    require “vendor/autoload.php”;
    
    $urls = [
        “https://xueyuanjun.com”,
        “http://laravel-academy.org”,
        “https://packagist.org"
    ];
    
    $scanner =  new LaravelAcademyUrlScannerUrlScanner($urls);
    print_r($scanner->getInvalidUrls());
    
  • 相关阅读:
    EggJs+Vue服务端渲染实践
    使用Charles代理功能将网络请求定向至本地文件
    iOS内存管理(一)
    RumTime实践之--UITableView和UICollectionView缺省页的实现
    CollectionView水平和竖直瀑布流的实现
    解决在HTTPS页面里嵌套HTTP页面浏览器block的问题
    利用servlet做转发,实现js跨域解决同源问题
    js将数字转换成大写的人民币表达式
    《将博客搬至CSDN》 分类: 勉励自己 2014-09-05 14:29 43人阅读 评论(0) 收藏
    jquery插件,美化select标签
  • 原文地址:https://www.cnblogs.com/stringarray/p/12839747.html
Copyright © 2011-2022 走看看