作者poopoo888888 (阿川)
看板PHP
标题[心得] COMPOSER进阶原理:PHP命名空间与PSR-0
时间Wed Dec 10 23:42:19 2014
小弟花了很长的时间
才稍微搞懂Composer、namespace、PSR-0
希望这篇文章能帮一些人节省时间
<(_ _)>
网页好读版
http://blog.turn.tw/?p=1122
-----------------------------------
上次的Composer设计原理与基本用法说明了PHP套件管理的历史与社群提出的解决之道,本
篇文章接着谈类别管理的进阶议题。
当类别名称一样…
当专案大了起来,有时候会有类别名称重复的问题。
假设今天要撰写一个论坛模组,提供讨论区与留言板功能。
你一定很想将讨论区的文章与留言板的文章都命名类别为Article:
// BoardArticle.php
<?php
class Article{
//...
}
// ForumArticle.php
<?php
class Article{
//...
}
当然了,这麽做会得到一个结果:
Fatal error: Cannot redeclare class Article
这种问题有一个简单的解决办法,就是加上前缀字。
类别分别命名为ForumArticle与BoardArticle就可以了。
Q1: 等一下!这个解法好阳春!我看到至少4个问题:
1. 类别名称容易变得冗长。
2. 有些类别一开始你以为不会跟人重复,结果之後真的重复了。
难道永远替类别加前缀字?
3. 类别名称写Article俐落多了!文章就是概念上的文章,不要逼我告诉你是讨论区
还是留言板!如果专案用到两种留言板模组,分别由以前的两个前辈写好,
难道还要逼我把作者名称写进去?
class TonyBoardArticle{
//...
}
class JackBoardArticle{
//...
}
4. 如果我在打造框架(framework)呢?几乎会把所有常见名词用过一次(像是Request、
Loader、Response、Controller、Model等类别)!
难道前面全部前缀?看看Codeigniter的原始码,全部用CI_当作前缀。超丑的。
命名空间(namespace)登场
於是PHP从5.3版之後支援了命名空间(namespace)。
所以可以用Article替类别定义了:
// BoardArticle.php
<?php
namespace Board;
class Article{
//...
}
// ForumArticle.php
<?php
namespace Forum;
class Article{
//...
}
使用类别时只要加上命名空间即可:
//index.php
<?php
include 'BoardArticle.php';
include 'ForumArticle.php';
$article = new Forum\Article();
$post = new Board\Article();
如果当前的php档只用到其中一个Article类别,
可以指定当前的php档只用哪个命名空间+类别的组合:
<?php
include 'BoardArticle.php';
include 'ForumArticle.php';
use Forum\Article;
$article = new Article();
$fArticle = new Forum\Article();
$bArticle = new Board\Article();
如此一来,当php找不到Article类别时,便会去使用use关键字宣告的组合。
当然了,就算用use指定过,原本的宣告方式还是可以用的。(如最下方两行所示)
当东西多了起来…
OK,可以继续完成我们的论坛模组了!
讨论区跟留言板有各自的文章,再来还需要「推文」:
<?php
namespace Board;
class Comment{
// ...
}
<?php
namespace Forum;
class Comment{
// ...
}
使用刚刚学到的命名空间去载入他们:
<?php
// index.php
include 'BoardArticle.php';
include 'ForumArticle.php';
include 'BoardComment.php';
include 'ForumComment.php';
$article = new Forum\Article();
$comment = new Forum\Comment();
// ...
果然是漂亮的各种命名阿!
Q2: include有好多行!上次的Composer设计原理与基本用法提到了Composer可以解决这种
问题,当引入命名空间之後,Composer也能发挥作用吗?
是的。
Composer登场
跟上次初学Composer一样,建立一个composer.json档:
{
"autoload": {
"files": [
"ForumArticle.php",
"ForumComment.php",
"BoardArticle.php",
"BoardComment.php"
]
}
}
注意,上次我们用”classmap”指定资料夹、把资料夹内档案全扫一次,这次我们用”
files”分别设定各个档案。
再来,在terminal输入
composer install
执行完毕之後,跟上次一样,只要载入一个档案:
<?php
require 'vendor/autoload.php';
$article = new Forum\Article();
$comment = new Forum\Comment();
就可以使用各个类别罗!
Q3: 等一下!看起来跟没有命名空间的时候差不多啊!
一样是把php档自动require进去而已?
对啊,你最上面的原始写法,也只是手动载入好几个档案,
在载入的时候本来就没有特别之处:
<?php
// index.php
include 'BoardArticle.php';
include 'ForumArticle.php';
include 'BoardComment.php';
include 'ForumComment.php';
$article = new Forum\Article();
$comment = new Forum\Comment();
// ...
载入php档就只是载入,跟命名空间是两回事。
Q4: 还是不太对啊!上次我用classmap一次把好几个资料夹内容扫过,这次我用files分别
指定每个档案干嘛?Composer不是厉害在能指定资料夹去自动扫过?
……你说的没错。
开个my_lib资料夹,把4个php档都丢进去吧。composer.json这样写就好了:
{
"autoload": {
"classmap": [
"my_lib"
]
}
}
再来,在terminal输入
composer install
这样就搞定了!
其实我只是想示范,除了用classmap设定资料夹之外,也可以用files直接指定档案。
Q5: OK,原谅你。不过,我必须说,我今天什麽都没学到。最後还是在composer.json写
classmap而已,跟上次一模一样。
是的…我刚说了,载入php档就只是载入,跟命名空间是两回事。
我今天介绍的namespace功能是PHP本身提供的。而Composer只是协助你载入的工具、
当然不可能改变程式语言本身。
Composer只是帮助你少打那一串require而已。
Q6: ㄟ等等…有个地方我觉得很丑。我们现在的my_lib资料夹里面有4个档案,档名很丑:
"ForumArticle.php"
"ForumComment.php"
"BoardArticle.php"
"BoardComment.php"
类别名称本身都是俐落的Article跟Comment了,档案名称还是有前缀字。
但也不可能有两个Article.php、两个Comment.php。你有没有办法解决这个美感问题?
这个问题简单,把那个my_lib资料夹删掉,开一个Forum资料夹、一个Board资料夹。
本来的ForumArticle.php改成Article.php丢进Forum资料夹、
本来的BoardArticle.php改成Article.php丢进Board资料夹。
composer.json改写成这样:
{
"autoload": {
"classmap": [
"Board",
"Forum"
]
}
}
再来,在terminal输入
composer install
这招不错吧!档案名称就是类别名称,乾净俐落!
而且资料夹的名字本身就是namespace的名称!
以後都这样做啦!一用到命名空间就开个同名资料夹把档案丢进去!
Q7: 这招我觉得还好耶…。本来档案都放在my_lib,我在composer.json只要填my_lib一行
就好,现在变成要填两行。要是我这个论坛模组有一大堆类别、用了一大堆命名空间呢?
那我classmap底下不就要填入好几行?那我宁可全部丢进my_lib,只填my_lib一行!
唔,这样说也是有道理。那重新建立my_lib资料夹,把Board跟Forum资料夹丢进
my_lib资料夹。composer.json改回这样写:
{
"autoload": {
"classmap": [
"my_lib"
]
}
}
再来,在terminal输入
composer install
classmap不只是告诉Composer去载入哪几个资料夹内的档案,还会把资料夹内的
资料夹也全部扫过一次。
怎麽样,Composer够神吧。
Q8: 原来classmap底下会递回扫描下去…。我决定了,我的Forum跟Board都是在
提供线上讨论功能,我决定替我这个模组命名为Discussion。
我要在my_lib底下开Discussion资料夹,然後把原本的Forum跟Board资料夹都丢进去。
你觉得这个想法如何?
还不错。一个Discussion资料夹就是你的整个Discussion模组。
提供了讨论区、留言板功能的Discussion模组,我喜欢。
Q9: 好像忘了什麽…。啊,刚刚说命名空间跟档案结构符合会最漂亮。那我要把那4个档案
的namespace改成这样:
<?php
namespace Dicussion/Forum;
class Article{
//...
}
刚刚说了,载入档案就只是载入档案,跟命名空间无关。
现在档案结构没变,所以我应该不用重新输入composer install。
让我试试…。
靠!怎麽喷error了!你骗我?
Fatal error: Class 'Discussion\Forum\Comment' not found
呃…,我前面的说法确实有点误导。
PHP自动载入的基本函式长这样:
void __autoload ( string $class )
如你所见,PHP至少需要Composer提供资讯指出$class该去哪个档案找。
namespace改变之後,PHP会找不到对应的$class在哪。
所以还是输入一下composer install吧!Composer会把需要的资讯整理好的。
Q10: OKOK,我知道了,我驾驭这一切了。我觉得这个Dicsussion模组真的超屌的,
不但命名空间漂亮,连档案结构都漂亮。
我要把这个Discussion资料夹整个丢给我朋友,他们公司最近需要论坛模组。
让我打电话给他…。
「什麽?你们已经做好半个论坛模组了?你只需要我模组的其中几个功能?
你们的模组也是放在Discussion资料夹?」
糟糕,资料夹名称重复了!所以我的模组拿给别人还是有不相容的可能,怎麽办?
没有错..还记得你Q1提到的第3个状况吗?确实有把作者名称加进去的必要!
别怕,我教你。你开一个Tony资料夹,把整个Discussion资料夹丢进去。
接着所有档案namespace改成像这样:
<?php
namespace Tony/Dicussion/Forum;
class Article{
//...
}
要用的时候就这样喔:
<?php
require 'vendor/autoload.php';
$article = new Tony\Discussion\Forum\Article();
$comment = new Tony\Discussion\Forum\Comment();
是变得有点长啦。
但这下搞定了吧!作者名称再撞到的话,就改个独特的名称就是了!
终於。让我们谈谈PSR-0
你一定常听到PSR-0对吧!
PSR-0是PHP跨框架相容性统一标准组织订出来的自动载入惯例。
来谈谈PSR-0几个最重要的要求吧!
* 命名空间加上类别名称一定要长这样:
\<Vendor Name>\(<Namespace>\)*<Class Name>
* 前面一定要是作者名称
* 中间可以有任意层次的命名空间、最後是类别名称。
* 中间任意层次的命名空间直接对应到档案结构。
发现了吗?在刚刚Q1~Q10的过程中,其实你已经把PSR-0学完了,
连设计原理都一起搞懂了。
懂这些之後,你也可以做好自己的模组、发布到Packagist给全世界
透过composer下载、使用了!
最後,如果遵守psr-0的话,composer.json可以这样写:
{
"autoload": {
"psr-0": {
"Tony\\Discussion\\": "my_lib/"
}
}
}
注意,双引号、两次反斜线并没有特别意思,只是json规定的格式。
跟classmap一样都可以完成任务。两者其实是有差别的…,我们下次再谈。
结语
Composer工具以及PHP-FIG组织的出现,
让一直以来散落各地的PHP社群开始有集中的趋势。
换句话说,各社群终於能共享彼此的library了。
然而,如你所见,psr-0不但导致档案结构容易变得深层,还要求档案结构必须配合程式码
,这也招致了不少批评。
除此之外,composer autoload内的classmap跟psr-0到底如何分工?
效能差异又为何?这些问题也都还在争论与验证当中。
不过,PSR-0在各框架已被广泛支援,因此建议你还是需要有所了解。
最後…
现在已经出现psr-0的替代方案,称为psr-4。
PSR-0从2014-10-21开始被注明为不建议使用。
至於PSR-4..我们下次再谈。
--
※ 发信站: 批踢踢实业坊(ptt.cc), 来自: 118.160.223.112
※ 文章网址: http://webptt.com/cn.aspx?n=bbs/PHP/M.1418226146.A.0AE.html
1F:推 yoyotvyoo: 推 12/10 23:48
2F:推 rickysu: 推 12/11 09:00
3F:推 lucky1lk: 推推 12/11 09:31
4F:推 DJake: 推推推推推 12/11 09:43
5F:推 onininon: 推 12/11 09:51
6F:推 shooliss: 推 12/11 22:20
7F:推 LPH66: 题外话, 其实这种 namespace 的命名规则也是 java package 12/12 09:01
8F:→ LPH66: 建议的命名方式, 因为这些都是可以到处 deploy 的东西 12/12 09:02
9F:→ LPH66: 因此定出来的规则也就大同小异 12/12 09:02
10F:推 aoeeoak: 推,用心写的易於消化 12/13 14:55
11F:推 GTim: 推!有在用,但不是很熟悉…… 12/15 04:23
12F:推 taikobo: 推,问答的方式很活泼,让我想到 Head First 系列 12/16 11:46
13F:推 serinasky: 推 02/23 02:17