设置用于PHP驱动的网站的模块化Subversion存储库
Willem Bogaerts - 克拉茨商业解决方案
抽象
在项目之间共享代码仍然不是颠覆性的问题。 特别是如果你熟悉SourceSafe,你会发现subversion使得很难共享代码。 Subversion似乎真的很好,在创建一个版本的混乱,很好的解决一个,但我需要源代码控制的原因是为了防止这样的混乱。 颠覆可以大大提高,但这并不是不可能的。 这个howto将演示一个目录设置,将subversion共享机制考虑在内,以及存储库带来的其他问题。
公约在这个Howto
你会在很多地方看到<repository>
。 将其替换为存储库的根目录。 您的存储库的根目录通常以https://
, file:///
或svn://开头
。
假设你知道什么是subversion ,你知道它的基本用法,你有或可以创建一个存储库。
这个方法不会给你“最好的”组织一个存储库的方式,因为它取决于你的需要。 它旨在通过展示一些这些决策以及它们如何影响存储库的结构来帮助。
Subversion的共享方式
通过定义svn:externals
属性,工作副本中的目录可以包含指向其他存储库的链接。 这将导致链接的存储库目录包含在您的工作副本中,但不会使其成为项目本身的一部分。 这意味着您将在工作副本中看到包含所有文件的目录,但不会在中央存储库中。 您只能在中央存储库中查看该属性。
这种共享方式有一些缺点:
- 您只能给出链接的绝对URL,因此无缝地迁移存储库几乎是不可能的。 甚至交换协议(例如,
svn://
到https://
)将导致工作副本损坏和很多麻烦。 - 您无法链接文件,仅链接目录。 这将对代码的组织产生很大的影响。
PHP目录问题
在使用网站上的目录,特别是PHP时,需要考虑几件事情。 为了安全起见,我们不希望将所有的源放在可以从浏览器访问的目录中。 我们将放置的唯一的文件是需要由浏览器调用的文件。 我将这些文件称为“运行”文件,而不是“定义”文件,而在其中只有类定义或函数定义。 混合运行代码并在一个文件中定义代码通常不是一个好主意。
另外,作为安全性,许多站点都有一个限制区域,其中包含单元测试,错误日志页面,甚至完整的后台站点。 这个受限制的区域通常受到Web服务器的密码保护。
因此,项目包含定义文件的目录和包含运行文件和可选的限制区域的Web根目录(通常是www /
或htdocs /
)。
PHP和相对目录
唉,PHP具有非常直观的方式来确定包含文件的位置。 包含文件的命令全部与第一个文件相关,而不是与当前文件相关。 为了使事情变得更糟,当原始位置不会导致现有文件时,将使用当前文件位置。
这听起来很复杂,这是一个例子:
假设你调用一个页面“index.php”。 此页面包含一个页面“library / functions.php”。 这又包括“settings.php”。 你会怀疑在库目录中搜索“settings.php”,但不是。 它在与“index.php”相同的目录中进行搜索!
如上所述,如果找不到文件,PHP将继续查看预期目录。 所以一切似乎按照你的期望工作,直到你遇到不同目录中具有相同名称的文件。 然后,您很难找出为什么PHP“突然”从错误的目录中选择文件。
这意味着您无法安全地包含具有相对路径的另一个文件。 我们必须让他们绝对的代码如下:
require_once(dirname(__FILE__) . '/library/functions.php');
不要在相对路径的开始处忘记'/',因为dirname函数返回没有斜杠的路径。
组织我们的存储库
项目的代码需要考虑几件事情。 对于开发,整个项目整体检查是方便的。 但是对于实时服务器,这可能不是你想要的。 您可能希望在远离Web目录的目录中查看数据库代码(SQL脚本),甚至可能在另一个服务器上。 可能有一部分项目您根本不想在服务器上,但在开发中需要,如文档文件。
此外,我们想要一个存储共享代码的中心位置。我们理论上可以从另一个项目中“借用”代码,但是很难跟踪哪些项目依赖于哪些其他项目。 相反,我们将把任何标准代码移动到中央位置。
根目录
包含所有标准代码的中心位置将是“ <repository> / standard /
”。 该目录当然会被细分为标准库。 项目将在“ <repository> / projects /
”文件夹中,例如可以由客户端和项目细分。 来自外部的代码,如下载的库(如PHPMailer或FPDF)将被放入“ <repository> / external /
”中。
分支机构和标签
在Subversion版本库中,将代码保存在名为“ trunk
”的分支中,并在需要时在同一级创建其他分支,这是很好的做法。 即使您不想要任何分支,也可以直接在项目下面创建一个目录“ trunk
”。 中继线是主动分支。
想想这个 当我们链接到标准库的中继线时,我们链接到活动分支。 这意味着当我们更新工作副本时,链接库中的所有错误更正将被更新。 但是,如果我们引入错误,这也将更新到我们的工作副本中。 即使是一个在线的网络服务器! 您可能希望链接到或多或少稳定的分支,但是修复错误将需要稍微更多的开销。
无论你选择什么链接,很高兴知道你可以随时切换。
组件或项目中的内容
有很多东西我们想放在一个存储库,我们不希望在我们的Web服务器上的所有东西在同一个地方。 有些东西最好不要放在网络服务器上,或者可能被检出到不同的服务器,如数据库服务器。
请注意,开发机器上的工作副本的设置与服务器上的工作副本的设置不同。 在开发机器上,您可能会有一个所有项目的中央根目录。 然后将此目录配置为可通过localhost Web服务器访问,因此您不必为正在处理的每个项目重新配置服务器。 在实时服务器上,必须配置任何东西,安全考虑因素使我们只能查看所需的那些文件,而不需要更多的内容。 为了开始,我们在每个组件中创建以下目录(如果需要):
- 文件/
- 客户输入,数据库和对象方案等。无需将其放在Web服务器上,但对开发人员来说非常有用。
- sql /
- 数据库创建,更新和转换脚本。 用于在数据库服务器上检出。
- 码/
- 实际应用代码
- 测试/
- 单元测试
这些目录在项目中大致相同,区别在于单元测试应该是可运行的,因此是代码部分的一部分。 如果您不希望在实时服务器上存在单元测试,则可以将它们分开。 对于项目,我的设置将是:
- 文件/
- 如上。 还包含使用组件的引用。
- 数据库/
-
包含数据库创建脚本和来自已使用组件的
sql
目录的引用。 - 网络/
- 定义应用程序代码。
- web / htdocs /
- 站点根与实际运行的应用程序代码,HTML页面和其他Web内容,如样式表和图像。
- web / htdocs / restricted /
- 管制区。
- 网络/测试/
- 单元测试。 包含来自使用组件测试的引用。
- 硒/
- 功能测试(见 http://selenium.openqa.org/ )。
运行和定义代码(再次)
我们可以将运行的代码和代码定义在单独的目录中,以便我们可以将运行的代码检出到Web根目录中的单独的目录中。 但是,这意味着包含该代码的子目录将成为URL的一部分(如www.example.com/restricted/errorhandling/viewerrorlog.php),并且可能不是您想要的(您可能希望www.example.com /restricted/viewerrorlog.php)。 有一个解决方案。 我们可以将运行的文件放在不可访问的地方,并将一种代理放在可访问的地方。 该代理是一个PHP文件,只包含
一个指向非可访问位置的文件的include
或require
语句:
<?php // Errorlog, which is called <project dir>/htdocs/restricted/viewerrorlog.php // It is a proxy that points to <project dir>/errorhandling/viewerrorlog.php, which cannot be called directly by a browser. // (because it is outside the web root) require(dirname(__FILE__) . '/../../errorhandling/viewerrorlog.php'); ?>
目录错误
处理然后从标准库共享,并包含“定义”类文件来处理错误和一个“运行”文件来查看它们。
机器相关设置
存储库中的设置文件有点不寻常。 您确实希望将它们存储在存储库中,但您不希望它们自动更新。 我为我的设置文件使用了一个中间的方法:我在设置目录中创建一个名为settings_example.php
的文件。 此文件包含有关如何为不同机器设置注释的所有设置。 它还包含一个注释,说你必须将它复制到一个名为“ settings.php
”的文件中
我仅从其他文件中引用settings.php
,并在settings
目录中设置一个值为“settings.php”的svn:ignore
属性。 这意味着工作副本不会运行“开箱即用”,但是由于依赖于依赖于机器的设置,所以不会这样做。 svn:ignore
属性可防止不同机器的设置覆盖更新。
把它付诸实践
一个例子。 说我们有一个需要发送邮件和创建PDF文件的项目。 我们决定使用PEAR和FPDF库中的PHPMailer。 为了避免在PHP设置中强加任何特殊限制,我们只需下载PHPMailer,不要使用“pear install”方法进行安装。
此外,该项目将具有标准错误处理和标准数据库类。 为了简单起见,我想你从一个空的仓库开始。
创建需要的目录
<repository> / external /
,
<repository> / standard /
and
<repository> / projects /
。我建议使用图形前端来颠覆,如
TortoiseSVN或
RapidSVN 。
我们先处理外部图书馆。 下载并解压缩PHPMailer和FPDF,并将它们分别导入到<repository> / external / phpmailer / trunk /
和<repository> / external / fpdf / trunk /
如果你喜欢,你可以从他们两个创建分支“第一下载”。
接下来,我们创建一个工作项目。 该项目为客户“CustomerInc”,项目名为“SamplePrj”。 我确信你可以为你的项目找到更好的名字。 所以我们创建了<repository> / projects / CustomerInc / SamplePrj / trunk /
的完整路径。 “主干”是主要分支:您可能在其他(开发)分支机构工作,但最终合并到中继线。 如果您正在中继线上工作(为什么不是新项目),则该目录是作为工作副本检出的目录。 但是让我们等一下。 在主干下,我们为网页代码(php,html等),数据库代码和文档创建“目的”目录。
在Web目录下,我创建了一个Web根( htdocs
)和一个专用的专用php文件的目录。
创建股票
直到现在,我直接在存储库上完成了所有操作。 但是要分享代码,我们必须先制作一份工作副本。 所以让我们这样做
因为我们的localhost网络服务器必须能够达到我们所有的项目代码,所以我将我的文件系统( / projects / <username> / <projectname>
)的根文件夹或硬盘( C:\ projects \ <username> \ <projectname>
)。 否则,Web服务器需要在使用subversion的所有用户的主目录中具有访问权限。 通过使用根文件夹,您可以确保Web服务器可以访问它们,甚至可以为不同的用户分隔目录。 对于基于unix的系统,所有subversion用户应该是您的Web服务器组的成员,并且总项目目录应该至少是组可读的。 至少网络的东西,就是。
在我们的工作副本中,我们有目录Web
, htdocs
目录所在的目录,以及我们想要连接到FPDF和PHPMailer的位置。 要创建这些链接,请使用以下值为web
目录添加一个subversion属性svn:external
:
fpdf<repository>
/external/fpdf/trunk/code phpmailer<repository>
/external/phpmailer/trunk/code
注意:我创建了到中继线的链接。 您可以链接到分支,或链接到特定的版本号。 我只创建了对代码段的外部引用。 您可能还需要为外部库的文档创建外部引用。
将有用的代码移动到标准库
随着您的项目成熟,您开发越来越多的代码,将在其他项目中做得很好。 在我们的例子中,假设我们开发了一个错误处理包和一个数据库类。 当然,第一步是从代码中删除任何项目依赖关系。 将代码共享(让我们称之为一个包)在一个单独的目录中,因为它必须在从标准库共享的单独的目录中。 所以我们确保通用的错误处理代码在目录中的错误处理,数据库类在目录数据库中
,两者都直接在web
目录下。
svn mkdir
和
svn move
命令或任何图形化的subversion客户端将项目中的包移动到
<repository> / standard / <package_name> / trunk / code
。不要忘记在此之后更新开发工作副本(您将看到包目录消失),然后在
svn:external
属性(格式为
<package_name> <repository> / standard / <package_name> / trunk
)中添加两行
/ code
),update(从目录返回,现在从标准库)并提交属性更改。最重要的问题是更新您的工作副本,特别是如果该工作副本是您想要的实时网站,尽可能少的停机时间。
如果您在服务器上每次更改后更新您的工作副本,都不会出错。当您的工作副本仍然在原始项目位置上具有包的情况下,并且要更新到同一目录来自
svn:externals
引用的状态时,该问题会上升。如果您尝试这样做,您将收到一条错误,指出该外部引用的目标目录已存在,因此无法创建外部引用。在更新该工作副本之前,可以通过手动删除该目录轻松解决此问题。
以类似于标准库的方式移动任何单元测试,文档,SQL脚本等。
更新Live Server
当多个人可以访问网络或数据库服务器时,最好使用所有这些帐户的一个帐户。 这样可以确保.svn
目录不受冲突的帐户设置和用户权限的影响。 此外,您可以在存储库中拒绝该帐户的写入权限,因此受损的网络服务器无法轻松执行恶作剧。
在Linux Web服务器上,以下权限是有意义的:为更新“实时”工作副本的用户阅读和写入,读取组(这是Web服务器组)的权限,而对其他人没有权限。 如果在目录中设置SGID位,则所有添加的文件也可以由Web服务器访问。 您可以使用chmod g + s <directory>
设置SGID位。 考虑将umask设置为0027以设置添加文件的权限。 一个新添加的htdocs目录可能看起来像:
drwxr-s--- 9 webdev abyssd 4096 2007-09-08 12:42 restricted/
-rw-r----- 1 webdev abyssd 441 2007-09-08 12:42 index.php
-rw-r----- 1 webdev abyssd 2954 2007-09-08 12:42 main.css
模块化数据库代码
我们已经讨论过如何在PHP中包含工作,但是如何在SQL中包含文件? 我们需要在SQL代码中包含来自不同目录的不同文件,因为我们以这种方式组织了我们的存储库。 如果我们共享PHP代码,我们应该共享相应的SQL代码。 我写了一个PHP和一个小Python脚本来做到这一点。 您可以在http://www.wp.dds.nl/sqlincludeparser_php.txt(PHP )和http://www.wp.dds.nl/sqlincludeparser_py.txt(Python )中找到它们。 这些脚本允许您在表单的基本文件中定义include语句:
CREATE DATABASE IF NOT EXISTS someDatabase; USE someDatabase; -- @include(errorhandling/errortables.sql) # (re)creates tables used by the errorhandling package DROP TABLE IF EXISTS someTable; CREATE TABLE someTable (someID INTEGER UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, ...etc.
如果您使每个包含的文件和基本文件都可重复,则可以使用它一遍又一遍地进入干净状态,这对于测试和开发来说非常有用。 要使用它,只需将我的脚本的输出管道传递给sql命令行客户端:
sqlincludeparser.py <base file location> | mysql -u <administrator user> -p [<other mysql options>]