Това, което следва, е опитът ми да заменя PHP с Javascript в разработката на WordPress.

Част 2 — Настройката
Част 1 — Въведението

Актуализация: Откакто публикувах тази статия, създадох NPM пакет, който се грижи за свързването с базата данни на WordPress и дефинирането на Sequelize модели. „Проверете го“ в GitHub или „прочетете за това в това въведение“. Все още трябва да прочетете, за да видите как работи, но знайте, че го направих много по-лесно за вас!

Съобщение: Стартирах WordExpress.io, който ще съдържа по-подробна документация за този проект, докато се развива. В момента то просто ще възпроизведе тези средни публикации.

В публикацията ще обсъдя подробно как работи схемата GraphQL във връзка с WordPress и Apollo от страна на сървъра (ще навляза в страната на клиента в друга публикация). Ще използвам Sequelize, за да се свържа с базата данни на WordPress, така че няма да се налага да пишем никакви MYSQL заявки. Публикацията ще бъде дълга, така че вземете Fresca и нека се заемем с нея.

Използване на Sequelize за дефиниране на връзка и модели

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

За да улесня живота си, ще използвам Sequelize. 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. Безплатно е и помага много при изграждането на модели и схеми.

Определяне на връзки

Ето дефиницията на модела Postmeta. Публикация има много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/belongsTo. Сега можем да извършим заявка за Post и да включим всичките му 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]. [Публикуване] означава, че входната точка за публикации очаква да върне списък с публикации, които са друг тип 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), обсъдени по-горе, са част от конекторите. След това създаваме обект resolveFunctions, който съдържа всички функции, които разрешават неща. Важното, което трябва да се отбележи тук, е, че за всяко поле в нашата GraphQL схема, което е от неизброим тип (т.е. Post, Postmeta, Menu и т.н.), от нас се изисква да имаме разрешаваща функция. Най-добрият пример за това е типът Query. Не забравяйте, че в нашата схема на заявка имахме 5 полета — Настройки, Публикации, Страница, Меню и Postmeta. Забележете във функциите за разрешаване по-горе, че всяко едно от тези полета има функция, която извиква конкретен конектор (с изключение на Настройки, тъй като тази информация не се съхранява в база данни).

И така, на клиента (който ще разгледам в друга публикация), когато отида, да речем, на страницата „Блог“, изобразява компонент PostList, който прави заявки за публикации. Когато тази заявка се изпълнява, тя извиква функцията за разрешаване на Query:Posts, която след това извиква Connectors.getPosts(), която от своя страна изпълнява MySQL заявка (благодарение на Sequelize), която връща всичките ни публикации. Можете да видите по-долу как работи с GraphiQL.

Чудесното на GraphiQL е, че той автоматично довършва вместо вас. Така че, ако започнете с празен лист, можете бързо да стигнете до мястото, където искате да бъдете. Поиграйте си с него!

Следващи стъпки

Проектът WordExpress има малко по-сложна схема от показаната тук. Призовавам ви да „клонирате репото“ и да си играете с него сами. Актуализирам документация в GitHub доста редовно. Ако имате нужда от помощ, изпратете проблем. Ако искате да допринесете, изпратете заявка за изтегляне!

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

Ramsay Lanier е старши разработчик в nclud, провокативна дигитална агенция във Вашингтон, окръг Колумбия. Можете да го намерите в twitter, github или в &pizza.