graphql_fragment_include-8.x-1.1/src/GraphQL/Fragment/GraphQLFragmentLoader.php
src/GraphQL/Fragment/GraphQLFragmentLoader.php
<?php
namespace Drupal\graphql_fragment_include\GraphQL\Fragment;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
/**
* An implementation of GraphqlFragmentLoaderInterface.
*
* Allows inclusion of GraphQL Fragments on a query, defined by the
* following syntax:
* # include Fragment.gql
* Includes are not supported by GraphQL, so we need to use comments for them.
*/
class GraphQLFragmentLoader implements GraphQLFragmentLoaderInterface {
use StringTranslationTrait;
/**
* Config object for this module.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* Logger instance for this module.
*
* @var \Psr\Log\LoggerInterface
*/
protected $logger;
/**
* Loaded includes.
*
* @var array
*/
protected $loadedFragments;
/**
* Constructor for IncludeLoader.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
* Drupal config factory.
* @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $loggerFactory
* Logger Factory.
*/
public function __construct(
ConfigFactoryInterface $configFactory,
LoggerChannelFactoryInterface $loggerFactory) {
$this->configFactory = $configFactory;
$this->logger = $loggerFactory->get('graphql_fragment_include');
}
/**
* {@inheritdoc}
*/
public function findFragments(string $graphQLQuery) {
preg_match_all("/^#\s+include\s+(\S+)\s*$/m", $graphQLQuery, $matches);
return $matches;
}
/**
* {@inheritdoc}
*/
public function loadFragments(string $graphQLQuery) {
// Ensure $this->loadedFragments is empty on each execution.
$this->loadedFragments = [];
$graphQLQuery = $this->parseQuery($graphQLQuery);
$this->loadedFragments = [];
return $graphQLQuery;
}
/**
* Parse query and load fragments..
*
* We use a helper function so we can manage $this->loadedFragments in a more
* easier way.
*
* @param string $graphQLQuery
* The GraphQL query to parse.
*
* @return string
* The parsed GraphQL query.
*/
protected function parseQuery(string $graphQLQuery) {
$matches = $this->findFragments($graphQLQuery);
if ($matches) {
$baseDir = DRUPAL_ROOT . $this->configFactory->get('graphql_fragment_include.settings')->get("fragments_base_dir");
foreach ($matches[1] as $key => $match) {
$fragmentPath = realpath($baseDir . '/' . $match);
if (!$this->isFragmentPathValid($fragmentPath)) {
$this->logger->warning($this->t("Fragment path '@fragment-path' is not valid (derived from '@match').", ['@fragment-path' => $fragmentPath, '@match' => $match]));
continue;
}
if (!is_file($fragmentPath)) {
$this->logger->warning($this->t("No fragment '@fragment-path' found (derived from '@match').", ['@fragment-path' => $fragmentPath, '@match' => $match]));
continue;
}
// Avoid double loading a fragment.
if ($this->isFragmentAlreadyLoaded($fragmentPath)) {
$graphQLQuery = trim(str_replace($matches[0][$key], "", $graphQLQuery));
}
else {
$this->markFragmentAsLoaded($fragmentPath);
$fragmentData = trim(file_get_contents($fragmentPath));
// Recursively load other fragments.
$fragmentData = $this->parseQuery($fragmentData);
$graphQLQuery = trim(str_replace($matches[0][$key], "", $graphQLQuery));
$graphQLQuery .= "\n\n# START $match \n" . $fragmentData . "\n# END $match ";
}
}
}
return $graphQLQuery;
}
/**
* Tells whether a fragment path is valid.
*
* It's considered valid if it starts with fragments_base_dir.
*
* @param string $fragmentPath
* The file to check.
*
* @return bool
* True if it's valid, false otherwise.
*/
protected function isFragmentPathValid(string $fragmentPath) {
$baseDir = realpath(DRUPAL_ROOT . $this->configFactory->get('graphql_fragment_include.settings')->get("fragments_base_dir"));
$fragmentPathStart = mb_substr($fragmentPath, 0, strlen($baseDir));
if ($fragmentPathStart === $baseDir) {
return TRUE;
}
return FALSE;
}
/**
* Tells whether a fragment has been already loaded.
*
* GraphQL fragments can not be repeated.
*
* @param string $fragmentPath
* The file to check.
*
* @return bool
* True if it has been already loaded, false otherwise.
*/
protected function isFragmentAlreadyLoaded(string $fragmentPath) {
return isset($this->loadedFragments[$fragmentPath]);
}
/**
* Marks a fragment as already loaded.
*
* @param string $fragmentPath
* The file to be marked as already loaded.
*/
protected function markFragmentAsLoaded(string $fragmentPath) {
$this->loadedFragments[$fragmentPath] = TRUE;
}
}
