Далее следует моя попытка заменить PHP на Javascript при разработке WordPress.

Часть 2 - Установка
Часть 1 - Введение

Обновление: с момента публикации этой статьи я создал пакет NPM, который обеспечивает подключение к базе данных WordPress и определение моделей Sequelize. Проверьте это на GitHub или прочтите об этом во введении. Вы все равно должны прочитать, чтобы увидеть, как это работает, но знайте, что я сделал это намного проще для вас!

Объявление: я запустил WordExpress.io, который будет содержать более подробную документацию об этом проекте по мере его развертывания. Сейчас он просто копирует эти сообщения на Medium.

В этом посте я подробно расскажу, как схема GraphQL работает вместе с WordPress и Apollo на стороне сервера (я расскажу о стороне клиента в другом посте). Я буду использовать Sequelize для подключения к базе данных WordPress, поэтому нам не придется писать какие-либо запросы MYSQL. Это будет длинный пост, так что берите Fresca, и давайте приступим.

Использование Sequelize для определения соединения и моделей

Как обсуждалось в предыдущем посте, ApolloServer требует двух вещей: схемы и преобразователей. Резолверы - это просто функции, которые отправляются и получают данные откуда-то и возвращают их в форме, соответствующей вашей схеме GraphQL. Чтобы создать преобразователи, нам нужно создать коннекторы. Коннекторы используются для подключения резолверов к бэкэнду, в котором хранятся фактические данные. В данном случае я создаю коннекторы, которые будут подключаться к моей базе данных WordPress MySQL.

Чтобы упростить жизнь, я буду использовать Sequelize. Сиквелиз довольно хорош. На их веб-сайте «Sequelize - это ORM на основе обещаний для Node.js и io.js. Он поддерживает диалекты PostgreSQL, MySQL, MariaDB, SQLite и MSSQL и обеспечивает надежную поддержку транзакций, отношений, репликации чтения и многое другое ».

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

Настройка подключения

// ./schema/connectors.js 
import { publicSettings, privateSettings } from '../settings/settings';
import WordExpressDatabase from './db';
const { name, username, password, host } = privateSettings.database;
const { amazonS3, uploads } = publicSettings;
const connectionDetails = {
  name: name,
  username: username,
  password: password,
  host: host,
  amazonS3: amazonS3,
  uploadDirectory: uploads
}
const Database = new WordExpressDatabase(connectionDetails);
const Connectors = Database.connectors;
export default Connectors;

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

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

Связь

// ./schema/db.js
//...
const { name, username, password, host} = this.connectionDetails;
const Conn = new Sequelize(
  name,
  username,
  password,
  {
    dialect: ‘mysql’,
    host: host,
    define: {
      timestamps: false,
      freezeTableName: true,
    }
  }
);
//...

Мы передаем connectionDetails, когда создаем новый экземпляр WordExpressDatabase в файле ./schema/connection.js. Он содержит все детали базы данных, содержащиеся в наших общедоступных / частных файлах настроек. Мы будем использовать это соединение при определении моделей данных.

Модели данных

Давайте посмотрим на модель Post в качестве примера.

// ./schema/db.js
//...
const Conn = this.connection;
return {
Post: Conn.define(prefix + 'posts', {
  id: { type: Sequelize.INTEGER, primaryKey: true},
  post_author: { type: Sequelize.INTEGER },
  post_title: { type: Sequelize.STRING },
  post_content: { type: Sequelize.STRING },
  post_excerpt: { type: Sequelize.STRING },
  post_status:{ type: Sequelize.STRING },
  post_type:{ type: Sequelize.STRING },
  post_name:{ type: Sequelize.STRING},
  post_parent: { type: Sequelize.INTEGER},
  menu_order: { type: Sequelize.INTEGER}
}),
//...

Здесь я определяю модель публикации, используя ключи объектов, которые отображаются непосредственно в таблице сообщений в базе данных WordPress. Обратите внимание, что я использую wp_prefix из личных настроек. По умолчанию Wordpress использует префикс «wp_», но вы можете его изменить, и люди часто это делают из соображений безопасности. В своей настройке я использую расширение по умолчанию, поэтому оно переводится как «wp_posts».

Каждый ключ должен иметь тип Sequelize - для WordPress это всегда либо строки, либо целые числа. Обратите внимание, что для ключа «id» мы также устанавливаем его как первичный ключ. Это важно для построения отношений при выполнении запросов (о чем я вскоре расскажу).

Вам не нужно включать КАЖДЫЙ столбец в таблицу SQL, из которой вы определяете свою модель. В самом деле, многие столбцы нам не нужны во внешнем интерфейсе.

Примечание: если у вас нет программного обеспечения для управления SQL, попробуйте Sequel Pro. Это бесплатно и очень помогает при построении моделей и схем.

Определение отношений

Вот определение модели Постмета. Сообщение hasmany Postmeta и Postmeta принадлежит к сообщению. Во-первых, давайте определим модель Postmeta. Затем давайте определимся с отношениями. Sequelize делает это ОЧЕНЬ легким.

// ./schema/db.js
return{
...
Postmeta: Conn.define(prefix + 'postmeta', {
  meta_id: { type: Sequelize.INTEGER, primaryKey: true, field: 'meta_id' },
  post_id: { type: Sequelize.INTEGER },
  meta_key: { type: Sequelize.STRING },
  meta_value: { type: Sequelize.INTEGER },
}),
...
Post.hasMany(Postmeta, {foreignKey: ‘post_id’});
Postmeta.belongsTo(Post, {foreignKey: ‘post_id’});

Как видите, модель Postmeta имеет поле post_id, которое является первичным ключом для модели Post. Мы можем связать эти два понятия, определив отношение hasMany / ownTo. Теперь мы можем выполнить запрос для сообщения и включить все его Postmeta.

Определение запросов к базе данных

Итак, не то, чтобы у нас была базовая настройка модели Post, нам понадобятся некоторые функции, которые запрос GraphQL может разрешить. Эти функции также определены в файле db.js и состоят из запросов Sequelize. Давайте посмотрим на основную функцию getPostByName.

// ./schema/db.js
getConnectors(){
...
return{
  getPostByName(name){
    return Post.findOne({
      where: {
        post_status: 'publish',
        post_name: name
      }
    })
  },
}
...

getPostByName принимает имя сообщения (также называемое его слагом) и находит опубликованное. Это вернет Promise в наш запрос GraphQL, и это здорово, потому что GraphQL знает, как обрабатывать обещания. Обратите внимание, мы только находим его. Давайте создадим запрос, который найдет все сообщения по типу сообщения.

// .schema/db.js
getPosts(args){
  
  const { post_type, limit = 10, skip = 0 } = args;
  return Post.findAll({
    where: {
      post_type: post_type,
      post_status: 'publish',
    },
    limit: limit,
    offset: skip
  })
},

Единственная разница здесь в том, что мы используем findAll, который по-прежнему будет возвращать обещание, но возвращает массив обещаний, что является важным отличием, которое будет иметь значение позже. Итак, мы определили Post и Postmeta - теперь давайте посмотрим на GraphQL. Без GraphQL / Apollo у нас не будет простого способа запрашивать эти данные со стороны клиента.

Определение схемы GraphQL

Мы собираемся использовать здесь подход снизу вверх. Чтобы определить схему, нам нужно определить корневой запрос, который содержит другие поля, которые необходимо определить. Но это самый простой подход. Для справки, вся схема GraphQL, написанная на языке типов GraphQL, находится в файле ./schema/typeDefinitions.js.

Вот определение схемы:

schema {
  query: Query
}

Все просто, правда? Ага.

Определение корневого запроса

Корневой запрос выглядит так:

type Query {
  settings: Setting
  posts(post_type: String = "post", limit: Int, skip: Int): [Post]
  menus(name: String): Menu
  page(name: String): Post
  postmeta(post_id: Int, after: String, first: Int, before: String, last: Int): Postmeta
}

Корневой запрос предоставляет несколько «точек входа». Точки входа могут принимать аргументы. Например, точка входа posts принимает аргумент post_type типа string. Если аргумент не указан, по умолчанию используется «post» . Мы также можем ограничить количество возвращаемых сообщений и пропустить определенное количество сообщений. Это полезно для нумерации страниц. Также обратите внимание, что в конце объявления точки входа в сообщения есть [Post]. [Post] означает, что точка входа posts ожидает возврата списка сообщений, которые относятся к другому типу GraphQL.

Тип сообщения выглядит так:

type Post {
    id: Int
    post_title: String
    post_content: String
    post_excerpt: String
    post_status: String
    post_type: String
    post_name: String
    menu_order: Int
    layout: Postmeta
    thumbnail: String
    post_meta(keys: [MetaType], after: String, first: Int, before: String, last: Int): Postmeta
  }

Это должно выглядеть знакомо, так как содержит все поля (плюс несколько дополнительных) из модели Post, определенной выше в файле db.js. Дополнительные поля (layout и post_meta) не являются таблицами в базе данных MySQL. Вместо этого они относятся к типу Postmeta GraphQL.

Все это имеет смысл, если взглянуть на всю схему GraphQL.

Определение резольверов

Вспомните, когда я сказал, что ApolloServer принимает два очень важных аргумента - схему и преобразователи. Мы рассмотрели схему, которая представляет собой все в файле typeDefinitions.js. Теперь нам нужно обсудить резолверы.

Весь файл resolveFunctions.js довольно маленький. Это выглядит так:

Сначала мы импортируем коннекторы. Помните, что getPostsByName (name) и getPosts (post_type), описанные выше, являются частью Connectors. Затем мы создаем объект resolveFunctions, который содержит все функции, которые разрешают данные. Здесь важно отметить, что для каждого поля в нашей схеме GraphQL, которое имеет неперечислимый тип (например, Post, Postmeta, Menu и т. Д.), Мы должны иметь разрешающую функцию. Лучшим примером этого является тип запроса. Помните, что в нашей схеме запроса у нас было 5 полей - «Настройки», «Сообщения», «Страница», «Меню» и «Postmeta». Обратите внимание, что в приведенных выше функциях разрешения каждое из этих полей имеет функцию, вызывающую определенный коннектор (за исключением настроек, поскольку эта информация не хранится в базе данных).

Итак, на клиенте (о котором я расскажу в другом сообщении), когда я перехожу, скажем, на страницу «Блог», компонент PostList выполняет рендеринг, который запрашивает сообщения. Когда этот запрос выполняется, он вызывает функцию разрешения Query: Posts, которая затем вызывает Connectors.getPosts (), которая, в свою очередь, запускает запрос MySQL (благодаря Sequelize), который возвращает все наши сообщения. Ниже вы можете увидеть, как это работает с использованием GraphiQL.

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

Следующие шаги

Проект WordExpress имеет немного более сложную схему, чем показано здесь. Я призываю вас клонировать репо и поиграть с ним самостоятельно. Я довольно регулярно обновляю документацию на GitHub. Если вам нужна помощь, сообщите о проблеме. Если вы хотите внести свой вклад, отправьте запрос на перенос!

В следующей статье я покажу вам, как работает маршрутизация, и мы рассмотрим, как была создана целевая страница WordExpress.io с использованием нашей новой блестящей реализации GraphQL / Apollo.

Рамзи Ланье - старший разработчик nclud, провокационного цифрового агентства в Вашингтоне, округ Колумбия. Вы можете найти его в twitter, github или & pizza.