PHP 中的代碼依賴管理(大量的 Composer 技巧來襲)

文章轉發自專業的Laravel開發者社區,原始連接: https://learnku.com/laravel/t...

在建立 PHP 的應用程序或庫時,一般有三種依賴關係:php

  • 硬依賴性:你的應用程序/庫須要此依賴纔可以正常運行
  • 可選的依賴關係:例如一個 PHP 庫能夠爲不一樣的框架提供一個功能
  • 開發依賴:調試工具,測試框架等...

如何管理這些依賴關係?

硬依賴性:laravel

{  
    "require": {  
        "acme/foo": "^1.0"  
    }  
}

可選的依賴關係:git

{  
    "suggest": {  
        "monolog/monolog": "Advanced logging library",  
        "ext-xml": "Required to support XML"  
    }  
}

開發依賴:github

{  
    "require-dev": {  
      "monolog/monolog": "^1.0",  
      "phpunit/phpunit": "^6.0"  
    }  
}

截止到目前仍是很順利。那麼什麼地方會出錯呢? 主要在 require-dev 上會有必定的限制。npm

問題和限制

過多的依賴關係

使用包管理器解決依賴是很是好的。這種方式能夠很好的更新和重用代碼。可是,你得對你引入了哪些包、多少包負責。你引入的這些包會有產生 bug 和不安全的風險。 除了受到第三方問題的困擾以外,你正在變得依賴別人寫下來的東西,而這些東西你可能沒法控制。 PackagistGitHub 對於減小這些風險作了很好的工做,不過風險依然存在. 在 JavaScript 社區中 left-pad fiasco 是一個很好的例子,添加一個包並非徹底沒有影響的,由於它會致使錯誤的發生。json

依賴的第二個問題是它們須要兼容。這是 Composer 的工做。可是 Composer 就是這樣, 有一些依賴你並不能同時安裝,添加越多的依賴就越可能出現衝突。安全

以爲段落長,直接看這裏: 對你引入的依賴進行負責,而且爭取少依賴。bash

強關係衝突

讓咱們來看下面的例子:app

{  
    "require-dev": {  
        "phpstan/phpstan": "^1.0@dev",  
        "phpmetrics/phpmetrics": "^2.0@dev"  
    }  
}

這兩個包是 靜態分析工具(static analysis tools) 而且他們不能同時安裝, 即產生了一個衝突 由於他們依賴不一樣且不兼容的 PHP-Parser版本.composer

這能夠稱做 "愚蠢的_"_ 衝突: 只有在你試圖去包含與你現有應用不兼容的依賴的時候纔會發生衝突. 這兩個包不須要互相兼容, 你的應用程序不會直接使用他們且他們也不會執行你的應用程序代碼.

另外一個例子是你寫了一個 SymfonyLaravel 的鏈接庫. 你想要包含 Symfony 和 Laravel 這兩個依賴去測試他們鏈接:

{  
    "require-dev": {  
        "symfony/framework-bundle": "^4.0",  
        "laravel/framework": "~5.5.0" # gentle reminder that Laravel  
                                      # packages are not semver  
    }  
}

在某些狀況下可能會工做可是極可能大部分的狀況下都會失敗. 這種場景可能有點傻由於你不 可能在同一時間同時包含這兩個包而且你更不可能想要去支持這個場景.

沒法測試的依賴關係

請看示例 composer.json:

{  
    "require": {  
        "symfony/yaml": "^2.8 || ^3.0"  
    },  
    "require-dev": {  
        "symfony/yaml": "^3.0"  
    }  
}

如上的示例… 只有指定的版本的Symfony YAML 組件可被安裝 (可用的symfony/yaml包版本是 [3.0.0, 4.0.0[.

在一個應用中, 你大多數狀況不須要關心這些.可是做爲一個組件庫,這多是一個問題。事實上,這意味着,你沒法在你的組件庫中測試 symfony/yaml [2.8.0, 3.0.0[.

這是否確實是一個問題,很大程度上取決於你的狀況。 要知道這種狀況是可能發生的,而且沒有有效的方法來杜絕它。 上面的例子很簡單,可是若是這個symfony / yaml:^ 3.0在依賴關係樹中隱藏的更深,例如:

{  
    "require": {  
        "symfony/yaml": "^2.8 || ^3.0"  
    },  
    "require-dev": {  
        "acme/foo": "^1.0"  # requires symfony/yaml ^3.0  
    }  
}

至少如今你是沒法知道的。

解決方案

不使用依賴包

親,不要緊,畢竟您不是真的須要這個依賴包

PHARs

PHARs (PHP 檔案)是將應用程序打包爲單一文件的一種方法,若是您想了解更多,建議您查閱 PHP官方網站.

以此爲例 PhpMetrics, 一個靜態分析工具:

$ wget  -o phpmetrics.phar
   
   
   $ chmod +x phpmetrics.phar  
   $ mv phpmetrics.phar /usr/local/bin/phpmetrics  
   $ phpmetrics --version  
   PhpMetrics, version 1.9.0
   
   
   # or if you want to keep the PHAR close and do not mind the .phar  
   # extension:
   
   
   $ phpmetrics.phar --version  
   PhpMetrics, version 1.9.0

警告: 將代碼打包爲PHAR並不像Java中的JARs將代碼隔離起來,即使如此 這裏有一個正在開發中的項目PHP-Scoper 去解決這個問題.

接下來讓咱們舉個栗子來講明這個問題. 你構建了一個控制檯應用程序 myapp.phar 依賴 Symfony YAML 2.8.0 執行給定的PHP腳本:

$ myapp.phar myscript.php

您的腳本 myscript.php 正使用由Composer 引入的 Symfony YAML 4.0.0.

可能發生的是PHAR加載了一個Symfony YAML類,例如,SymfonyYamlYaml 執行您的腳本,您的腳本也依賴於SymfonyYamlYaml ,可是猜猜看,這個類早已經被加載了。這個 問題就是加載的是symfony / yaml 2.8.0這個包,而不是你的腳本須要的4.0.0。 所以,若是API不一樣,這將會很難打破。

TL:DR; PHARs是很好的適應於這些靜態分析工具像PhpStan 或者PhpMetrics 可是因爲一些依賴性的衝突,一旦代碼運行是就變的不那麼可靠了 (至少目前是這樣!).

使用PHAR時還有一些其餘的事情須要記住:

  • 它們很難跟蹤,由於在Composer 中沒有原聲的支持對於它們。然而,存在一些解決方案例如這個Composer 插件tooly-composer-script 或者PhiVe 一個PHAR 安裝程序
  • 如何管理版本取決於項目。 一些項目提供了一個具備不一樣穩定通道的「自我更新」命令,一些項目提供了獨特的下載端點和最新版本,一些項目使用了GitHub發行版,併爲每一個版本發佈了一個PHAR。

使用多個存儲庫

迄今爲止最流行的技術之一。所以,咱們不須要在一個 composer.json 中要求全部的橋依賴關係,而是將這個包分解到多個存儲庫中。

若是咱們把前面的例子叫作 acme/foo,那麼咱們將爲Symfony建立另外一個包 acme/foo-bundle,爲Laravel建立 acme/foo-provider

請注意,全部東西實際上仍然能夠放在單個存儲庫中,而且只有像 Symfony 這樣的其餘軟件包的只讀存儲庫。

這種方法的主要優勢是,它仍然相對簡單,不須要任何額外的工具,除了最終存儲庫分配器像 splitsh,例如 Symfony,Laravel 和 PhpBB。缺點是你如今有多個包來維護,而不是一個。

調整配置

還有一種方法是使用更高級的安裝和測試腳本。對比上一個例子,咱們能夠作一些其餘的事:

#!/usr/bin/env bash  
# bin/tests.sh


# 測試核心庫  
vendor/bin/phpunit --exclude-group=laravel,symfony


# 測試 Symfony 框架
composer require symfony/framework-bundle:^4.0  
vendor/bin/phpunit --group=symfony  
composer remove symfony/framework-bundle


# 測試 Laravel 框架  
composer require laravel/framework:~5.5.0  
vendor/bin/phpunit --group=symfony  
composer remove laravel/framework

在個人經驗中,它是有效的,但這致使了臃腫的測試腳本,在運行中它們相對緩慢,難以維護,對新的貢獻者也不太友好.

使用多個composer.json

這種方法相對來講是比較新的(在 PHP 中),主要是由於所需的工具不是現成的,因此我將在這個解決方案上進一步說明。

這個想法比較簡單,例以下邊:

{  
        "autoload": {...},  
        "autoload-dev": {...},
    
    
        "require": {...},  
        "require-dev": {  
            "phpunit/phpunit": "^6.0",  
            "phpstan/phpstan": "^1.0@dev",  
            "phpmetrics/phpmetrics": "^2.0@dev"  
        }  
    }

咱們將安裝 phpstan/phpstanphpmetrics/phpmetrics 使用不一樣的 composer.json 文件。可是這首先會有一個疑問:咱們把它們放在哪裏?採用哪一種結構?

composer-bin-plugin 便應運而生。一個很是簡單的 Composer 插件,它容許您以不一樣的目錄與一個 composer.json 進行交互。所以咱們先假設咱們有一個 composer.json 根文件

{  
        "autoload": {...},  
        "autoload-dev": {...},
    
    
        "require": {...},  
        "require-dev": {  
            "phpunit/phpunit": "^6.0"  
        }  
    }

咱們可以安裝這個插件:

$ composer require --dev bamarni/composer-bin-plugin

如今插件已經安裝好了,每當您執行 composer bin acme smth ,它就會在子目錄 vendor-bin / acme 中執行composer smth命令。 因此咱們如今能夠像這樣安裝PhpStan和PhpMetrics:

$ composer bin phpstan require phpstan/phpstan:^1.0@dev  
   $ composer bin phpmetrics require phpmetrics/phpmetrics:^2.0@dev

這將建立如下目錄結構:

... # projects files/directories  
    composer.json  
    composer.lock  
    vendor/  
    vendor-bin/  
        phpstan/  
            composer.json  
            composer.lock  
            vendor/  
        phpmetrics/  
            composer.json  
            composer.lock  
            vendor/

其中 vendor-bin / phpstan / composer.json 看起來像這樣:

{  
        "require": {  
            "phpstan/phpstan": "^1.0"  
        }  
    }

而且 vendor-bin/phpmetrics/composer.json 看起來像這樣:

{  
        "require": {  
            "phpmetrics/phpmetrics": "^2.0"  
        }  
    }

因此如今咱們能夠調用 vendor-bin / phpstan / vendor / bin / phpstanvendor-bin / phpmetrics / vendor / bin / phpstan 來輕鬆地使用PhpStan和PhpMetrics。

如今咱們更進一步的以一個庫在不一樣框架的引用爲例

{  
        "autoload": {...},  
        "autoload-dev": {...},
    
    
        "require": {...},  
        "require-dev": {  
            "phpunit/phpunit": "^6.0",  
            "symfony/framework-bundle": "^4.0",  
            "laravel/framework": "~5.5.0"  
        }  
    }

所以,同上 Symfony 引用的 vendor-bin/symfony/composer.json 文件:

{  
        "autoload": {...},  
        "autoload-dev": {...},
    
    
        "require": {...},  
        "require-dev": {  
            "phpunit/phpunit": "^6.0",  
            "symfony/framework-bundle": "^4.0"  
        }  
    }

Laravel 引用的 vendor-bin/laravel/composer.json 文件:

{  
        "autoload": {...},  
        "autoload-dev": {...},
    
    
        "require": {...},  
        "require-dev": {  
            "phpunit/phpunit": "^6.0",  
            "laravel/framework": "~5.5.0"  
        }  
    }

咱們的根 composer.json 如今應該是這樣的:

{  
        "autoload": {...},  
        "autoload-dev": {...},
    
    
        "require": {...},  
        "require-dev": {  
            "bamarni/composer-bin-plugin": "^1.0"  
            "phpunit/phpunit": "^6.0"  
        }  
    }

爲了測試核心庫之間的引用關係,你須要建立3個不一樣的單元測試文件,其中每個都有 autoload.php(例如: Symfony 的引用文件 vendor-bin/symfony/vendor/autoload.php)。

若是你真的試試,你將會發現這種方法的一個主要缺點: 冗餘配置。 肯定你須要重複的根配置文件 composer.json 到其餘兩個 vendor-bin/{symfony,laravel}/composer.json, 調整自動加載變化的文件路徑,當你須要一個新的依賴,你須要在其餘的composer.json包含它。這是不可控的,因此 composer-inheritance-plugin出現了。

這個小包裝插件composer-merge-pluginvendor-bin/symfony/composer.json內容合併到根composer.json。因此不是以下:

{  
    "autoload": {...},  
    "autoload-dev": {...},


    "require": {...},  
    "require-dev": {  
        "phpunit/phpunit": "^6.0",  
        "symfony/framework-bundle": "^4.0"  
    }  
}

如今是這樣:

{  
    "require-dev": {  
        "symfony/framework-bundle": "^4.0",  
        "theofidry/composer-inheritance-plugin": "^1.0"  
    }  
}

其餘的配置,自動加載和依賴將被包含在根 composer.json 。沒有配置的, composer-inheritance-plugin是一個瘦小的包composer-merge-plugin來預配置任何使用composer-bin-plugin

您能夠檢查安裝它須要的依賴,經過:

$ composer bin symfony show

我在不少項目中使用這個方法,像alice,不一樣於PhpStan和PHP-CS-Fixer這樣的靜態分析工具和框架橋接器。另外一個例子是alice-data-fixtures,其中有不少不一樣的ORM橋持久層(Doctrine ORM, Doctrine ODM, Eloquent ORM,等)和框架的整合。

做爲替代phars的另一種工具,我在多個私人項目中使用了它,而且它工做得很好。

結論

我相信有些人會發現一些奇怪的方法或不推薦使用它們。 這裏的目標不是判斷或者推薦一個特殊的東西,而是列出一些可能的方法來管理一些依賴關係,以及每一個依賴關係的優勢和缺點。 因此,根據你的問題和你的我的喜愛,選擇一個最適合你的。 正如人們所說,沒有解決辦法,只有權衡。

相關文章
相關標籤/搜索