One place for hosting & domains

      Использование

      Установка и использование Composer в Ubuntu 20.04


      Введение

      Composer — это популярный менеджер зависимостей PHP, который упрощает процесс установки и обновления зависимостей проекта. Он проверяет, от каких прочих пакетов зависит конкретный проект, а затем устанавливает все необходимые версии пакетов в соответствии с требованиями. Composer также часто используется для загрузки новых проектов на основе популярных инфраструктур PHP, таких как Symfony и Laravel.

      Данное руководство поможет установить и начать работу с Composer на сервере Ubuntu 20.04.

      Предварительные требования

      Для прохождения этого обучающего модуля вам потребуется доступ к серверу Ubuntu 20.04 на уровне sudo без привилегий root и включенный на сервере брандмауэр. Чтобы выполнить настройку, воспользуйтесь руководством по первоначальной настройке сервера Ubuntu 20.04.

      Шаг 1 — Установка PHP и необходимых зависимостей

      В дополнение к зависимостям, уже входящим в комплект системы Ubuntu 20.04, таким как git и curl, Composer требует php-cli для выполнения скриптов PHP в командной строке и unzip для распаковки архивов. Сейчас мы установим эти зависимости.

      Во-первых, необходимо обновить кэш менеджера пакетов:

      Запустите следующую команду для установки требуемых пакетов:

      • sudo apt install php-cli unzip

      Для подтверждения установки введите Y и нажмите ENTER.

      После установки обязательных компонентов можно переходить к установке Composer.

      Шаг 2 — Загрузка и установка Composer

      Composer предоставляет написанный на PHP скрипт installer. Мы должны загрузить его, убедиться, что он не поврежден, а затем использовать его для установки Composer.

      Убедитесь, что вы находитесь в домашней директории, а затем загрузите установщик с помощью curl:

      • cd ~
      • curl -sS https://getcomposer.org/installer -o composer-setup.php

      Далее мы убедимся, что хэш установщика совпадает с хэшем SHA-384 для последней версии установщика на странице Composer Public Keys / Signatures. Чтобы упростить проверку, вы можете использовать следующую команду для программного получения последней версии хэша со страницы Composer и ее сохранения в переменной оболочки:

      • HASH=`curl -sS https://composer.github.io/installer.sig`

      Для проверки полученного значения можно использовать следующую команду:

      Output

      e0012edf3e80b6978849f5eff0d4b4e4c79ff1609dd1e613307e16318854d24ae64f26d17af3ef0bf7cfb710ca74755a

      Теперь выполните следующий код PHP, приведенный на странице загрузки Composer, чтобы подтвердить безопасность запуска скрипта установки:

      • php -r "if (hash_file('SHA384', 'composer-setup.php') === '$HASH') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"

      Вывод должен выглядеть так:

      Output

      Installer verified
      

      Если вы увидите сообщение Installer corrupt, вам нужно повторно загрузить скрипт установки и еще раз убедиться, что вы используете правильный хэш. Затем повторите процедуру проверки. После успешной проверки установщика вы можете продолжить.

      Чтобы выполнить глобальную установку composer, используйте следующую команду, которая выполнит загрузку и установку Composer в качестве общесистемной команды composer в каталоге /usr/local/bin:

      • sudo php composer-setup.php --install-dir=/usr/local/bin --filename=composer

      Вывод будет выглядеть следующим образом:

      Output

      All settings correct for using Composer Downloading... Composer (version 1.10.5) successfully installed to: /usr/local/bin/composer Use it: php /usr/local/bin/composer

      Чтобы протестировать установку, запустите команду:

      Output

      ______ / ____/___ ____ ___ ____ ____ ________ _____ / / / __ / __ `__ / __ / __ / ___/ _ / ___/ / /___/ /_/ / / / / / / /_/ / /_/ (__ ) __/ / ____/____/_/ /_/ /_/ .___/____/____/___/_/ /_/ Composer version 1.10.5 2020-04-10 11:44:22 Usage: command [options] [arguments] Options: -h, --help Display this help message -q, --quiet Do not output any message -V, --version Display this application version --ansi Force ANSI output --no-ansi Disable ANSI output -n, --no-interaction Do not ask any interactive question --profile Display timing and memory usage information --no-plugins Whether to disable plugins. -d, --working-dir=WORKING-DIR If specified, use the given directory as working directory. --no-cache Prevent use of the cache -v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug ...

      Это подтверждает, что диспетчер зависимостей Composer был успешно установлен и доступен в рамках всей системы.

      Примечание: если вы предпочитаете иметь отдельные исполняемые модули Composer для каждого проекта, который вы размещаете на этом сервере, вы можете выполнить установку локально для каждого проекта. Этот метод также полезен, когда системный пользователь не имеет прав на установку программного обеспечения в рамках всей системы.

      Для этого воспользуйтесь командой php composer-setup.php​​. В текущем каталоге будет сгенерирован файл composer.phar, который можно будет запустить с помощью команды php composer.phar.

      А теперь давайте рассмотрим использование Composer для управления

      Шаг 3 — Использование Composer в проекте PHP

      PHP проекты часто зависят от внешних библиотек, и управление этими зависимостями и их версиями может вызывать затруднения. Composer решает эту проблему посредством отслеживания версий и зависимостей проектов, а также упрощает процесс поиска, установки и обновления пакетов, требуемых для проекта.

      Чтобы использовать Composer в вашем проекте, вам потребуется файл composer.json. Файл composer.json указывает Composer, какие зависимости для вашего проекта нужно загрузить, а также какие версии каждого пакета можно использовать. Это очень важно для сохранения последовательности вашего проекта и отказа от установки нестабильных версий, которые могут вызывать проблемы с обратной совместимостью.

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

      Использование Composer для установки пакета в качестве зависимости в проект подразумевает следующие шаги:

      • Определите, какая библиотека необходима приложению.
      • Изучите подходящую библиотеку из открытого источника на Packagist.org, официальном репозитории пакетов для Composer.
      • Выберите пакет, который вы будете использовать в качестве зависимости.
      • Запустите composer require, чтобы включить зависимость в файл composer.json и установить пакет.

      Давайте попробуем сделать это на примере демо-приложения.

      Приложение преобразует указанное предложение в понятную человеку часть URL-адреса (slug). Как правило, подобные приложения используются для преобразования названия страницы в URL-адрес (например, последняя часть URL для данного обучающего руководства).

      Начнем с создания директории для нашего проекта. Мы назовем его slugify.

      • cd ~
      • mkdir slugify
      • cd slugify

      Хотя это не требуется, вы можете запустить команду composer init для создания детального файла composer.json для вашего проекта. Поскольку единственная цель нашего проекта — продемонстрировать установку зависимостей на Composer, мы используем более простой файл composer.json, который будет сгенерирован автоматически, когда нам потребуется наш первый пакет.

      Теперь нужно найти на Packagist.org пакет, который поможет нам генерировать понятные человеку части URL-адреса. При поиске термина «slug» на Packagist вы получите примерно такой результат:

      Результаты поиска в Packagist по ключевому слову «slug»

      Вы увидите два числа с правой стороны каждого пакета в списке. Число сверху указывает на количество установок пакета через Composer, а число внизу показывает, какие оценки пакету ставили на GitHub. Как правило, пакеты с большим количеством установок и большим количеством звезд более стабильны, потому что многие люди их используют. Также важно проверить описание пакета на соответствие тому, что вам нужно.

      Нам нужен конвертер ​​из строки в понятную человеку часть URL-адреса. Среди результатов поиска хорошо подходит пакет cocur/slugify, отображаемый на первой строке, для которого указано существенное количество установок и звезд.

      Пакеты на Packagist имеют имя автора и имя пакета. Каждый пакет имеет уникальный идентификатор (пространство имен) в том же формате, который использует GitHub для своих репозиториев: vendor/package. Библиотека, которую мы хотим установить, использует пространство имен cocur/slugify. Для ее указания в вашем проекте требуется пространство имен пакета.

      Теперь, когда вы знаете, какой пакет хотите установить, запустите composer require, чтобы добавить его в качестве зависимости, а также сгенерировать файл composer.json для вашего проекта. При запросе пакетов важно учитывать, что Composer отслеживает как зависимости на уровне приложений, так и зависимости на уровне системы. Зависимости на уровне системы важны, чтобы показать, на какие модули PHP полагается пакет. Для пакета cocur/slugify требуется модуль PHP, который мы еще не установили.

      Если требуемый пакет использует системную библиотеку, которая еще не установлена на вашем сервере, вы получите сообщение об ошибке с указанием невыполненного требования:

      • composer require cocur/slugify

      Output

      Using version ^4.0 for cocur/slugify ./composer.json has been updated Loading composer repositories with package information Updating dependencies (including require-dev) Your requirements could not be resolved to an installable set of packages. Problem 1 - Installation request for cocur/slugify ^4.0 -> satisfiable by cocur/slugify[v4.0.0]. - cocur/slugify v4.0.0 requires ext-mbstring * -> the requested PHP extension mbstring is missing from your system. ...

      Для решения проблемы с системной зависимостью мы можем выполнить поиск отсутствующего пакета с помощью apt search:

      Output

      Sorting... Done Full Text Search... Done php-mbstring/focal 2:7.4+75 all MBSTRING module for PHP [default] php-patchwork-utf8/focal 1.3.1-1 all UTF-8 strings handling for PHP php7.4-mbstring/focal 7.4.3-4ubuntu1 amd64 MBSTRING module for PHP

      Определив правильное имя пакета, вы можете использовать apt еще раз для установки системной зависимости:

      • sudo apt install php-mbstring

      После завершения установки вы можете запустить команду composer require еще раз:

      • composer require cocur/slugify

      Output

      Using version ^4.0 for cocur/slugify ./composer.json has been created Loading composer repositories with package information Updating dependencies (including require-dev) Package operations: 1 install, 0 updates, 0 removals - Installing cocur/slugify (v4.0.0): Downloading (100%) Writing lock file Generating autoload files

      Как видите, Composer автоматически определил, какую версию пакета использовать. Если вы сейчас проверите каталог вашего проекта, он будет содержать два новых файла: composer.json и composer.lock, а также каталог vendor.

      Output

      total 12 -rw-rw-r-- 1 sammy sammy 59 May 4 13:56 composer.json -rw-rw-r-- 1 sammy sammy 3229 May 4 13:56 composer.lock drwxrwxr-x 4 sammy sammy 4096 May 4 13:56 vendor

      Файл composer.lock используется для хранения информации о том, какие версии каждого пакета установлены, а также для использования одних и тех же версий пакетов, если кто-либо будет клонировать ваш проект и устанавливать зависимости. Каталог vendor служит местом расположения зависимостей проекта. Папка vendor не обязательно должна использоваться для контроля версий, в нее следует поместить только файлы composer.json и composer.lock.

      При установке проекта, который уже содержит файл composer.json, запустите composer install, чтобы загрузить зависимости проекта.

      Давайте быстро пробежимся по ограничениям версии. Если вы просмотрите содержимое файла composer.json, то увидите следующее:

      Output

      { "require": { "cocur/slugify": "^4.0" } }

      Вы можете заметить специальный символ ^ перед номером версии в файле composer.json. Composer поддерживает несколько ограничений и форматов для определения требуемой версии пакета, чтобы обеспечить гибкость и одновременно сохранить стабильность вашего проекта. Оператор карет ​​(^), используемый в автоматически генерируемом файле composer.json, рекомендуется применять для обеспечения максимальной совместимости в соответствии с семантическим управлением версиями. В данном случае он определяет 4.0 в качестве минимальной совместимой версии и позволяет обновляться до любой будущей версии ниже 5.0.

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

      Ниже представлены примеры, которые помогут лучше понять, как работают ограничения версии в Composer:

      Ограничение Значение Пример допустимых версий
      ^1.0 >= 1.0 < 2.0 1.0, 1.2.3, 1.9.9
      ^1.1.0 >= 1.1.0 < 2.0 1.1.0, 1.5.6, 1.9.9
      ~1.0 >= 1.0 < 2.0.0 1.0, 1.4.1, 1.9.9
      ~1.0.0 >= 1.0.0 < 1.1 1.0.0, 1.0.4, 1.0.9
      1.2.1 1.2.1 1.2.1
      1.* >= 1.0 < 2.0 1.0.0, 1.4.5, 1.9.9
      1.2. * >= 1.2 < 1.3 1.2.0, 1.2.3, 1.2.9

      Более подробное описание ограничений версии в Composer см. в официальной документации.

      Теперь нужно рассмотреть, как автоматически загружать зависимости с помощью Composer.

      Шаг 4 — Включение скрипта автозагрузки

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

      Вам нужно будет только включить файл vendor/autoload.php в скрипты PHP перед созданием экземпляра любого класса.

      Протестируем его в нашем демонстрационном приложении. Откройте в текстовом редакторе новый файл с именем test.php:

      Добавьте следующий код, который будет подключать файл vendor/autoload.php, загружать зависимость cocur/slugify и использовать ее для создания понятной человеку части URL-адреса:

      test.php

      <?php
      require __DIR__ . '/vendor/autoload.php';
      
      use CocurSlugifySlugify;
      
      $slugify = new Slugify();
      
      echo $slugify->slugify('Hello World, this is a long sentence and I need to make a slug from it!');
      

      Сохраните файл и закройте редактор.

      Запустите скрипт:

      Вы должны получить вывод hello-world-this-is-a-long-sentence-and-i-need-to-make-a-slug-from-it.

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

      Шаг 5 — Обновление зависимостей проекта

      Если вам нужно обновить зависимости проекта на более поздние версии, запустите команду update:

      Она будет проверять новые версии библиотек, которые требуются вам в вашем проекте. Если будет найдена новая версия, которая совместима с ограничением версии, определенным в файле composer.json, Composer заменит ранее установленную версию на новую. Файл composer.lock будет обновлен, чтобы отразить эти изменения.

      Вы также можете обновить одну или несколько конкретных библиотек, указав их следующим образом:

      • composer update vendor/package vendor2/package2

      Обязательно зарегистрируйте файлы composer.json и composer.lock в системе контроля версий после обновления зависимостей, чтобы другие тоже могли установить обновленные версии.

      Заключение

      Composer — это мощный инструмент, значительно упрощающий управление зависимостями в проектах PHP. Он дает надежный способ обнаружения, установки и обновления пакетов PHP, от которых зависит проект. В этом обучающем модуле мы узнали, как установить Composer, как включить в проект новые зависимости и как обновлять эти зависимости при выходе новых версий.



      Source link

      Использование Go с MongoDB с помощью драйвера Go MongoDB


      Автор выбрал фонд Free Software Foundation для получения пожертвования в рамках программы Write for DOnations.

      Введение

      Много лет MongoDB зависела от решений, создаваемых общими усилиями, но затем ее разработчики объявили, что работают над официальным драйвером для Go. В марте 2019 года этот новый драйвер достиг уровня готовности к эксплуатации в версии 1.0.0, и с тех пор он регулярно обновляется.

      Как и другие официальные драйвера MongoDB, драйвер Go является неотъемлемой частью языка программирования Go и обеспечивает удобную возможность использования MongoDB в качестве решения для баз данных программы Go. Он полностью интегрирован с API MongoDB, а также имеет все функции запросов, индексирования и агрегирования API и другие продвинутые функции. В отличие от сторонних библиотек, он будет полностью поддерживаться инженерами MongoDB, поэтому вы сможете быть уверенными в его дальнейшей разработке и поддержке.

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

      Предварительные требования

      Для этого обучающего руководства вам потребуется следующее:

      • Go, установленный на вашем компьютере, и рабочее пространство Go, настроенное в соответствии с разделом Установка Go и настройка локальной среды программирования. В этом обучающем руководстве проект будет называться tasker. Вам потребуется Go v1.11 или выше, установленный на компьютере с активированными модулями Go.
      • MongoDB, установленный для вашей операционной системы в соответствии с разделом Установка MongoDB. MongoDB 2.6 или выше — минимальная версия, поддерживаемая драйвером MongoDB Go.

      Если вы используете Go v1.11 или 1.12, то убедитесь, что модули Go активированы, присвоив переменной среды GO111MODULE значение on — так, как показано ниже:

      Дополнительную информацию об активации переменных среды можно найти в этом обучающем руководстве: Чтение и установка переменных сред и переменных оболочки.

      Команды и код, указанные в этом руководстве, были протестированы в Go версии v1.14.1 и MongoDB версии v3.6.3.

      Шаг 1 — Установка драйвера MongoDB Go

      На этом шаге вы установите пакет драйвера Go для MongoDB и импортируете его в ваш проект. Также вы подключитесь к вашей базе данных MongoDB и проверите состояние подключения.

      Теперь создайте новый каталог для этого обучающего руководства в файловой системе:

      После настройки каталога проекта измените его с помощью следующей команды:

      Затем инициализируйте проект Go с файлом go.mod. Этот файл определяет требования проекта и блокирует зависимости от правильных версий:

      Если директория вашего проекта находится за пределами директории $GOPATH, то вам нужно указать путь импорта вашего модуля следующим образом:

      • go mod init github.com/<your_username>/tasker

      Теперь файл go.mod будет выглядеть следующим образом:

      go.mod

      module github.com/<your_username>/tasker
      
      go 1.14
      

      Добавьте драйвер MongoDB Go в качестве зависимости для вашего проекта, используя следующую команду:

      • go get go.mongodb.org/mongo-driver

      Результат будет выглядеть примерно следующим образом:

      Output

      go: downloading go.mongodb.org/mongo-driver v1.3.2 go: go.mongodb.org/mongo-driver upgrade => v1.3.2

      Теперь файл go.mod будет выглядеть следующим образом:

      go.mod

      module github.com/<your_username>/tasker
      
      go 1.14
      
      require go.mongodb.org/mongo-driver v1.3.1 // indirect
      

      Затем создайте файл main.go в корневом каталоге проекта и откройте его в текстовом редакторе:

      Для начала работы с драйвером импортируйте следующие пакеты в файл main.go:

      main.go

      package main
      
      import (
          "context"
          "log"
      
          "go.mongodb.org/mongo-driver/mongo"
          "go.mongodb.org/mongo-driver/mongo/options"
      )
      

      Здесь вы добавляете пакеты mongo и опций, которые предоставляет драйвер MongoDB Go.

      Затем, в зависимости от импорта, создайте новый клиент MongoDB и подключитесь к запущенному серверу MongoDB:

      main.go

      . . .
      var collection *mongo.Collection
      var ctx = context.TODO()
      
      func init() {
          clientOptions := options.Client().ApplyURI("mongodb://localhost:27017/")
          client, err := mongo.Connect(ctx, clientOptions)
          if err != nil {
              log.Fatal(err)
          }
      }
      

      mongo.Connect() принимает Context и объект options.ClientOptions, который используется для установки строки подключения и других параметров драйвера. Вы можете посмотреть документацию по опциям пакетов, чтобы узнать, какие варианты конфигурации доступны.

      Context — крайний срок, указывающий, когда операция должна остановиться и показать результат. Это помогает предотвращать снижение производительности работы в производственных системах, когда отдельные операции работают медленно. В этом коде вы передаете context.TODO(), чтобы указать, что вы не уверены, какой именно контекст нужно использовать сейчас, но планируете добавить контекст в дальнейшем.

      Затем убедимся, что ваш сервер MongoDB был обнаружен и подключен для успешного использования метода Ping. Добавьте следующий код под функцией init:

      main.go

      . . .
          log.Fatal(err)
        }
      
        err = client.Ping(ctx, nil)
        if err != nil {
          log.Fatal(err)
        }
      }
      

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

      Добавьте следующий код для создания базы данных:

      main.go

      . . .
        err = client.Ping(ctx, nil)
        if err != nil {
          log.Fatal(err)
        }
      
        collection = client.Database("tasker").Collection("tasks")
      }
      

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

      Сохраните и закройте файл.

      На этот момент полный main.go выглядит следующим образом:

      main.go

      package main
      
      import (
          "context"
          "log"
      
          "go.mongodb.org/mongo-driver/mongo"
          "go.mongodb.org/mongo-driver/mongo/options"
      )
      
      var collection *mongo.Collection
      var ctx = context.TODO()
      
      func init() {
          clientOptions := options.Client().ApplyURI("mongodb://localhost:27017/")
          client, err := mongo.Connect(ctx, clientOptions)
          if err != nil {
              log.Fatal(err)
          }
      
          err = client.Ping(ctx, nil)
          if err != nil {
              log.Fatal(err)
          }
      
          collection = client.Database("tasker").Collection("tasks")
      }
      

      Вы выполнили настройку вашей программы для подключения к серверу MongoDB с помощью драйвера Go. На следующем шаге вы продолжите создание программы диспетчера задач.

      Шаг 2 — Создание программы интерфейса командной строки

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

      Запустите следующую команду для добавления пакета в качестве зависимости:

      • go get github.com/urfave/cli/v2

      Затем откройте файл main.go еще раз:

      Добавьте следующий выделенный код в файл main.go:

      main.go

      package main
      
      import (
          "context"
          "log"
          "os"
      
          "github.com/urfave/cli/v2"
          "go.mongodb.org/mongo-driver/mongo"
          "go.mongodb.org/mongo-driver/mongo/options"
      )
      . . .
      

      Вы импортируете пакет cli, как упоминалось. Также вы импортируете пакет os, который вы будете использовать для передачи аргументов командной строки в вашу программу:

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

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:     "tasker",
              Usage:    "A simple CLI program to manage your tasks",
              Commands: []*cli.Command{},
          }
      
          err := app.Run(os.Args)
          if err != nil {
              log.Fatal(err)
          }
      }
      

      Этот фрагмент кода создает программу интерфейса командной строки под названием tasker и добавляет краткое описание использования, которое будет отображаться при запуске программы. Набор командной строки находится в том месте, где вы будете добавлять команды для вашей программы. Команда Run отображает список аргументов для подходящей команды.

      Сохраните и закройте файл.

      Вот команда, которая вам потребуется для создания и запуска программы:

      Вывод должен выглядеть так:

      Output

      NAME: tasker - A simple CLI program to manage your tasks USAGE: main [global options] command [command options] [arguments...] COMMANDS: help, h Shows a list of commands or help for one command GLOBAL OPTIONS: --help, -h show help (default: false)

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

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

      Шаг 3 — Создание задачи

      На этом шаге вы добавите субкоманду в вашу программу CLI при помощи пакета cli. В конце этого раздела вы сможете добавить новую задачу в базу данных MongoDB, используя новую команду add (добавить) в вашей программе CLI.

      Начнем с открытия файла main.go:

      Затем импортируем пакеты go.mongodb.org/mongo-driver/bson/primitive, time (времени) и errors (ошибок):

      main.go

      package main
      
      import (
          "context"
          "errors"
          "log"
          "os"
          "time"
      
          "github.com/urfave/cli/v2"
          "go.mongodb.org/mongo-driver/bson/primitive"
          "go.mongodb.org/mongo-driver/mongo"
          "go.mongodb.org/mongo-driver/mongo/options"
      )
      . . .
      

      Затем создадим новую структуру для представления одной задачи базе данных и ее вставки непосредственно перед функцией main:

      main.go

      . . .
      type Task struct {
          ID        primitive.ObjectID `bson:"_id"`
          CreatedAt time.Time          `bson:"created_at"`
          UpdatedAt time.Time          `bson:"updated_at"`
          Text      string             `bson:"text"`
          Completed bool               `bson:"completed"`
      }
      . . .
      

      Вы будете использовать пакет primitive для установки типа ID каждой задачи, поскольку MongoDB использует ObjectID для поля _idпо умолчанию. Еще одно действие MongoDB по умолчанию — имя поля из строчных букв используется в качестве ключа для каждого экспортированного поля, когда ему присваивают серию, но это можно изменять с использованием тегов структуры bson.

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

      main.go

      . . .
      func createTask(task *Task) error {
          _, err := collection.InsertOne(ctx, task)
        return err
      }
      . . .
      

      Метод collection.InsertOne() добавляет выделенную задачу в набор баз данных и возвращает ID документа, который был добавлен. Поскольку вам не потребуется этот идентификатор, вы удаляете его, присваивая его оператору подчеркивания.

      Следующий шаг — добавление новой команды в программу диспетчера задач для создания новых задач. Назовем ее add:

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:  "tasker",
              Usage: "A simple CLI program to manage your tasks",
              Commands: []*cli.Command{
                  {
                      Name:    "add",
                      Aliases: []string{"a"},
                      Usage:   "add a task to the list",
                      Action: func(c *cli.Context) error {
                          str := c.Args().First()
                          if str == "" {
                              return errors.New("Cannot add an empty task")
                          }
      
                          task := &Task{
                              ID:        primitive.NewObjectID(),
                              CreatedAt: time.Now(),
                              UpdatedAt: time.Now(),
                              Text:      str,
                              Completed: false,
                          }
      
                          return createTask(task)
                      },
                  },
              },
          }
      
          err := app.Run(os.Args)
          if err != nil {
              log.Fatal(err)
          }
      }
      

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

      В этом коде вы берете первый аргумент для add (добавления) и используете его для установки свойства Text новой задачи, а также присваиваете соответствующие значения по умолчанию другим свойствам. Новая задача передается далее в createTask, которая вносит задачу в базу данных и возвращает nil, если все идет хорошо, после чего команда прекращается.

      Сохраните и закройте файл.

      Протестируйте ее, добавив несколько задач с помощью команды add. В случае успешного выполнения на экране не будет ошибок:

      • go run main.go add "Learn Go"
      • go run main.go add "Read a book"

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

      Шаг 4 — Перечисление всех задач

      Перечисление документов в коллекции можно сделать с помощью метода collection.Find(), который предполагает фильтр, а также указатель значения, в которое можно расшифровать результат. Его значение — Cursor, которое предоставляет поток документов, которые можно обрабатывать пошагово, расшифровывая отдельные документы по очереди. Затем Cursor закрывается после завершения его использования.

      Откройте файл main.go:

      Обязательно импортируйте пакет bson:

      main.go

      package main
      
      import (
          "context"
          "errors"
          "log"
          "os"
          "time"
      
          "github.com/urfave/cli/v2"
          "go.mongodb.org/mongo-driver/bson"
          "go.mongodb.org/mongo-driver/bson/primitive"
          "go.mongodb.org/mongo-driver/mongo"
          "go.mongodb.org/mongo-driver/mongo/options"
      )
      . . .
      

      Затем создайте следующие функции сразу после выполнения createTask:

      main.go

      . . .
      func getAll() ([]*Task, error) {
        // passing bson.D{{}} matches all documents in the collection
          filter := bson.D{{}}
          return filterTasks(filter)
      }
      
      func filterTasks(filter interface{}) ([]*Task, error) {
          // A slice of tasks for storing the decoded documents
          var tasks []*Task
      
          cur, err := collection.Find(ctx, filter)
          if err != nil {
              return tasks, err
          }
      
          for cur.Next(ctx) {
              var t Task
              err := cur.Decode(&t)
              if err != nil {
                  return tasks, err
              }
      
              tasks = append(tasks, &t)
          }
      
          if err := cur.Err(); err != nil {
              return tasks, err
          }
      
        // once exhausted, close the cursor
          cur.Close(ctx)
      
          if len(tasks) == 0 {
              return tasks, mongo.ErrNoDocuments
          }
      
          return tasks, nil
      }
      

      BSON (JSON в двоичном коде) — метод представления документов в базе данных MongoDB, а пакет bson это то, что помогает нам работать с объектами BSON в Go. Тип bson.D, используемый в функции getAll(), представляет документ BSON, а также используется в тех случаях, когда порядок свойств важен. Передавая bson.D{}} в качестве фильтра в Tasks(), вы указываете, что хотите сопоставить все документы в коллекции.

      В функции filterTasks() вы пошагово выполняете Cursor, возвращаемый методом collection.Find(), и расшифровываете каждый документ в экземпляр Task (задачи). Затем каждая задача добавляется в список задач, созданных при запуске функции. После завершения использования Cursor будет закрыт, а задачи будут возвращены список задач.

      Прежде чем вы создадите команду для перечисления всех задач, создадим функцию helper (помощник), которая принимает на себя долю задач и отображает стандартный вывод. Вы будете использовать пакет color для придания цвета выводимым результатам.

      Чтобы начать использовать этот пакет, установите его с помощью следующей команды:

      • go get gopkg.in/gookit/color.v1

      Вывод должен выглядеть так:

      Output

      go: downloading gopkg.in/gookit/color.v1 v1.1.6 go: gopkg.in/gookit/color.v1 upgrade => v1.1.6

      И импортируйте его в файл main.go вместе с пакетом fmt:

      main.go

      package main
      
      import (
          "context"
          "errors"
        "fmt"
          "log"
          "os"
          "time"
      
          "github.com/urfave/cli/v2"
          "go.mongodb.org/mongo-driver/bson"
          "go.mongodb.org/mongo-driver/bson/primitive"
          "go.mongodb.org/mongo-driver/mongo"
          "go.mongodb.org/mongo-driver/mongo/options"
          "gopkg.in/gookit/color.v1"
      )
      . . .
      

      Затем создайте новую функцию printTasks после функции main:

      main.go

      . . .
      func printTasks(tasks []*Task) {
          for i, v := range tasks {
              if v.Completed {
                  color.Green.Printf("%d: %sn", i+1, v.Text)
              } else {
                  color.Yellow.Printf("%d: %sn", i+1, v.Text)
              }
          }
      }
      . . .
      

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

      Теперь добавьте следующие выделенные строки для создания новой команды all в списке командной строки. Эта команда отобразит все дополнительные задачи в стандартном выводе:

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:  "tasker",
              Usage: "A simple CLI program to manage your tasks",
              Commands: []*cli.Command{
                  {
                      Name:    "add",
                      Aliases: []string{"a"},
                      Usage:   "add a task to the list",
                      Action: func(c *cli.Context) error {
                          str := c.Args().First()
                          if str == "" {
                              return errors.New("Cannot add an empty task")
                          }
      
                          task := &Task{
                              ID:        primitive.NewObjectID(),
                              CreatedAt: time.Now(),
                              UpdatedAt: time.Now(),
                              Text:      str,
                              Completed: false,
                          }
      
                          return createTask(task)
                      },
                  },
                  {
                      Name:    "all",
                      Aliases: []string{"l"},
                      Usage:   "list all tasks",
                      Action: func(c *cli.Context) error {
                          tasks, err := getAll()
                          if err != nil {
                              if err == mongo.ErrNoDocuments {
                                  fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                                  return nil
                              }
      
                              return err
                          }
      
                          printTasks(tasks)
                          return nil
                      },
                  },
              },
          }
      
          err := app.Run(os.Args)
          if err != nil {
              log.Fatal(err)
          }
      }
      
      . . .
      

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

      Сохраните и закройте файл.

      Создайте программу и запустите ее с помощью команды all:

      Она отобразит все задачи, которые вы добавили на данный момент:

      Output

      1: Learn Go 2: Read a book

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

      Шаг 5 — Завершение задачи

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

      Начнем с открытия файла main.go:

      Вставьте следующий фрагмент кода после функции filterTasks:

      main.go

      . . .
      func completeTask(text string) error {
          filter := bson.D{primitive.E{Key: "text", Value: text}}
      
          update := bson.D{primitive.E{Key: "$set", Value: bson.D{
              primitive.E{Key: "completed", Value: true},
          }}}
      
          t := &Task{}
          return collection.FindOneAndUpdate(ctx, filter, update).Decode(t)
      }
      . . .
      

      Функция совпадает с первым документом, где текстовое свойство равняется параметру text. В документе update (обновления) указано, что свойству completed (завершено) можно присвоить значение true (истина). Если в операции FindOneAndUpdate() имеется ошибка, то она будет возвращена командой completeTask(). В противном случае будет возвращено nil.

      Затем добавим новую команду done в вашу программу CLI (интерфейса командной строки), обозначающую задачу как завершенную:

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:  "tasker",
              Usage: "A simple CLI program to manage your tasks",
              Commands: []*cli.Command{
                  {
                      Name:    "add",
                      Aliases: []string{"a"},
                      Usage:   "add a task to the list",
                      Action: func(c *cli.Context) error {
                          str := c.Args().First()
                          if str == "" {
                              return errors.New("Cannot add an empty task")
                          }
      
                          task := &Task{
                              ID:        primitive.NewObjectID(),
                              CreatedAt: time.Now(),
                              UpdatedAt: time.Now(),
                              Text:      str,
                              Completed: false,
                          }
      
                          return createTask(task)
                      },
                  },
                  {
                      Name:    "all",
                      Aliases: []string{"l"},
                      Usage:   "list all tasks",
                      Action: func(c *cli.Context) error {
                          tasks, err := getAll()
                          if err != nil {
                              if err == mongo.ErrNoDocuments {
                                  fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                                  return nil
                              }
      
                              return err
                          }
      
                          printTasks(tasks)
                          return nil
                      },
                  },
                  {
                      Name:    "done",
                      Aliases: []string{"d"},
                      Usage:   "complete a task on the list",
                      Action: func(c *cli.Context) error {
                          text := c.Args().First()
                          return completeTask(text)
                      },
                  },
              },
          }
      
          err := app.Run(os.Args)
          if err != nil {
              log.Fatal(err)
          }
      }
      
      . . .
      

      Вы используете аргумент, переданный команде done для нахождения первого документа, чье свойство text совпадает. В случае обнаружения свойству completed в документе будет привоено значение true.

      Сохраните и закройте файл.

      Затем запустите программу с помощью команды done:

      • go run main.go done "Learn Go"

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

      Скриншот вывода на терминал после завершения задачи

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

      Шаг 6 — Отображение только незавершенных задач

      На этом шаге вы будете включать код для извлечения незавершенных задач из базы данных с помощью драйвера MongoDB. Незавершенные задачи — те, чьему свойству completed присвоено значение false.

      Давайте добавим новую функцию, которая извлекает задачи, которые еще не выполнены. Откройте файл main.go:

      Добавьте этот фрагмент кода после функции completeTask:

      main.go

      . . .
      func getPending() ([]*Task, error) {
          filter := bson.D{
              primitive.E{Key: "completed", Value: false},
          }
      
          return filterTasks(filter)
      }
      . . .
      

      Вы создаете фильтр при помощи пакетов bson и primitive из драйвера MongoDB, который будет сопоставлять документы, чьему свойству completed присвоено значение false. Затем вызывающему выдается список незавершенных задач.

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

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:  "tasker",
              Usage: "A simple CLI program to manage your tasks",
              Action: func(c *cli.Context) error {
                  tasks, err := getPending()
                  if err != nil {
                      if err == mongo.ErrNoDocuments {
                          fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                          return nil
                      }
      
                      return err
                  }
      
                  printTasks(tasks)
                  return nil
              },
              Commands: []*cli.Command{
                  {
                      Name:    "add",
                      Aliases: []string{"a"},
                      Usage:   "add a task to the list",
                      Action: func(c *cli.Context) error {
                          str := c.Args().First()
                          if str == "" {
                              return errors.New("Cannot add an empty task")
                          }
      
                          task := &Task{
                              ID:        primitive.NewObjectID(),
                              CreatedAt: time.Now(),
                              UpdatedAt: time.Now(),
                              Text:      str,
                              Completed: false,
                          }
      
                          return createTask(task)
                      },
                  },
      . . .
      

      Свойство Action выполняет действие по умолчанию, когда программа выполняется без каких-либо субкоманд. Здесь отображается логика перечисления незавершенных задач. Вызывается функция getPending(), и конечные задачи отображаются в стандартном выводе с помощью printTasks(). Если незавершенных задач нет, отображается подсказка, предлагающая пользователю добавить новую задачу с помощью команды add.

      Сохраните и закройте файл.

      Запуск программы сейчас без добавления каких-либо команд отобразит все незавершенные задачи в базе данных:

      Вывод должен выглядеть так:

      Output

      1: Read a book

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

      Шаг 7 — Отображение завершенных задач

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

      Откройте файл main.go:

      Добавьте следующий код в конце файла:

      main.go

      . . .
      func getFinished() ([]*Task, error) {
          filter := bson.D{
              primitive.E{Key: "completed", Value: true},
          }
      
          return filterTasks(filter)
      }
      . . .
      

      Как и в случае с функцией getPending(), вы добавили функцию getFinished(), которая возвращает список завершенных задач. В данном случае свойству completed в фильтре присвоено значение true, поэтому будут выведены только те документы, которые соответствуют этому состоянию.

      Затем создайте команду finished, выводящую все завершенные задачи:

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:  "tasker",
              Usage: "A simple CLI program to manage your tasks",
              Action: func(c *cli.Context) error {
                  tasks, err := getPending()
                  if err != nil {
                      if err == mongo.ErrNoDocuments {
                          fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                          return nil
                      }
      
                      return err
                  }
      
                  printTasks(tasks)
                  return nil
              },
              Commands: []*cli.Command{
                  {
                      Name:    "add",
                      Aliases: []string{"a"},
                      Usage:   "add a task to the list",
                      Action: func(c *cli.Context) error {
                          str := c.Args().First()
                          if str == "" {
                              return errors.New("Cannot add an empty task")
                          }
      
                          task := &Task{
                              ID:        primitive.NewObjectID(),
                              CreatedAt: time.Now(),
                              UpdatedAt: time.Now(),
                              Text:      str,
                              Completed: false,
                          }
      
                          return createTask(task)
                      },
                  },
                  {
                      Name:    "all",
                      Aliases: []string{"l"},
                      Usage:   "list all tasks",
                      Action: func(c *cli.Context) error {
                          tasks, err := getAll()
                          if err != nil {
                              if err == mongo.ErrNoDocuments {
                                  fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                                  return nil
                              }
      
                              return err
                          }
      
                          printTasks(tasks)
                          return nil
                      },
                  },
                  {
                      Name:    "done",
                      Aliases: []string{"d"},
                      Usage:   "complete a task on the list",
                      Action: func(c *cli.Context) error {
                          text := c.Args().First()
                          return completeTask(text)
                      },
                  },
                  {
                      Name:    "finished",
                      Aliases: []string{"f"},
                      Usage:   "list completed tasks",
                      Action: func(c *cli.Context) error {
                          tasks, err := getFinished()
                          if err != nil {
                              if err == mongo.ErrNoDocuments {
                                  fmt.Print("Nothing to see here.nRun `done 'task'` to complete a task")
                                  return nil
                              }
      
                              return err
                          }
      
                          printTasks(tasks)
                          return nil
                      },
                  },
              }
          }
      
          err := app.Run(os.Args)
          if err != nil {
              log.Fatal(err)
          }
      }
      . . .
      

      Команда finished извлекает задачи, чьему свойству completed присвоено значение true с помощью функции getFinished(). Затем она передает их в функцию printTasks, чтобы они отображались в стандартном выводе.

      Сохраните и закройте файл.

      Запустите следующую команду:

      Вывод должен выглядеть так:

      Output

      1: Learn Go

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

      Шаг 8 — Удаление задачи

      На этом шаге вы добавите новую субкоманду delete (удаление), чтобы пользователи могли удалять задачу из базы данных. Для удаления одной задачи вы будете использовать метод collection.DeleteOne() из драйвера MongoDB. Также он использует фильтр для сопоставления удаляемого документа.

      Откройте файл main.go еще раз:

      Добавьте функцию deleteTask для удаления задач из базы данных непосредственно после функции getFinished:

      main.go

      . . .
      func deleteTask(text string) error {
          filter := bson.D{primitive.E{Key: "text", Value: text}}
      
          res, err := collection.DeleteOne(ctx, filter)
          if err != nil {
              return err
          }
      
          if res.DeletedCount == 0 {
              return errors.New("No tasks were deleted")
          }
      
          return nil
      }
      . . .
      

      В этом методе deleteTask имеется строковый аргумент, представляющий удаляемую задачу. Создается фильтр для сопоставления задачи, чьему свойству text присвоен строковый аргумент. Вы передаете фильтр в метод DeleteOne(), который сопоставляет элемент коллекции и удаляет его.

      Вы можете проверить свойство DeletedCount по результату метода DeleteOne, чтобы подтвердить, были ли удалены какие-либо документы. Если фильтр не сможет сопоставить удаляемый документ, то DeletedCount будет равно нулю, и вы сможете вывести ошибку.

      Теперь добавьте новую команду rm, как выделено:

      main.go

      . . .
      func main() {
          app := &cli.App{
              Name:  "tasker",
              Usage: "A simple CLI program to manage your tasks",
              Action: func(c *cli.Context) error {
                  tasks, err := getPending()
                  if err != nil {
                      if err == mongo.ErrNoDocuments {
                          fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                          return nil
                      }
      
                      return err
                  }
      
                  printTasks(tasks)
                  return nil
              },
              Commands: []*cli.Command{
                  {
                      Name:    "add",
                      Aliases: []string{"a"},
                      Usage:   "add a task to the list",
                      Action: func(c *cli.Context) error {
                          str := c.Args().First()
                          if str == "" {
                              return errors.New("Cannot add an empty task")
                          }
      
                          task := &Task{
                              ID:        primitive.NewObjectID(),
                              CreatedAt: time.Now(),
                              UpdatedAt: time.Now(),
                              Text:      str,
                              Completed: false,
                          }
      
                          return createTask(task)
                      },
                  },
                  {
                      Name:    "all",
                      Aliases: []string{"l"},
                      Usage:   "list all tasks",
                      Action: func(c *cli.Context) error {
                          tasks, err := getAll()
                          if err != nil {
                              if err == mongo.ErrNoDocuments {
                                  fmt.Print("Nothing to see here.nRun `add 'task'` to add a task")
                                  return nil
                              }
      
                              return err
                          }
      
                          printTasks(tasks)
                          return nil
                      },
                  },
                  {
                      Name:    "done",
                      Aliases: []string{"d"},
                      Usage:   "complete a task on the list",
                      Action: func(c *cli.Context) error {
                          text := c.Args().First()
                          return completeTask(text)
                      },
                  },
                  {
                      Name:    "finished",
                      Aliases: []string{"f"},
                      Usage:   "list completed tasks",
                      Action: func(c *cli.Context) error {
                          tasks, err := getFinished()
                          if err != nil {
                              if err == mongo.ErrNoDocuments {
                                  fmt.Print("Nothing to see here.nRun `done 'task'` to complete a task")
                                  return nil
                              }
      
                              return err
                          }
      
                          printTasks(tasks)
                          return nil
                      },
                  },
                  {
                      Name:  "rm",
                      Usage: "deletes a task on the list",
                      Action: func(c *cli.Context) error {
                          text := c.Args().First()
                          err := deleteTask(text)
                          if err != nil {
                              return err
                          }
      
                          return nil
                      },
                  },
              }
          }
      
          err := app.Run(os.Args)
          if err != nil {
              log.Fatal(err)
          }
      }
      . . .
      

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

      Сохраните и закройте файл.

      Вы можете указывать незавершенные задачи, запустив программу без передачи субкоманд:

      Output

      1: Read a book

      Запуск субкоманды rm в задаче «Читать книгу» удалит ее из базы данных:

      • go run main.go rm "Read a book"

      Если вы снова укажете все незавершенные задачи, то увидите, что задача «Читать книгу» больше не отображается, а вместо этого отображается подсказка, предлагающая создать новую задачу:

      Output

      Nothing to see here Run `add 'task'` to add a task

      На этом шаге вы добавили функцию для удаления задач из базы данных.

      Заключение

      Вы успешно создали программу «диспетчер задач» интерфейса командной строки и заодно научились основам применения драйвера MongoDB Go.

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

      Окончательный код этого обучающего руководства можно найти в репозитории GitHub.



      Source link

      Использование типа данных MySQL BLOB для хранения изображений с PHP в Ubuntu 18.04


      Автор выбрал Girls Who Code для получения пожертвования в рамках программы Write for DOnations.

      Введение

      Большие двоичные объекты (BLOB) — это тип данных MySQL для хранения двоичных данных, таких как изображения, мультимедийные файлы и файлы PDF.

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

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

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

      В этом обучающем модуле мы используем тип данных MySQL BLOB для сохранения изображений в коде PHP в Ubuntu 18.04.

      Предварительные требования

      Для выполнения этого обучающего модуля вам потребуется следующее:

      Шаг 1 — Создание базы данных

      Для начала создайте тестовую базу данных для своего проекта. Для этого подключитесь к серверу через SSH и запустите следующую команду для входа на сервер MySQL с привилегиями root:

      Введите пароль пользователя root для вашей базы данных MySQL и нажмите ENTER, чтобы продолжить.

      Затем запустите следующую команду для создания базы данных. В этом обучающем модуле мы назовем базу данных test_company:

      • CREATE DATABASE test_company;

      После создания базы данных вы увидите следующее:

      Output

      Query OK, 1 row affected (0.01 sec)

      Затем создайте учетную запись test_user на сервере MySQL и обязательно замените PASSWORD на надежный пароль:

      • CREATE USER 'test_user'@'localhost' IDENTIFIED BY 'PASSWORD';

      Вывод должен выглядеть так:

      Output

      Query OK, 0 rows affected (0.01 sec)

      Чтобы предоставить пользователю test_user полные права доступа к базе данных test_company, выполните команду:

      • GRANT ALL PRIVILEGES ON test_company.* TO 'test_user'@'localhost';

      Вы должны увидеть следующее:

      Output

      Query OK, 0 rows affected (0.01 sec)

      Очистите таблицу прав доступа, чтобы СУБД MySQL перезагрузила разрешения:

      На экран должно быть выведено следующее:

      Output

      Query OK, 0 rows affected (0.01 sec)

      Мы подготовили базу данных test_company и пользователя test_user. Теперь мы можем перейти к созданию таблицы products для сохранения образцов продуктов. Позднее мы используем эту таблицу для вставки и получения записей, чтобы продемонстрировать работу объектов MySQL BLOB.

      Выполните выход из сервера MySQL:

      Снова войдите в систему под учетными данными созданного пользователя test_user:

      Введите в диалоге пароль пользователя test_user и нажмите ENTER, чтобы продолжить. Переключитесь на базу данных test_company с помощью следующей команды:

      После выбора базы данных test_company MySQL выведет на экран следующее:

      Output

      Database changed

      Затем создайте таблицу products, запустив команду:

      • CREATE TABLE `products` (product_id BIGINT PRIMARY KEY AUTO_INCREMENT, product_name VARCHAR(50), price DOUBLE, product_image BLOB) ENGINE = InnoDB;

      Эта команда создает таблицу с именем products. В таблице имеется четыре столбца:

      • product_id: в этом столбце используется тип данных BIGINT, за счет чего обеспечивается поддержка больших списков продуктов, где может содержаться до 2⁶³-1 элементов. Вы пометили столбец как PRIMARY KEY для уникальной идентификации продуктов. Чтобы MySQL обрабатывала генерирование новых идентификаторов для вставленных столбцов, вы использовали ключевое слово AUTO_INCREMENT.

      • product_name: в этом столбце хранятся названия продуктов. Мы использовали тип данных VARCHAR, поскольку это поле обычно обрабатывает не более 50 бувенно-числовых символов. Предел в 50 символов — это гипотетическое значение, используемое для целей этого обучающего модуля.

      • price: для демонстрационных целей мы добавили в таблицу products столбец price для хранения информации о розничной цене продуктов. Поскольку некоторые продукты могут иметь значения с плавающей запятой (например, 23.69, 45.36, 102.99), мы использовали тип данных DOUBLE.

      • product_image: в этом столбце данные типа BLOB используются для хранения двоичных данных изображений продуктов.

      Мы использовали для таблицы InnoDB, СИСТЕМУ ХРАНЕНИЯ, поддерживающую широкий набор функций, включая транзакции MySQL. После выполнения этой команды для создания таблицы products вы увидите на экране следующее:

      Output

      Query OK, 0 rows affected (0.03 sec)

      Выполните выход с сервера MySQL:

      Вы увидите следующее

      Output

      Bye

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

      Шаг 2 — Создание скриптов PHP для подключения к базе данных и ее заполнения

      На этом шаге мы создадим скрипт PHP для подключения к базе данных MySQL, созданной нами на шаге 1. Этот скрипт подготовит три образца продуктов и вставит их в таблицу products.

      Для создания кода PHP откройте новый файл в текстовом редакторе:

      • sudo nano /var/www/html/config.php

      Введите в файл следующую информацию и замените PASSWORD паролем пользователя test_user, созданным на шаге 1:

      /var/www/html/config.php

      <?php
      
      define('DB_NAME', 'test_company');
      define('DB_USER', 'test_user');
      define('DB_PASSWORD', 'PASSWORD');
      define('DB_HOST', 'localhost');
      
      $pdo = new PDO("mysql:host=" . DB_HOST . "; dbname=" . DB_NAME, DB_USER, DB_PASSWORD);
      $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
      $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
      
      

      Сохраните и закройте файл.

      В этом файле мы использовали четыре константы PHP для подключения к базе данных MySQL, созданной на шаге 1:

      • DB_NAME : эта константа хранит название базы данных test_company.

      • DB_USER : эта переменная хранит имя пользователя test_user.

      • DB_PASSWORD : эта константа хранит ПАРОЛЬ MySQL для учетной записи test_user.

      • DB_HOST: сервер, где располагается база данных. В данном случае мы используем сервер localhost.

      Следующая строка в вашем файле инициирует объект данных PHP (PDO) и выполняет подключение к базе данных MySQL:

      ...
      $pdo = new PDO("mysql:host=" . DB_HOST . "; dbname=" . DB_NAME, DB_USER, DB_PASSWORD);
      ...
      

      Ближе к концу файла мы зададим пару атрибутов PDO:

      • ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION: этот атрибут предписывает PDO выдать исключение, которое можно зарегистрировать для целей отладки.
      • ATTR_EMULATE_PREPARES, false: эта опция повышает безопасности, предписывая СУБД MySQL выполнять подготовку вместо PDO.

      Мы добавим файл /var/www/html/config.php в два скрипта PHP для вставки и получения записей, которые мы сейчас создадим.

      Вначале создайте скрипт PHP /var/www/html/insert_products.php для вставки записей в таблицу products:

      • sudo nano /var/www/html/insert_products.php

      Затем добавьте следующую информацию в файл /var/www/html/insert_products.php:

      /var/www/html/insert_products.php

      <?php
      
      require_once 'config.php';
      
      $products = [];
      
      $products[] = [
                    'product_name' => 'VIRTUAL SERVERS',
                    'price' => 5,
                    'product_image' => file_get_contents("https://i.imgur.com/VEIKbp0.png")
                    ];
      
      $products[] = [
                    'product_name' => 'MANAGED KUBERNETES',
                    'price' => 30,
                    'product_image' => file_get_contents("https://i.imgur.com/cCc9Gw9.png")
                    ];
      
      $products[] = [
                    'product_name' => 'MySQL DATABASES',
                    'price' => 15,
                    'product_image' => file_get_contents("https://i.imgur.com/UYcHkKD.png" )
                    ];
      
      $sql = "INSERT INTO products(product_name, price, product_image) VALUES (:product_name, :price, :product_image)";
      
      foreach ($products as $product) {
          $stmt = $pdo->prepare($sql);
          $stmt->execute($product);
      }
      
      echo "Records inserted successfully";
      

      Сохраните и закройте файл.

      Вы добавили файл config.php в верхней части файла. Это первый файл, который вы создали для определения переменных базы данных и подключения к базе данных. Также этот файл инициирует объект PDO и сохраняет его в переменной $pdo.

      Далее вы создали массив данных о продуктах, которые нужно вставить в базу данных. Помимо значений product_name и price, подготовленных в виде текстовых и числовых значений соответственно, данный скрипт использует встроенную функцию PHP file_get_contents для чтения изображений из внешнего источника и передачи их в виде строк в столбец product_image.

      Затем вы подготовили выражение SQL и использовали выражение PHP foreach{...} для вставки каждого продукта в базу данных.

      Для выполнения файла /var/www/html/insert_products.php запустите его в окне браузера, используя следующий URL. Замените your-server-IP публичным IP-адресом вашего сервера:

      http://your-server-IP/insert_products.php
      

      После выполнения файла вы получите в браузере сообщение, подтверждающее успешную вставку записей в базу данных.

      Сообщение об успешной вставке записей в базу данных

      Вы успешно вставили три записи с изображениями продуктов в таблицу products. На следующем шаге мы создадим скрипт PHP для извлечения этих записей и их вывода в браузере.

      Шаг 3 — Отображение информации о продуктах из базы данных MySQL

      Мы разместили в базе данных информацию о продуктах и их ихображения. Теперь мы создадим еще один скрипт PHP, который будет запрашивать и выводить информацию о продуктах в таблице HTML в браузере.

      Для создания файла введите следующее:

      • sudo nano /var/www/html/display_products.php

      Затем введите в файл следующую информацию:

      /var/www/html/display_products.php

      <html>
        <title>Using BLOB and MySQL</title>
        <body>
      
        <?php
      
        require_once 'config.php';
      
        $sql = "SELECT * FROM products";
        $stmt = $pdo->prepare($sql);
        $stmt->execute();
        ?>
      
        <table border="1" align = 'center'> <caption>Products Database</caption>
          <tr>
            <th>Product Id</th>
            <th>Product Name</th>
            <th>Price</th>
            <th>Product Image</th>
          </tr>
      
        <?php
        while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
            echo '<tr>';
            echo '<td>' . $row['product_id'] . '</td>';
            echo '<td>' . $row['product_name'] . '</td>';
            echo '<td>' . $row['price'] . '</td>';
            echo '<td>' .
            '<img src = "data:image/png;base64,' . base64_encode($row['product_image']) . '" width = "50px" height = "50px"/>'
            . '</td>';
            echo '</tr>';
        }
        ?>
      
        </table>
        </body>
      </html>
      

      Сохраните изменения в файле и закройте его.

      Здесь мы опять использовали файл config.php для подключения к базе данных. Затем мы подготовили и выполнили команду SQL, используя PDO для извлечения всех элементов из таблицы products с помощью команды SELECT * FROM products.

      Затем мы создали таблицу HTML и заполнили ее данными о продуктах, используя выражение PHP while() {...}. Строка $row = $stmt->fetch(PDO::FETCH_ASSOC) отправляет запрос в базу данных и сохраняет результат в переменной $row как многомерный массив, который затем отображается как столбец таблицы HTML с использованием синтаксиса $row['column_name'].

      Изображения из столбца product_image заключены в теги <img src = "">. Мы использовали атрибуты width и height для уменьшения ширины и высоты изображений, чтобы они поместились в столбце таблицы HTML.

      Чтобы конвертировать данные, хранящиеся в объекте типа BLOB, обратно в изображения, мы использовали встроенную функцию PHP base64_encode и следующий синтаксис схемы URI данных:

      data:media_type;base64, base_64_encoded_data
      

      В данном случае image/png — это значение параметра media_type (тип файла), а закодированная строка Base64 из столбца product_image — это данные base_64_encoded_data.

      Выполните в браузере файл display_products.php, введя следующий адрес:

      http://your-server-IP/display_products.php
      

      Запустив в браузере файл display_products.php, вы увидите таблицу HTML со списком продуктов и связанных с ними изображений.

      Список продуктов из базы данных MySQL

      Это подтверждает, что скрипт PHP для извлечения изображений из базы данных MySQL работает ожидаемым образом.

      Заключение

      В этом обучающем модуле мы использовали тип данных MySQL BLOB для хранения и вывода изображений в коде PHP в Ubuntu 18.04. Мы увидели основные преимущества хранения изображений в базе данных по сравнению с их хранением в файловой системе. В число этих преимуществ входят портативность, безопасность и удобство резервного копирования. Эта технология будет вам полезна, если вы создаете приложение, для которого требуется хранить информацию и изображения вместе, например, студенческий портал или базу данных сотрудников.

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



      Source link