ScandiPWA
Create Magento AppCreate ScandiPWA AppUser ManualGitHub
  • Why Scandi
  • πŸš€Quick-start Guide
  • πŸ—ΊοΈRoadmap
  • Introduction to the Stack
    • CMA, CSA, and ScandiPWA
    • Challenges
  • Setting up Scandi
    • Storefront Mode Setup
      • Proxying requests to server
    • Magento Mode Setup
    • Existing Magento 2 setup
    • Magento Commerce Cloud setup
    • Updating to new releases
      • Storefront mode upgrade
      • Magento mode upgrade
      • CMA upgrade
      • CSA upgrade
      • Custom ScandiPWA composer dependency update
      • Local ScandiPWA Composer Package Setup
    • Docker Setup [deprecated]
      • Legacy Docker setup
      • Migrating to CMA & CSA
  • Developing with Scandi
    • Override Mechanism
      • Overriding JavaScript
        • Overriding classes
        • Overriding non-classes
      • Overriding Styles
      • Overriding the HTML / PHP
      • Parent Themes
    • Extensions
      • Creating an extension
      • Installing an extension
      • Migrating from 3.x to 4.x
      • Publishing an extension
      • Extension Terminology
    • Working With Magento
      • Magento troubleshooting
      • Working with Magento modules
      • Working with GraphQL
      • GraphQL Security
      • Working with "granular cache"
    • Developer Tools
      • Debugging in VSCode
      • ScandiPWA CLI
      • Configuring ESLint
      • CSA Commands
    • Deploying Your App
      • Build & Deploy Android app
      • Build & Deploy iOS app
  • Structure
    • Directory Structure
    • Building Blocks
      • Components
        • Styling Components
      • Routes
      • Redux Stores
      • GraphQL Queries
      • Global Styles
      • The Util Directory
      • Type Checking
    • Application assets
    • Code Style
      • JavaScript Code Style
      • SCSS Code Style
  • Tutorials
    • Customizing Your Theme
      • Styling
        • Customizing the Global Styles
        • Adding a New Font
        • Overriding a Components Styles
        • Extending a Component's Styles
      • Customizing JavaScript
        • Customizing the Footer Copyright
        • Adding a New Page
        • Adding a Section in My Account
        • Adding a Tab on the Product Page
        • Creating a New Redux Store
    • Payment Method Integration
      • Setting Up for Development
      • Redirecting to the Payment Provider
      • Handling the Customer's Return
    • Creating a Custom Widget
      • Scandi CMS System Overview
      • Creating a Magento Widget
      • Implementing the Rendering
    • Video Tutorials
      • #1 Setting up and talking theory
      • #2 Templating in React
      • #3 Overriding a file
      • #4 Styling the application
      • #5 Patterns of ScandiPWA
    • Dark Mode Extension
    • Deploying Native Apps
    • Product 3D Model Extension
      • Part 1: Magento 3D Model Uploads
      • Part 2: GraphQL API
      • Part 3: Scandi Frontend
    • Social Share, Full Extension Development
      • STEP-1 and 2 Creating Magento 2 Module
      • STEP-3 Backend Configurations Settings
      • STEP-4 Simple GraphQl and Resolver
      • STEP-5 Creating Extension, Base Redux Store
      • STEP-6 Extension plugins
      • STEP-7 GraphQL types, Helpers
      • STEP-8 Query Field and FieldList
      • STEP-9 render Plugins and MSTP Plugin, Component creation
      • STEP-10 SocialShare Component Development
      • STEP-11 SocialShare for CategoryPage
      • TASK-1 Changing LinkedIn to Twitter
      • STEP-12 Comments for Admin Users
      • STEP-13 Final, bugfixes
    • Accessing Magento 2 Controllers
      • STEP-1 Creating Magento 2 Module
      • STEP-2 - Create Magento 2 Frontend Route and Basic Controller
      • STEP-3 Accessing Magento 2 Controller, Bypassing ScandiPWA frontend
      • STEP-4 Creating ScandiPWA Extension with additional dependencies
      • STEP-5 Creating Plugin and Axios request
  • About
    • Support
    • Release notes
    • Technical Information
    • Data Analytics
    • Contributing
      • Installation from Fork
      • Repository structure
      • Code contribution process
      • Submitting an Issue
      • Publishing ScandiPWA
Powered by GitBook
On this page
  • Extending the GraphQL Schema
  • Implementing the Field

Was this helpful?

  1. Tutorials
  2. Product 3D Model Extension

Part 2: GraphQL API

Expose product 3D models through the GraphQL API

PreviousPart 1: Magento 3D Model UploadsNextPart 3: Scandi Frontend

Last updated 3 years ago

Was this helpful?

At this point, the admin can upload and manage 3D models for each product, but these models are not yet visible to the frontend. However, before we can start implementing the frontend logic for product 3D models, we need to expose this data through an API.

GraphQL is an API language has over the conventional REST APIs – greater flexibility, better API documentation, type checking, and a simple query structure.

to fetch data from the backend. For example, to fetch product data, Scandi uses the products query:

Example GraphQL query:
{
  products(search:"bag") {
    items {
      id
      name
    }
  }
}
Corresponding response:
{
  "data": {
    "products": {
      "items": [
        {
          "id": 1,
          "name": "Joust Duffle Bag"
        },
        {
          "id": 8,
          "name": "Voyage Yoga Bag"
        },
        {
          "id": 4,
          "name": "Wayfarer Messenger Bag"
        }
      ]
    }
  }
}

This query already has multiple useful fields – we can query the product name, sku, price and other properties if we want. But now we want to add a brand-new queryable field: model_3d_urls. This field will return an array of strings, each representing the URL of a 3D model associated with the product.

Extending the GraphQL Schema

A GraphQL Schema is a document that describes the fields available in a GraphQL API. For example, there is a schema that specifies that the items field of the products query is an array of ProductInterface. There is another schema file that describes the fields that are part of ProductInterface:

vendor/magento/module-catalog-graph-ql/etc/schema.graphqls (snippet)
interface ProductInterface
{
    id: Int
    name: String
    sku: String @doc(description: "A code assigned to a product")
    description: ComplexTextValue
    # [...]
}    

Each field consists of a name for the field (like "id"), and a corresponding type (Int/String, etc.). This describes the values that the API is expected to have.

Because of the way GraphQL is setup in Magento, all of the schema files are merged together. This means that we can easily "extend" the ProductInterface type to include other fields as well. All we need to do is create a new GraphQL schema file in our module (in etc/schema.graphqls), and declare the additional ProductInterface fields there:

etc/schema.graphqls
interface ProductInterface {
    model_3d_urls: [String!]!
}

And we have updated the GraphQL schema! Run magento setup:upgrade for Magento to update it.

The exclamation mark (!) means that the field cannot be null. This means that values such as model_3d_urls: null and model_3d_urls: [null, null] are not valid. It is a good idea to tell GraphQL that we expect these values to be present on any product if that is the case, because this will result in an error if the field is not set, helping catch bugs early.

Implementing the Field

Now, our ProductInterface type provides an additional field. We have made a "promise" to API users that they can query a model_3d_urls field on any product. Indeed, if we refresh the schema in our GraphQL client, we are now able to make a query such as this one:

{
  products(search:"bag") {
    items {
      id
      name
      model_3d_urls
    }
  }
}

However, if we send the query, we will get an error – because there is nothing in the current resolver that would populate model_3d_urls with a value, and GraphQL is complaining that the data returned by the resolver does not match the schema we defined.

A GraphQL Resolver is an implementation of the schema. It is the PHP code that actually handles PHP requests and returns a data object of the response.

The products query already has a resolver – that's the bit of code that returns the items so that we can see the id, name and other fields. Now, we want to "plug in" to the resolver so that it returns the additional data we want.

The best way to "plug in" depends on how the resolver is implemented. Sometimes, we have to use Magento plugins (interceptors) to "wrap around" the resolver and add additional data before it returns. In this case, however, there is a specific class whose purpose is to process the product data after it is generated, but before it is returned through GraphQL.

The product DataPostProcessor accepts an array of objects that implement the ProductsDataPostProcessorInterface. Then, whenever the product collection is processed, it passes the data through each of the processor objects, allowing them to modify the data.

We can use the dependency injection configuration file to inject an additional processor:

etc/di.xmletc/di.xml
<?xml version="1.0"?>
<config
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"
>
    <!-- [...] -->

    <type name="ScandiPWA\Performance\Model\Resolver\Products\DataPostProcessor">
        <arguments>
            <argument name="processors" xsi:type="array">
                <item name="model_3d_urls" xsi:type="object">
Scandi\Product3DGraphQl\Model\Resolver\Products\CollectionPostProcessor\Model3DProcessor
                </item>
            </argument>
        </arguments>
    </type>
</config>

Then, we can define that class and implement the functionality we want:

<?php declare(strict_types=1);
namespace Scandi\Product3DGraphQl\Model\Resolver\Products\CollectionPostProcessor;

use Scandi\Product3DGraphQl\Helper\Model3DProvider;
use ScandiPWA\Performance\Api\ProductsDataPostProcessorInterface;

class Model3DProcessor implements ProductsDataPostProcessorInterface
{
    const MODEL_FIELD = 'model_3d_urls';

    /** @var Model3DProvider */
    protected $modelProvider;
    
    // [DI constructor omitted]

    public function process(
        array $products,
        string $graphqlResolvePath,
        $graphqlResolveInfo,
        ?array $processorOptions = []
    ): callable {
        // Collect relevant product IDs:
        $ids = [];
        foreach ($products as $product) {
            $ids[] = $product->getId();
        }

        // Get all 3D Models that belong to those IDs (1 SQL request)
        $modelsByProductId = $this->modelProvider->getModelsForProductIds($ids);

        // Return a function that adds a new field to the given product
        return function (&$product) use ($modelsByProductId) {
            $models = $modelsByProductId[$product['entity_id']];

            $urls = [];
            foreach ($models as $model) {
                $urls[] = $model->getResourceUrl();
            }

            $product[self::MODEL_FIELD] = $urls;
        };
    }
}

You can now verify that we can query the 3D Model URLs of the products and they will be returned correctly. Example response:

{
  "data": {
    "products": {
      "items": [
        {
          "id": 1,
          "name": "Joust Duffle Bag",
          "model_3d_urls": [
            "http://localhost/media/scandi/product_3d_models/m/o/model1.glb",
            "http://localhost/media/scandi/product_3d_models/m/o/model2.glb"
          ]
        },
        {
          "id": 8,
          "name": "Voyage Yoga Bag",
          "model_3d_urls": []
        },
        {
          "id": 4,
          "name": "Wayfarer Messenger Bag",
          "model_3d_urls": []
        },
        {
          "id": 14,
          "name": "Push It Messenger Bag",
          "model_3d_urls": []
        }
      ]
    }
  }
}

This works as expected – products with no 3D models get an empty array in model_3d_urls and products with 3D models have an array of valid model URLs.

You can use a GraphQL client such as to make GraphQL queries easily. This can be useful when exploring an existing API as we did above, or to debug your own API, as we are about to do.

Make sure you set the API URL to (assuming your M2 server is running at localhost) and you're good to go – you can try out the example query from above!

several advantages
Scandi uses GraphQL
Altair GraphQL Client
http://localhost/graphql