Implementing a GraphQL server with components in PHP

Implementing a GraphQL server with components in PHP

Some of the links in this post are affiliate links. This means if you click on the link and purchase the item, We will receive an affiliate commission at no extra cost to you. All opinions remain our own.

GraphQL is a question language for APIs, which provides purchasers the ability to ask for precisely what information they want and obtain precisely that, nothing roughly. In this method, a single question can already fetch all of the required information to render an element.

(A REST API, in comparison, should set off several roundtrips to fetch information from several sources from completely different endpoints, which may develop into very sluggish, notably on cell.)

Even although GraphQL (which suggests “Graph Query Language”) makes use of a graph information model to characterize information, the GraphQL server doesn't essentially want to make use of a graph as the information construction to resolve the question, however, can use any information construction that wishes. The graph is solely a psychological model, not a precise implementation.

This is endorsed by the GraphQL mission by stating on its website graphql.org (emphasis mine):

Graphs are highly effective instruments for modeling many real-world phenomena as a result of they resemble our pure psychological models and verbal descriptions of the underlying process. With GraphQL, you model your business area as a graph by defining a schema; inside your schema, you outline differing types of nodes and the way they join/relate to at least one one other. On the shopper, this creates a sample just like Object-Oriented Programming: varieties that reference different varieties. On the server, since GraphQL solely defines the interface, you might have the liberty to make use of it with any backend (new or legacy!).

This is excellent news, as a result of coping with both graphs or timber (which are subsets of graphs) is not trivial and may result in an exponential or logarithmic time complexity for resolving the question (i.e. the time required to resolve a question might enhance some orders of magnitude for every new entry to the question).

In this text, I will describe the architectural design of the GraphQL server in PHP GraphQL by PoP, which makes use of parts as an information construction as an alternative to graphs. This server owes its title to PoP, the library to construct parts in PHP over which it is based mostly. (I'm the writer of each task.)

This article is divided into 5 sections, explaining:

  1. What is a component
  2. How PoP works
  3. How components are defined in PoP
  4. How components are naturally suitable for GraphQL
  5. The performance of using components to resolve a GraphQL query

Let’s start.

1. What is an element

The structure of each webpage may be represented utilizing parts. An element is merely a set of items of code (akin to HTML, JavaScript, and CSS) put all collectively to create an autonomous entity, which may wrap different parts to create extra advanced constructions, and be wrapped by different parts too. Every element has a function, which may vary from one thing very primary, akin to a hyperlink or a button, to one thing very elaborate, akin to a carousel or a drag-and-drop image uploader.

Building a site by parts is akin to enjoying with LEGO. For occasion, in the webpage from the image under, easy parts (hyperlinks, buttons, avatars) are composed to create extra advanced constructions (widgets, sections, sidebars, menus) all how as much as the highest, till we receive the webpage:

The page is an element wrapping parts, as proven by the squares

Components may be applied each for the client-side (akin to JS libraries Vue and React, or CSS element libraries Bootstrap and Material-UI) and for the server-side, in any language.

2. How PoP works

PoP describes a structure based mostly on a server-side element model and implements it in PHP by the component-model library.

In the sections under, the phrases “component” and “module” are used interchangeably.

The element hierarchy

The relationship of all modules wrapping one another, from the top-most module all how all the way down to the final stage, is referred to as the element hierarchy. This relationship may be expressed by an associative array (an array of key => property) on the server-side, in which every module states its title as the important thing attribute and its interior modules underneath the property "modules".

The information in the PHP array may be straight used on the client-side too, encoded as a JSON object.

The element hierarchy seems to be like this:

$componentHierarchy = [
  'module-level0' => [
    "modules" => [
      'module-level1' => [
        "modules" => [
          'module-level11' => [
            "modules" => [...]
          ],
          'module-level12' => [
            "modules" => [
              'module-level121' => [
                "modules" => [...]
              ]
            ]
          ]
        ]
      ],
      'module-level2' => [
        "modules" => [
          'module-level21' => [
            "modules" => [...]
          ]
        ]
      ]
    ]
  ]
]

The relationship amongst modules is outlined in a strictly top-down style: a module wraps different modules and is aware of who they are, but it surely doesn’t know and doesn’t care, which modules are wrapping him.

For occasion, in the element hierarchy above, module 'module-level1' is aware of it wraps modules 'module-level11' and 'module-level12', and, transitively, it additionally is aware of its wraps 'module-level121'; however module 'module-level11' doesn’t care who is wrapping him, consequently is unaware of 'module-level1'.

Having the component-based construction, we add the precise info required by every module, which is categorized into both settings (akin to configuration values and different properties) and information (such because the IDs of the queried database objects and different properties), and positioned accordingly underneath entries modulesettings and moduledata:

$componentHierarchyData = [
  "modulesettings" => [
    'module-level0' => [
      "configuration" => [...],
      ...,
      "modules" => [
        'module-level1' => [
          "configuration" => [...],
          ...,
          "modules" => [
            'module-level11' => [
              ...children...
            ],
            'module-level12' => [
              "configuration" => [...],
              ...,
              "modules" => [
                'module-level121' => [
                  ...children...
                ]
              ]
            ]
          ]
        ],
        'module-level2' => [
          "configuration" => [...],
          ...,
          "modules" => [
            'module-level21' => [
              ...children...
            ]
          ]
        ]
      ]
    ]
  ],
  "moduledata" => [
    'module-level0' => [
      "dbobjectids" => [...],
      ...,
      "modules" => [
        'module-level1' => [
          "dbobjectids" => [...],
          ...,
          "modules" => [
            'module-level11' => [
              ...children...
            ],
            'module-level12' => [
              "dbobjectids" => [...],
              ...,
              "modules" => [
                'module-level121' => [
                  ...children...
                ]
              ]
            ]
          ]
        ],
        'module-level2' => [
          "dbobjectids" => [...],
          ...,
          "modules" => [
            'module-level21' => [
              ...children...
            ]
          ]
        ]
      ]
    ]
  ]
]

Next, the database object information is added to the element hierarchy. This info is not positioned underneath every module, however, underneath a shared part referred to as databases, to keep away from duplicating info when 2 or extra completely different modules fetch the identical objects from the database.

In addition, the library represents the database object information in a relational method, to keep away from duplicating info when 2 or extra completely different database objects are associated with a widespread object (akin to 2 posts having the identical writer).

In different phrases, database object information is normalized. The construction is a dictionary, organized underneath every object sort first and object ID second, from which we can receive the article properties:

$componentHierarchyData = [
  ...
  "databases" => [
    "dbobject_type" => [
      "dbobject_id" => [
        "property" => ...,
        ...
      ],
      ...
    ],
    ...
  ]
]

For occasion, the article under accommodates an element hierarchy with two modules, "page" => "post-feed", the place module "post-feed" fetches blog posts. Please discover the next:

  • Each module is aware of which are its queried objects from the property dbobjectids (IDs 4 and 9 for the blog posts)
  • Each module is aware of the article sort for its queried objects from the property dbkeys (every put up’s information is discovered underneath "posts", and the put up’s writer information, equivalent to the writer with the ID given underneath the put up’s property "author", is discovered underneath "users"):
  • Because the database object information is relational, property "author" accommodates the ID to the writer object as an alternative to printing the writer information straight
$componentHierarchyData = [
  "moduledata" => [
    'page' => [
      "modules" => [
        'post-feed' => [
          "dbobjectids": [4, 9]
        ]
      ]
    ]
  ],
  "modulesettings" => [
    'page' => [
      "modules" => [
        'post-feed' => [
          "dbkeys" => [
            'id' => "posts",
            'author' => "users"
          ]
        ]
      ]
    ]
  ],
  "databases" => [
    'posts' => [
      4 => [
        'title' => "Hello World!",
        'author' => 7
      ],
      9 => [
        'title' => "Everything fine?",
        'author' => 7
      ]
    ],
    'users' => [
      7 => [
        'name' => "Leo"
      ]
    ]
  ]
]

Data-loading

When a module shows a property from a database object, the module might not know, or care, what object it is; all it cares about is defining what properties from the loaded object are required.

For occasion, contemplate the image under a module hundreds an object from the database (in this case, a single put up), after which its descendant modules will present sure properties from the article, akin to "title" and "content":

While some modules load the database object, others load properties

Hence, alongside the element hierarchy, the “data loading” modules will be in cost of loading the queried objects (the module loading the only put up, in this case), and its descendant modules will outline what properties from the DB object are required ("title" and "content", in this case).

Fetching all of the required properties for the DB object may be finished by traversing the element hierarchy: ranging from the data loading module, PoP iterates all its descendant modules all how down till reaching a new data loading module, or till the tip of the tree; at every stage, it obtains all required properties, after which merges all properties collectively and queries them from the database, all of them solely as soon as.

Because the database object information is retrieved in a relational method, then we can additionally apply this technique among the many relationships between database objects themselves.

Consider the image under Starting from the article sort "post", and transferring down the element hierarchy, we will have to shift the database object sort to "user" and "comment", equivalent to the put up’s writer and every of the put up’s comments respectively, after which, for every comment, it should change the article sort as soon as once more to "user" equivalent to the comment’s writer. Moving from a database object to a relational object is what I name “switching domains”.

After switching to a new area, from that stage on the element hierarchy downwards, all required properties will be subjected to the brand new area: Property "name" is fetched from the "user" an object representing the put up’s writer, "content" from the "comment" an object representing every of the put up’s comments, after which "name" from the "user" an object representing the writer of every comment:

Changing the DB object from one area to a different

Traversing the element hierarchy, PoP is aware of when it is switching area and, appropriately, fetch the relational object information.

3. How parts are outlined in PoP

Module properties (configuration values, what database information to fetch, and so forth) and descendant modules are outlined by ModuleProcessor objects, on a module by module foundation and PoP creates the element hierarchy from all ModuleProcessors dealing with all concerned modules.

Similar to a React app (the place we should point out which element is rendered on

), the element model in PoP should have an entry module. Starting from it, PoP will traverse all modules in the element hierarchy, fetch the properties for every from the corresponding ModuleProcessor, and create the nested associative array with all properties for all modules.

When an element defines a descendant element, it references it by an array of 2 elements:

  1. The PHP class
  2. The element title

This is so as a result of parts usually share properties. For occasion, parts POST_THUMBNAIL_LARGE and POST_THUMBNAIL_SMALL will share most properties, except the dimensions of the thumbnail. Then, it is sensible to group all related parts underneath a similar PHP class, and use swap statements to establish the requested module and return the corresponding property.

A ModuleProcessor for put up widget parts to be positioned on completely different pages seems to be like this:

class PostWidgetModuleProcessor extends SummaryModuleProcessor {

  const POST_WIDGET_HOMEPAGE = 'post-widget-homepage';
  const POST_WIDGET_AUTHORPAGE = 'post-widget-authorpage';

  function getSubmodulesToCourse of() {
  
    return [
      self::POST_WIDGET_HOMEPAGE,
      self::POST_WIDGET_AUTHORPAGE,
    ];
  }

  function getSubmodules($module): array 
  {
    $ret = [];

    swap ($module[1]) {      
      case self::POST_WIDGET_HOMEPAGE:
      case self::POST_WIDGET_AUTHORPAGE:
        $ret[] = [
          UserLayoutModuleProcessor::class,
          UserLayoutModuleProcessor::POST_THUMB
        ];
        $ret[] = [
          UserLayoutModuleProcessor::class,
          UserLayoutModuleProcessor::POST_TITLE
        ];
        break;
    }
    swap ($module[1]) {      
      case self::POST_WIDGET_HOMEPAGE:
        $ret[] = [
          UserLayoutModuleProcessor::class,
          UserLayoutModuleProcessor::POST_DATE
        ];
        break;
    }

    return $ret;
  }

  function getImmutableConfiguration($module, &$props) 
  {
    $ret = [];

    swap ($module[1]) {
      case self::POST_WIDGET_HOMEPAGE:        
        $ret['description'] = __('Latest posts', 'my-domain');
        $ret['showmore'] = $this->getProp($module, $props, 'showmore');
        $ret['class'] = $this->getProp($module, $props, 'class');
        break;

      case self::POST_WIDGET_AUTHORPAGE:        
        $ret['description'] = __('Latest posts by the writer', 'my-domain');
        $ret['showmore'] = false;
        $ret['class'] = 'text-center';
        break;
    }

    return $ret;
  }
  
  function initModelProps($module, &$props) 
  {
    swap ($module[1]) {
      case self::POST_WIDGET_HOMEPAGE:
        $this->setProp($module, $props, 'showmore', false);
        $this->appendProp($module, $props, 'class', 'text-center');
        break;
    }

    dad or mum::initModelProps($module, $props);
  }
  // ...
}

Creating reusable parts is achieved by crafting a summary ModuleProcessor lessons defining placeholder functions that have to be applied by some instantiating class:

summary class PostWidgetLayoutAbstractModuleProcessor extends SummaryModuleProcessor
{
  function getSubmodules($module): array
  {  
    $ret = [
      $this->getContentModule($module),
    ];

    if ($thumbnail_module = $this->getThumbnailModule($module)) 
    {
      $ret[] = $thumbnail_module;
    }

    if ($aftercontent_modules = $this->getAfterContent materialModules($module)) 
    {
      $ret = array_merge(
        $ret,
        $aftercontent_modules
      );
    }

    return $ret;
  }

  summary protected function getContentModule($module): array;

  protected function getThumbnailModule($module): ?array 
  {
    // Default worth (overridable)
    return [self::class, self::THUMBNAIL_LAYOUT];
  }

  protected function getAfterContent materialModules($module): array 
  {
    return [];
  }

  function getImmutableConfiguration($module, &$props): array 
  {
    return [
      'description' => $this->getDescription(),
    ];
  }

  protected function getDescription($module): string
  {
    return '';
  }
}

Custom ModuleProcessor lessons can then lengthen the summary class, and outline their very own properties:

class PostLayoutModuleProcessor extends AbstractPostLayoutModuleProcessor {

  const POST_CONTENT = 'post-content'
  const POST_EXCERPT = 'post-excerpt'
  const POST_THUMBNAIL_LARGE = 'post-thumbnail-large'
  const POST_THUMBNAIL_MEDIUM = 'post-thumbnail-medium'
  const POST_SHARE = 'post-share'

  function getSubmodulesToCourse of() {
  
    return [
      self::POST_CONTENT,
      self::POST_EXCERPT,
      self::POST_THUMBNAIL_LARGE,
      self::POST_THUMBNAIL_MEDIUM,
      self::POST_SHARE,
    ];
  }

}

class PostWidgetLayoutModuleProcessor extends AbstractPostWidgetLayoutModuleProcessor
{
  protected function getContentModule($module): ?array 
  {
    swap ($module[1]) 
    {
      case self::POST_WIDGET_HOMEPAGE_LARGE:
        return [
          PostLayoutModuleProcessor::class,
          PostLayoutModuleProcessor::POST_CONTENT
        ];

      case self::POST_WIDGET_HOMEPAGE_MEDIUM:
      case self::POST_WIDGET_HOMEPAGE_SMALL:
        return [
          PostLayoutModuleProcessor::class,
          PostLayoutModuleProcessor::POST_EXCERPT
        ];
    }

    return dad or mum::getContentModule($module);
  }

  protected function getThumbnailModule($module): ?array 
  {
    swap ($module[1]) 
    {
      case self::POST_WIDGET_HOMEPAGE_LARGE:
        return [
          PostLayoutModuleProcessor::class,
          PostLayoutModuleProcessor::POST_THUMBNAIL_LARGE
        ];

      case self::POST_WIDGET_HOMEPAGE_MEDIUM:
        return [
          PostLayoutModuleProcessor::class,
          PostLayoutModuleProcessor::POST_THUMBNAIL_MEDIUM
        ];
    }

    return dad or mum::getThumbnailModule($module);
  }

  protected function getAfterContent materialModules($module): array 
  {
    $ret = [];

    swap ($module[1]) 
    {
      case self::POST_WIDGET_HOMEPAGE_LARGE:
        $ret[] = [
          PostLayoutModuleProcessor::class,
          PostLayoutModuleProcessor::POST_SHARE
        ];
        break
    }

    return $ret;
  }

  protected function getDescription($module): string
  {
    return __('These are my blog posts', 'my-domain');
  }
}

4. How parts are naturally appropriate for GraphQL

The element model can naturally map a tree-shaped GraphQL question, making it a perfect structure to implement a GraphQL server.

GraphQL by PoP has implemented the ModuleProcessor classes wanted to rework a GraphQL question to its corresponding element hierarchy and resolve it utilizing the PoP data loading engine.

This is why and the way this resolution works.

Mapping client-side parts to GraphQL queries

The GraphQL question may be represented utilizing PoP’s element hierarchy, in which every object sort represents an element, and each relationship subject from an object sort to a different object sort represents an element wrapping one other element.

Let’s see how this is the case through the use of an instance. Let’s say that we need to construct the next “Featured director” widget:

Featured director widget

Using Vue or React (or some other component-based library), we'd first establish the parts. In this case, we'd have an outer element (in crimson), which wraps an element (in blue), which itself wraps an element (in inexperienced):

Identifying parts in the widget

The pseudo-code seems to be like this:

Country: {nation} {foreach movies as movie} {/foreach}

Title: {title} Pic: {thumbnail} {foreach actors as actor} {/foreach}

Name: {title} Photo: {avatar}

 

Then we establish what information is wanted for every element. For we'd like the title, avatar and nation. For we'd like thumbnail and title. And for we'd like title and avatar:

Identifying information properties for every element

And we construct our GraphQL question to fetch the required information:

question {
  featuredDirector {
    title
    nation
    avatar
    movies {
      title
      thumbnail
      actors {
        title
        avatar
      }
    }
  }
}

As it may be appreciated, there is a direct relationship between the form of an element hierarchy and a GraphQL question. Indeed, a GraphQL question may even be thought of to be the illustration of an element hierarchy.

Resolving the GraphQL question utilizing server-side parts

Since a GraphQL question has the identical form of an element hierarchy, PoP transforms the question to its equal element hierarchy, resolves it utilizing its method to fetch information for the parts, and eventually recreates the form of the question to ship the information in the response.

Let’s see how this works.

To process the information, PoP converts the GraphQL varieties into parts: => Director, => Film, => Actor, and utilizing the order in which they seem in the question, PoP creates a digital element hierarchy with the identical components: root element Director, which wraps the element Film, which wraps the element Actor.

From now on, speaking about GraphQL varieties or PoP parts makes no distinction.

To load their information, PoP deals with them in “iterations”, retrieving the article information for every sort on its personal iteration, like this:

Dealing with varieties in iterations

PoP’s data loading engine implements the next pseudo-algorithm to load the information:

Preparation:

  1. Have an empty queue retailer the checklist of IDs from the objects that have to be fetched from the database, organized by sort (every entry will be: [type => list of IDs])
  2. Retrieve the ID of the featured director object, and place it on the queue underneath the sort Director

Loop till there are no extra entries on the queue:

  1. Get the primary entry from the queue: the sort and checklist of IDs (eg: Director and [2]), and take away this entry off the queue
  2. Execute a single question in opposition to the database to retrieve all objects for that sort with these IDs
  3. If the sort has relational fields (eg: sort Director has relational subject movies of sort Film), then gather all of the IDs from these fields from all of the objects retrieved in the present iteration (eg: all IDs in subject movies from all objects of the sort Director), and place these IDs on the queue underneath the corresponding sort (eg: IDs [3, 8] underneath sort Film).

By the tip of the iterations, we will have loaded all the article information for every type, like this:

Dealing with varieties in iterations

Please discover how all IDs for a sort are collected, till the sort is processed in the queue. If, for example, we add a relational subject most well-likedActors to sort Director, these IDs could be added to the queue underneath sort Actor, and it could be processed along with the IDs from subject actors from sort Film:

Dealing with varieties in iterations

However, if a sort has been processed after which we have to load extra information from that sort, then it’s a new iteration on that sort. For occasion, including a relational subject most well-likedDirector to the Author sort will make the sort Director be added to the queue as soon as once more:

Iterating over a repeated sort

Pay consideration additionally that right here we can use a caching mechanism: on the second iteration for the sort Director, the article with ID 2 is not retrieved once more, because it was already retrieved on the primary iteration so it may be taken from the cache.

Now that we've fetched all the article information, we have to form it into the expected response, mirroring the GraphQL question. As it is at present, information is organized as in a relational database:

Table for sort Director:

ID title nation avatar movies
2 George Lucas USA George-lucas.jpg [3, 8]

Table for sort Film:

ID title thumbnail actors
3 The Phantom Menace episode-1.jpg [4, 6]
8 Attack of the Clones episode-2.jpg [6, 7]

Table for sort Actor:

ID title avatar
4 Ewan McGregor mcgregor.jpg
6 Nathalie Portman portman.jpg
7 Hayden Christensen christensen.jpg

At this stage, PoP has all the information organized as tables and shows how each sort relates to one another (i.e. Director references Film by subject movies, Film references Actor by subject actors). Then, by iterating the element hierarchy from the foundation, navigating the relationships, and retrieving the corresponding objects from the relational tables, PoP will produce the tree form from the GraphQL question:

Tree-shaped response

Finally, printing the information into the output produces the response with the identical form of the GraphQL question:

{
  information: {
    featuredDirector: {
      title: "George Lucas",
      nation: "USA",
      avatar: "george-lucas.jpg",
      movies: [
        { 
          title: "Star Wars: Episode I",
          thumbnail: "episode-1.jpg",
          actors: [
            {
              name: "Ewan McGregor",
              avatar: "mcgregor.jpg",
            },
            {
              name: "Natalie Portman",
              avatar: "portman.jpg",
            }
          ]
        },
        { 
          title: "Star Wars: Episode II",
          thumbnail: "episode-2.jpg",
          actors: [
            {
              name: "Natalie Portman",
              avatar: "portman.jpg",
            },
            {
              name: "Hayden Christensen",
              avatar: "christensen.jpg",
            }
          ]
        }
      ]
    }
  }
}

5. Analysis of the performance of utilizing parts to resolve a GraphQL question

Let’s analyze the large O notation of the data loading algorithm to know how the quantity of queries executed in opposition to the database grows because the quantity of inputs grows, to ensure that this resolution is performant.

PoP’s data loading engine has hundreds of information in iterations corresponding to every sort. By the time it starts an iteration, it will have already got the checklist of all of the IDs for all of the objects to fetch, therefore it could actually execute 1 single question to fetch all the information for the corresponding objects. It then follows that the number of queries to the database will develop linearly with the number of varieties concerned in the question. In different phrases, the time complexity is O(n), the place n is the quantity of varieties in the question (nonetheless, if a sort is iterated greater than as soon as, then it have to be added greater than as soon as to n).

This resolution is very performant, actually greater than the exponential complexity expected from coping with graphs, or logarithmic complexity expected from coping with timber.

Conclusion

A GraphQL server doesn't want to use graphs to characterize information. In this text, we explored the structure described by PoP and applied it by GraphQL by PoP, which is based mostly on parts and hundreds of information in iterations in line with sort.

Through this method, the server can resolve GraphQL queries with linear time complexity, which is a higher-end result than the exponential or logarithmic time complexity expected from utilizing graphs or timber.