Сохранение файлов на сервере (PHP)

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

Задача. Пользователь работает автономно, в локальной базе данных. Существует необходимость сохранять на жестком диске на сервере FileMaker бэкап или какие-то выгрузки из локальной базы данных.
Предполагается, что на сервере запущена служба PHP, в папку веб-узла файлмейкера /fmi-test/common/ помещен файл со сценарием-обработчиком запросов:

.

/*
script loads file into specified folder
POST parameters:
tmp_dir - directory to store file, encoded base64
filename - name of file, encoded base64
attach - file hex encoded

*/

ini_set('display_errors', 1);

define("FILES", "/Library/FileMaker Server/Data/Documents/"); // define folder to store files

// this part checks if temp directory was defined in POST parameters. Then decodes it

if (isset($_POST['tmp_dir']) )
{
$dir = FILES . base64_decode($_POST['tmp_dir']);

} else {
print "Error: dir not defined" ;
exit;
}

// this part checks if file name was defined in POST parameters. Then decodes it

if (isset($_POST['filename']) )
{
$path = $dir . '/' . base64_decode($_POST['filename']);
} else {
print "Error: filename not defined" ;
exit;
}

// this part checks if file name was defined in POST parameters

if (isset($_POST['attach']))
{
$attach = pack('H*', $_POST['attach']); // decodes hex encoded file
@mkdir ($dir); // creates temp directory
$put = @file_put_contents($path, $attach); // stores file in specified directory with specified name

// next command returns error message or success message where first row is code (200), next row is temp directory, third row is full path to file
echo !$put ? "error: cannot save file" : 200 . "\n" . $dir . "\n" . $path;
exit;

}

print "error unknown";
exit;

В локальном приложении файл, который требуется сохранить на серверном диске, помещен в контейнер.
Вызываем универсальный скрипт (я назвал скрипт $_save_file_on_dik), и в параметре скрипта указываем этот контейнер. В результате выполнения скрипта файл из контейнера будет сохранен в некоторую директорию на сервере и скрипт вернет код ошибки, полный путь к этой директории и полный путь к файлу.

Сценарий приведен ниже

# script loads file to hard disk on filemaker server location

Set Variable [ $file; Value:Get(ScriptParameter) ]
If [ IsEmpty($file) ]
Exit Script [ ]
End If
# path to php file
Set Variable [ $PHP_PATH; Value:Get(HostIPAddress) & "/fmi-test/common/put_files.php" ]
Set Variable [ $url; Value:$PHP_PATH ]
# path to
Set Variable [ $uuid; Value:Get(UUID) ]
Set Variable [ $filename; Value:Base64Encode ( GetAsText($file)) ]
Set Variable [ $tmp_dir; Value:Base64Encode ($uuid) ]
Set Variable [ $cURL_options; Value:"--data " & Quote("filename=" & $filename & "&tmp_dir=" & $tmp_dir & "&attach=" & HexEncode ( $file)) ]
Insert from URL [ $url ] [ Select; With dialog:off; $result; $url; cURL options:$cURL_options ]
# return: List(error message; path_to_tmp_dir; path_to_file)
Exit Script [ Result: $result ]

Сначала разберемся, что делает скрипт, а потом прокомментируем работу php-сценария.
И первый и второй можно модифицировать, приспосабливая под свои задачи.

Итак, скрипт файлмейкера:
берет файл, переданный в параметре.
Запоминает в переменную URL — адрес PHP файла.
Вычисляет уникальное (UUID) имя временной папки, в которую будет сохранен файл.
Вычисляет имя файла в контейнере.
Кодирует имя файла в Base64
Кодирует имя временной папки в Base64
Вычисляет параметры —data для POST запроса
(в эти параметры включается имя файла, имя временной папки и собственно сам файл, тоже преобразованный в ТЕКСТ кодировкой HexEncode)
Выполняет Insert From URL (результат сразу записывается в переменную $result)

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

Что же делает PHP скрипт?
А он выполняет нечто подобное, но в другом порядке:
запоминает в константе FILES путь к корневой директории на сервере, в которую будут складываться все файлы (для этой директории должны быть заданы права на чтение и на запись)
проверяет, было ли передано в запросе имя временной папки. Если оно передано, то скрипт декодирует имя папки
проверяет, было ли передано в запросе имя файла, если передано, то декодирует его.
проверяет, был ли передан в запросе сам файл. Если передан, то файл раскодируется, создается временная папка на диске, файл помещается в эту временную папку.
отправляется ответ, включающий код и описание ошибки, код исполнения, путь к временной папке, полный путь к файлу.

Что важно добавить? «Временная папка» — это такой довольно условный термин. В своей практике я сохраняю файлы на сервер на очень короткое время и затем средствами плагина BE удаляю папку вместе с файлом. Для этой цели мне не важно имя папки, главное, чтобы имена были уникальными, чтобы одноименные файлы случайно не затирались один другим. Поэтому я использую Get(UUID).
Ничто не мешает создавать постоянные папки и выстраивать собственную структуру серверного «хранилища».

Почему используется кодирование и декодирование данных? Кодирование позволяет передать в параметрах имена для папок, включающих «недопустимые символы» вроде слэша, а в именах файлов вообще любые символы и пробелы. Закодированные имя — это сплошной текст, не содержащий недопустимых символов, он корректно считывается PHP скриптом. Закодировать файл с помощью base64 мне не удалось корректно, в результате такой кодировки может появиться случайно последовательность символов, которая неправильно будет трактоваться PHP скриптом. HexEncode позволяет решить эту проблему.
Ну, и лайфак для пользователей ФМ16. Если вам нужно передать внутри одного параметра несколько текстов в формате JSON или XML, или несколько больших списков, то используйте функцию HexEncode для преобразования текста в одну сплошную строку и тогда можно будет передать а затем распарсить несколько строк. Тот же самый прием можно использовать, если нужно запихнуть HTML текст внутрь JSON.

Leave a Reply

Ваш e-mail не будет опубликован. Обязательные поля помечены *

− 1 = 7