Subversion, PHP · 12 min read · Jan 19, 2026

Настройка модульного репозитория Subversion для веб-сайтов на PHP

Настройка модульного репозитория Subversion для веб-сайтов на PHP

Виллем Богаертс - Kratz Business Solutions

Аннотация

Обмен кодом между проектами по-прежнему не является тривиальной задачей с Subversion. Особенно если вы знакомы с SourceSafe, вы обнаружите, что Subversion затрудняет обмен кодом. Subversion, кажется, действительно хорош в создании путаницы с версиями и хорош в решении этой проблемы, но причина, по которой мне нужен контроль версий, заключается в том, чтобы предотвратить такую путаницу. Здесь Subversion можно значительно улучшить, но это не невозможно. Этот howto продемонстрирует настройку каталогов, которая учитывает механизм обмена Subversion, а также другие проблемы, которые возникают с репозиториями.

Конвенция в этом Howto

Вы увидите во многих местах. Замените это на корень вашего репозитория. Корень вашего репозитория - это URL, который обычно начинается с https://, file:/// или svn://.
Предполагается, что вы знаете, что такое Subversion, что вы знаете его основное использование и что у вас есть или вы можете создать репозиторий.
Этот howto не дает вам “лучший” способ организовать репозиторий, потому что это зависит от ваших потребностей. Он направлен на то, чтобы помочь, показывая некоторые из этих решений и то, как они влияют на структуру репозитория.

Способ обмена кодом в Subversion

Каталоги в рабочей копии могут содержать ссылки на другие репозитории, определяя свойство svn:externals. Это приведет к тому, что каталог связанного репозитория будет включен в вашу рабочую копию, но не станет частью самого проекта. Это означает, что вы увидите каталог со всеми файлами в вашей рабочей копии, но не в центральном репозитории. Вы можете увидеть это свойство только в центральном репозитории.

Существует несколько недостатков этого способа обмена:

  • Вы можете указывать только абсолютные URL для ссылок, поэтому бесшовная миграция репозитория практически невозможна. Даже переключение протоколов (например, с svn:// на https://) приведет к поломке рабочих копий и множеству проблем.
  • Вы не можете связывать файлы, только каталоги. Это сильно повлияет на организацию кода.

Проблемы с каталогами PHP

Есть несколько вещей, о которых стоит подумать при работе с каталогами на веб-сайте, и в частности с PHP. В целях безопасности мы не хотим помещать все источники в каталог, доступный из браузера. Единственные файлы, которые мы поместим туда, - это файлы, которые необходимо вызывать из браузера. Я называю эти файлы “исполняемыми” файлами в отличие от “определяющих” файлов, которые содержат только определения классов или функций. Смешивание исполняемого кода и определяющего кода в одном файле обычно не является хорошей идеей.

Также в целях безопасности многие сайты имеют ограниченную область, которая содержит модульные тесты, страницы журналов ошибок или даже полноценный бэк-офис. Эта ограниченная область обычно защищена паролем веб-сервером.

Таким образом, проект содержит каталоги с определяющими файлами и корневой каталог веб-сайта (часто 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 возвращает пути без завершающих слешей.

Организация нашего репозитория

Есть несколько вещей, которые следует учитывать для кода проекта. Для разработки удобно иметь весь проект, проверенный целиком. Но для работающего сервера это может быть не то, что вам нужно. Вы можете захотеть проверить код базы данных (SQL-скрипты) в каталоге, который находится далеко от веб-каталогов, возможно, даже на другом сервере. Может быть, есть части проекта, которые вы вообще не хотите на сервере, но они нужны в разработке, такие как файлы документации.

Более того, мы хотим центральное место для хранения общего кода. Теоретически мы могли бы “заимствовать” код из другого проекта, но было бы действительно сложно отслеживать, какие проекты зависят от каких других проектов. Вместо этого мы переместим любой стандартный код в центральное место.

Корневые каталоги

Центральное место, содержащее весь стандартный код, будет “/standard/“. Этот каталог, конечно, будет подразделен на стандартные библиотеки. Проекты будут находиться в папке “/projects/“, которая может быть подразделена по клиенту и проекту, например. Код извне, такой как загруженные библиотеки, такие как PHPMailer или FPDF, будет помещен в “/external/“.

Ветки и теги

Хорошей практикой в репозитории Subversion является хранение вашего кода в ветке, называемой “trunk”, и создание других веток на том же уровне, если это необходимо. Даже если вы не хотите никаких веток, создайте каталог “trunk” непосредственно под проектом. Trunk - это активная ветка.

Подумайте об этом. Когда мы ссылаемся на trunk стандартной библиотеки, мы ссылаемся на активную ветку. Это означает, что все исправления ошибок в связанной библиотеке будут обновлены всякий раз, когда мы обновляем рабочую копию. Но если мы вводим ошибку, это также будет обновлено в наших рабочих копиях. Даже в той, что на работающем веб-сервере! Вы можете захотеть ссылаться на более или менее стабильную ветку, но исправление ошибок потребует немного больше накладных расходов.

Какую бы ссылку вы ни выбрали, хорошо знать, что вы всегда можете переключиться позже.

Что в компоненте или проекте

Есть много вещей, которые мы хотим поместить в репозиторий, и мы не хотим, чтобы все было в одном месте на нашем веб-сервере. Некоторые вещи лучше вообще не помещать на веб-сервер или могут быть проверены на другом сервере, например, на сервере базы данных.

Обратите внимание, что настройка рабочей копии на машине разработки отличается от настройки на сервере. На машине разработки у вас, вероятно, будет центральный корневой каталог для всех ваших проектов. Этот каталог затем настраивается как доступный через ваш локальный веб-сервер, так что вам не нужно перенастраивать сервер для каждого проекта, над которым вы работаете. На рабочем сервере все равно необходимо настраивать, и соображения безопасности заставляют нас проверять только те файлы, которые нужны, и ничего больше. Для начала мы создаем следующие каталоги в каждом компоненте (если необходимо):

documentation/Входные данные от клиентов, схемы базы данных и объектов и т. д. Нет необходимости помещать это на веб-сервер, но это очень полезно для разработчиков.sql/Скрипты создания, обновления и преобразования базы данных. для проверки на сервере базы данных.code/Фактический код приложения.test/Модульные тесты

Эти каталоги примерно одинаково выглядят в проектах, с тем отличием, что модульные тесты должны быть исполняемыми и, следовательно, являются частью секции кода. Если вы не хотите, чтобы модульные тесты присутствовали на рабочем сервере, вы можете держать их отдельно. Для проектов моя настройка будет:

documentation/Как выше. Также содержит ссылки на используемые компоненты.database/Содержит скрипты создания базы данных и ссылки из

sql

каталогов используемых компонентов.web/Определяющий код приложения.web/htdocs/Корень сайта с фактическим исполняемым кодом приложения, HTML-страницами и другим веб-контентом, таким как таблицы стилей и изображения.web/htdocs/restricted/Ограниченная область.web/test/Модульные тесты. Содержит ссылки на тесты используемых компонентов.selenium/Функциональные тесты (см.

http://selenium.openqa.org/

).

Исполняемый и определяющий код (снова)

Мы могли бы поместить исполняемый код и определяющий код в отдельные каталоги, чтобы мы могли проверить исполняемый код в отдельный каталог внутри корня веб-сайта. Однако это означает, что подкаталог, содержащий этот код, станет частью URL (например, www.example.com/restricted/errorhandling/viewerrorlog.php), и это может быть не то, что вы хотите (вы можете захотеть www.example.com/restricted/viewerrorlog.php). Есть решение для этого. Мы можем поместить исполняемые файлы в недоступное место и поместить своего рода прокси в доступное место. Этот прокси - это PHP-файл, который содержит ничего больше, чем оператор include или require, указывающий на файл в недоступном месте:

/htdocs/restricted/viewerrorlog.php
// Это прокси, который указывает на /errorhandling/viewerrorlog.php, который не может быть вызван напрямую браузером.
// (потому что он находится вне корня веб-сайта)
require(dirname(__FILE__) . '/../../errorhandling/viewerrorlog.php');
?>

Каталог errorhandling затем делится из стандартной библиотеки и содержит “определяющие” классы для обработки ошибок и “исполняемый” файл для их просмотра.

Зависимые от машины настройки

Файлы настроек несколько необычны в репозитории. Вы хотите иметь их в репозитории, но не хотите, чтобы они обновлялись автоматически. Я использую промежуточный метод для своих файлов настроек: я создаю файл под названием settings_example.php в каталоге настроек. Этот файл содержит все настройки с комментариями о том, как настроить их для разных машин. Он также содержит комментарий, который говорит, что вы должны скопировать его в файл под названием “settings.php”

Я ссылаюсь только на settings.php из других файлов, и устанавливаю свойство svn:ignore со значением “settings.php” в каталоге настроек. Это означает, что рабочая копия не будет работать “из коробки”, но она все равно не будет работать из-за зависимости от настроек, зависящих от машины. Свойство svn:ignore предотвращает перезапись настроек с разных машин при обновлении.

Применение на практике

Пример. Предположим, у нас есть проект, который должен отправлять почту и создавать PDF-файлы. Мы решили, что будем использовать PHPMailer от PEAR и библиотеки FPDF. Чтобы избежать наложения каких-либо специальных ограничений в настройках PHP, мы просто загружаем PHPMailer и не используем метод “pear install” для его установки.

Кроме того, этот проект будет иметь стандартную обработку ошибок и стандартный класс базы данных. Для простоты я предполагаю, что вы начинаете с пустого репозитория.

Создание необходимых каталогов

Сначала создайте корневые каталоги, описанные выше: /external/, /standard/ и /projects/. Я рекомендую использовать графический интерфейс для Subversion, такой как TortoiseSVN или RapidSVN.

Сначала мы займемся внешними библиотеками. Скачайте и распакуйте PHPMailer и FPDF и импортируйте их в /external/phpmailer/trunk/ и /external/fpdf/trunk/ соответственно. Если хотите, вы можете создать ветки “firstdownload” для обеих.

Затем мы создаем проект, над которым будем работать. Этот проект предназначен для клиента “CustomerInc”, и проект называется “SamplePrj”. Я уверен, что вы можете придумать лучшие названия для своих проектов. Итак, мы создаем полный путь /projects/CustomerInc/SamplePrj/trunk/. “Trunk” - это основная ветка: вы можете работать в других (развивающихся) ветках, но они в конечном итоге объединяются с trunk. Если вы работаете в trunk (и почему бы и нет, если это новый проект), этот каталог - это каталог, который следует проверить как рабочую копию. Но давайте подождем с этим. Под trunk мы создаем “целевые” каталоги для веб-кода (php, html и т. д.), кода базы данных и документации.
Под каталогом web я создал корень веб-сайта (htdocs) и каталог для общих файлов php, специфичных для проекта.

Создание общих файлов

До сих пор я делал все непосредственно в репозитории. Но чтобы поделиться кодом, нам сначала нужно создать рабочую копию. Итак, давайте сделаем это.

Поскольку наш локальный веб-сервер должен иметь возможность получить доступ ко всему коду нашего проекта, я создаю свою рабочую копию в корневом каталоге своей файловой системы (/projects//) или на жестком диске (C:\projects\\). В противном случае веб-серверу необходимо иметь права доступа в домашнем каталоге всех пользователей, использующих Subversion. Используя корневой каталог, вы можете убедиться, что веб-сервер может получить доступ ко всем им, и вы даже можете разделить каталоги для разных пользователей. Для систем на базе Unix все пользователи Subversion должны быть членами группы вашего веб-сервера, и общий каталог проектов должен быть как минимум доступен для чтения группой. По крайней мере, веб-часть, то есть.

В нашей рабочей копии у нас есть каталог web, где находится каталог htdocs и где мы хотим ссылки на FPDF и PHPMailer. Чтобы создать эти ссылки, добавьте свойство Subversion svn:external к каталогу web со следующим значением:

fpdf /external/fpdf/trunk/code
phpmailer /external/phpmailer/trunk/code

(да, значение состоит из 2 строк) и затем обновите свою рабочую копию. Теперь вы должны получить два дополнительных каталога с вашим обновлением, как показано на этом скриншоте рабочей копии нашего проекта.

Примечание: я создал ссылки на trunk. Вы можете ссылаться на ветку или ссылаться на конкретный номер ревизии. Я создал только внешние ссылки на секции кода. Вы, вероятно, захотите создать внешние ссылки на документацию внешних библиотек также.

Перемещение полезного кода в стандартную библиотеку

По мере того как ваши проекты развиваются, вы разрабатываете все больше и больше кода, который будет полезен в других проектах. В нашем случае, скажем, что мы разработали пакет обработки ошибок и класс базы данных. Конечно, первый шаг - удалить любые зависимости проекта из этого кода. Поместите код для обмена (давайте назовем его пакетом) в отдельный каталог, так как он должен находиться в отдельном каталоге, когда он делится обратно из стандартной библиотеки. Итак, мы убедимся, что общий код обработки ошибок находится в каталоге errorhandling, а класс базы данных - в каталоге database, оба непосредственно под каталогом web.

Вы можете использовать команды svn mkdir и svn move или любой графический клиент Subversion, чтобы переместить пакет в вашем проекте в /standard//trunk/code. Не забудьте обновить вашу рабочую копию разработки после этого (вы увидите, что каталог пакета исчезнет), затем добавьте две строки к свойству svn:external (в форме /standard//trunk/code), обновите (чтобы вернуть каталог, теперь из стандартной библиотеки) и зафиксируйте изменение свойства. Большинство проблем возникает при обновлении ваших рабочих копий, особенно если эта рабочая копия является работающим веб-сайтом, для которого вы хотите минимизировать время простоя.
Если вы обновляете свои рабочие копии после каждого изменения на сервере, ничего не может пойти не так. Проблема возникает, когда у вас есть рабочая копия, которая все еще имеет пакет в оригинальном проектном местоположении, и вы хотите обновиться до состояния, когда тот же каталог теперь поступает из ссылки svn:externals. Если вы попытаетесь это сделать, вы получите ошибку, говорящую, что целевой каталог для этой внешней ссылки уже существует, поэтому внешняя ссылка не может быть создана. Это легко решить, удалив этот каталог вручную перед обновлением этой рабочей копии.

Переместите любые модульные тесты, документацию, SQL-скрипты и т. д. аналогичным образом в стандартную библиотеку.

Обновление рабочего сервера

Когда более одного человека имеет доступ к веб-серверу или серверу базы данных, лучше использовать одну учетную запись для всех них. Это гарантирует, что каталоги .svn не страдают от конфликтующих настроек учетной записи и прав пользователя. Более того, вы можете запретить этой учетной записи права на запись в репозиторий, так что скомпрометированный веб-сервер не сможет легко отправить вредоносные данные.

На веб-сервере Linux следующие права имеют смысл: чтение и запись для пользователя, который обновляет “живую” рабочую копию, права на чтение для группы (которая является группой веб-сервера) и никаких прав для других. Если вы установите бит SGID на каталогах, все добавленные файлы также будут доступны веб-серверу. Вы можете установить бит SGID с помощью chmod g+s . Рассмотрите возможность установки 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.w-p.dds.nl/sqlincludeparser_php.txt (PHP) и http://www.w-p.dds.nl/sqlincludeparser_py.txt (Python). Эти скрипты позволяют вам определять операторы включения в базовом файле следующего вида:

CREATE DATABASE IF NOT EXISTS someDatabase;
USE someDatabase;
-- @include(errorhandling/errortables.sql) # (re)создает таблицы, используемые пакетом обработки ошибок
DROP TABLE IF EXISTS someTable;
CREATE TABLE someTable
      (someID INTEGER UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
...и т. д.

Если вы сделаете каждый включаемый файл и базовый файл повторяемыми, вы сможете использовать его, чтобы снова и снова возвращаться к чистому состоянию, что может быть очень полезно для тестирования и разработки. Чтобы использовать его, просто перенаправьте вывод моего скрипта в клиент командной строки SQL:

sqlincludeparser.py  | mysql -u  -p []
Share: X/Twitter LinkedIn

Get new posts in your inbox

No spam. Unsubscribe anytime.