В связи с тем, что предыдущий сервис с помощью которого я хранил изображения накрылся медным тазом (скорее всего из-за того, что был не прибыльный), мне пришлось искать другие варианты хранения изображений. Сервера я использую бюджетные и не хотелось бы мне платить приличную цену за дополнительные 10 ГБ дисковой памяти. Изучая рынок я наткнулся на Google Cloud Storage (GCS) и решил, что данный продукт мне подойдет (ну как минимум можно протестировать). В рунете (да и не только в нем) мало уделяется внимания для настройки GCS с использованием PHP, поэтому я решил внести свою лепту в это направление.

В данной статье будет рассмотрено 2 варианта настройки GCS для загрузки файлов (в примере будет реализована загрузка изображения) с помощью php-клиента и с помощью существующего sdk (утилита gsutil) используя shell. Итак, поехали!

Регистрация


Первое, что необходимо будет сделать — зарегистрироваться в Google Cloud Platform. Для этого можете перейти по ссылке. Возможно еще не кончилась акция, и вы сможете получить 300$ в подарок! Проблем у вас не должно возникнуть, поэтому процесс регистрации решил не описывать. Правда вам необходимо будет оставить свой номер телефона и кредитной карты.

Для проверки перейдите в раздел «Оплата» и если вы увидите подобное окно, значит вам желательно привязать платежный аккаунт, иначе вы не сможете использовать GCS JSON API.



Создание и настройка проекта


В шапке станицы у вас должен отобразиться блок Выберите проект, при нажатии на который вы увидите следующее окно. В моём случае вместо Выберите проект отображается Photo Project, т.к. у меня уже созданы два проекта.



Создайте свой новый проект, нажав на кнопку СОЗДАТЬ ПРОЕКТ. Введите название проекта, и нажмите на кнопку СОЗДАТЬ. В течении нескольких секунд будет создан ваш проект и вы сможете его выбрать.

После того, как вы выбрали созданный проект перейдите в раздел Оплата с помощью меню навигации слева на странице. Обратите внимание, чтобы в блоке Проекты в этом платежном аккаунте был ваш созданный проект. В моём случае новый проект с названием My Project 71698 был автоматически добавлен в этот блок.



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

В случае, если вашего проекта в блоке не появилось, то вам необходимо перейти в раздел Storage с помощью меню навигации и выбрать платежный аккаунт с помощью кнопки Включить оплату. В моём случае у проекта Share The Route платежный аккаунт создан не был.



Создать сегмент можно с помощью интерфейса или через запрос. Пока давайте создадим сегмент с помощью интерфейса нажав на кнопку Создать сегмент.

В зависимости от поставленной задачи, вы можете выбрать нужный вам Класс хранилища по умолчанию.



Если кратко описать классы хранилища, то:

Multi-Regional — подойдет для сайта, которым пользуется вся страна
Regional — сайт, который в большинстве случаев используется в одном каком-то регионе
Nearline — для данных, которые используются не чаще, чем раз в месяц.
Coldline — для данных, которые используются не чаще, чем раз в год.
Выбрав все необходимые настройки нажмите на кнопку Создать.

Настройка сервера используя php-клиент


Для настройки сервера вы можете обратиться к Cloud Storage Client Libraries, если вы захотите настроить проект под другой язык программирования.

Для начала, вам необходимо скачать библиотеку для работы с GCS используя composer. Если вдруг у кого его нет — поставьте.

composer require google/cloud-storage

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



В новом окне выберите Новый сервисный аккаунт и заполните поле Название сервисного аккаунта. В Роли укажите Владелец (Проект->Владелец). После этого будет создан ключ в формате json. Вы можете сохранить его в удобное для вас место.



Далее, согласно инструкции, необходимо создать переменную среды указав путь до файла с ключом:

Linux/Mac OS

export GOOGLE_APPLICATION_CREDENTIALS="/home/user/Downloads/[FILE_NAME].json"


Windows

set GOOGLE_APPLICATION_CREDENTIALS="C:\Users\name\Downloads\[FILE_NAME].json"

Но в связи с тем, что по какой-то причине на моей Mac OS Sierra это не сработало, я покажу и альтернативный вариант, если вдруг кто-то столкнется с подобной проблемой.

На этом все необходимые настройки закончены и мы можем переходить к написанию кода:

Создадим index.html для загрузки изображения:

<html>
    <head>
        <title>File Upload</title>
    </head>
    <body>
        <h1>File Upload</h1>
        <form method="post" action="gcs.php" enctype="multipart/form-data">
            <label for="inputfile">Upload File</label>
            <input type="file" id="file" name="file"></br>
            <input type="submit" value="Click To Upload">
        </form>
    </body>
</html>

И скрипт gcs.php, который будет загружать файл в облако:

# Подключаем autoloader для GCS
require __DIR__ . '/vendor/autoload.php';

# Подключаем необходимый класс для работы
use Google\Cloud\Storage\StorageClient;

# Проверяем, загрузил ли пользователь файл
if(isset($_FILES) && $_FILES['file']['error'] == 0) {
    $allowed = array('png', 'jpg', 'gif','zip', 'jpeg');

    $ext = pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION);

    if(!in_array(strtolower($ext), $allowed)) {
        echo 'The file is not an image.';
        die;
    }

    # Указываем название проекта
    $projectId = 'crafty-booth-205017';

    # Создаем соединение с GCS
    $storage = new StorageClient([
        'projectId' => $projectId,
        'keyFilePath' => '/home/user/Downloads/[FILE_NAME].json'  # Наш ключ
    ]);

    # Наш сегмент
    $bucketName = 'my-project-for-img';
    $bucket = $storage->bucket($bucketName);

    # Код загрузки файла
    $uploader = $bucket->getResumableUploader(
        fopen($_FILES['file']['tmp_name'], 'r'), [
            'name' => 'images/load_image.png',
            'predefinedAcl' => 'publicRead',
	    ]
    );

    # Отправка файла в облоко GCS
    try {
        $uploader->upload();
        echo 'File Uploaded';
    }  catch (GoogleException $ex) {
        $resumeUri = $uploader->getResumeUri();
        $object = $uploader->resume($resumeUri);
        echo 'No File Uploaded';
    }
}
else{
    echo 'No File Uploaded';
}

Теперь давайте немого разберем код.

$projectId — для того, чтобы узнать идентификатор вашего проекта, вы можете в шапке кликнуть на выпадающий список проектов и увидеть в новом окне столбцы Имя и Идентификатор. Столбец Идентификатор, как раз содержит необходимое нам значение.



keyFilePath — ссылка на наш ключ, который мы скачали. Данное свойство необходимо указывать, если установленную переменную среды не удается увидеть запущенному на сервере веб-сервису.
$bucketName — имя сегмента, который мы создали через интерфейс. Его вы можете увидеть в разделе Storage.
Метод getResumableUploader можно использовать и с одним параметром, тогда загружаемое изображение будет сохранено в корень сегмента с таким же именем. В моём примере использовались дополнительные свойства:
name — отвечает за новое имя файла. Но тут можно указывать не только имя, но и вместе с этим путь, относительно сегмента. В данном случае добавлена директория images, которая будет автоматически создана, если её ещё нет.
predefinedAcl — устанавливает уровень доступа к загружаемому файлу. Значение publicRead говорит о том, что данный файл может быть доступен по ссылке любому пользователю/сайту.
Более подробно метод можно изучить по следующей ссылке.

Плюсы подхода:

+ библиотека размером < 10 МБ;
+ можно использовать на сторонних хостингах.

Минусы подхода:

— библиотека находится в бете;
— сложная для понимания документация.

Таким образом, данный вариант я бы посоветовал для тех людей, у которых мало свободного пространства на сервере или если вы используете сторонний сервер без доступа к терминалу для установки утилиты gsutil.

Настройка сервера используя утилиту gsutil


Для начала нужно установить данную утилиту на сервер. Для этого скачиваем и устанавливаем sdk (пример для Linux/Mac OS. Под Debian/Ubuntu и Windows смотрите по ссылке):

curl https://sdk.cloud.google.com | bash

После установки сбрасываем shell:

exec -l $SHELL

Если при сбросе shell'а вам выпала ошибка о том, что модуль node не был найден, то попробуйте его удалить и снова установить через репозиторий.

Выполните инициализацию:

gcloud init

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

После того, как пройдет авторизация, вам будет предложено выбрать сегмент, с которым вы будете работать.

Теперь вы можете использовать все возможности утилиты gsutil. Ознакомиться со всеми командами можете по ссылке.
Для копирования изображений нам понадобиться команда cp. Она имеет следующую структуру:

gsutil cp [OPTION]... src_url dst_url

Для отправки изображения используем тот же index.html
<html>
    <head>
        <title>File Upload</title>
    </head>
    <body>
        <h1>File Upload</h1>
        <form method="post" action="gcs.php" enctype="multipart/form-data">
            <label for="inputfile">Upload File</label>
            <input type="file" id="file" name="file"></br>
            <input type="submit" value="Click To Upload">
        </form>
    </body>
</html>

И существенно изменим gcs.php:
# Проверяем, загрузил ли пользователь файл
if(isset($_FILES) && $_FILES['file']['error'] == 0) { 
    $allowed = array('png', 'jpg', 'gif','zip', 'jpeg');

    $ext = pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION);

    if(!in_array(strtolower($ext), $allowed)) {
    	echo 'The file is not an image.';
        die;
    }

    $name = time() . "." . $ext;
    $cmd = "gsutil cp -a public-read $_FILES['file']['tmp_name'] gs://my-project-for-img/images/{$name}";
    $result = liveExecuteCommand($cmd);
    if ($result['exit_status'] !== 0) {
        echo 'No File Uploaded';
        die;
    }
    echo 'File Uploaded';
}
else{
    echo 'No File Uploaded';
}

Функция liveExecuteCommand позволяет отследить, как выполняется переданная команда с промежуточным выводом, но вы можете использовать и shell_exec.

public function liveExecuteCommand($cmd, $isLog = false)
    {

        while (@ ob_end_flush()); // end all output buffers if any

        $proc = popen("$cmd 2>&1 ; echo Exit status : $?", 'r');

        $live_output     = "";
        $complete_output = "";

        while (!feof($proc))
        {
            $live_output     = fread($proc, 4096);
            $complete_output = $complete_output . $live_output;
            if ($isLog)
                echo "$live_output";
            @ flush();
        }

        pclose($proc);

        # get exit status
        preg_match('/[0-9]+$/', $complete_output, $matches);

        # return exit status and intended output
        return array (
            'exit_status'  => intval($matches[0]),
            'output'       => str_replace("Exit status : " . $matches[0], '', $complete_output)
        );
    }

Плюсы подхода:

+ удобная документация;
+ возможность работы через терминал;
+ больше возможностей.
+ простой синтаксис

Минусы:

— вес sdk около 280 МБ;
— необходим доступ к терминалу на сервере

По моему мнению данный подход больше подходит тем, у кого нет проблем со свободным местом на сервере (т.к. sdk в 28 раз тяжелее) или необходима работа как через shell, так и через терминал.

Заключение


В статье я попытался подробно и по шагам расписать, как можно использовать GCS для сохранения изображений и в каком случае использовать тот или иной подход. Не претендую на абсолютную истину, но т.к. на данную тематику слишком мало информации в рунете — считаю, что статья должна быть интересна тем, кто захочет так же добавить GCS в свой проект.

Комментарии (3)


  1. patsuckow
    26.05.2018 23:11

    У меня пока не было необходимости хранить фотографии где бы-то ни было ещё, кроме простого хостинга сайта, поэтому мой вопрос наверное прозвучит немного глупо, но всё же:
    я не совсем понял, этот способ позволяет только загружать и хранить изображения (как в вэб-хранилище, типа Dropbox) или так получаем прямые ссылки на файлы изображений, которые можно вставлять в шаблон сайта для отображения?


    1. AGENTxXx Автор
      26.05.2018 23:15

      Доброго времени суток! Скорее всего не всю статью прочитали или не очень внимательно. Да, мы получаем прямые ссылки, которые можем вставлять на сайте. У Cloud'а хороший канал изображения будут загружаться быстро (я сравнивал, если загружать на свой vds, то примерно в 4 раза с него медленней было).
      Ну а так, цитата из статьи:

      Значение publicRead говорит о том, что данный файл может быть доступен по ссылке любому пользователю/сайту.


      1. patsuckow
        27.05.2018 09:27

        ой, точно) видимо эту строку не понял. Спасибо за статью! :)