You are here

abstract class BackendBase in SCSS Compiler 1.0.x

Provides a base compiler backend implementation.

Copyright (C) 2021 Library Solutions, LLC (et al.).

This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.

@internal

Hierarchy

Expanded class hierarchy of BackendBase

File

src/BackendBase.php, line 21

Namespace

Drupal\compiler_scss
View source
abstract class BackendBase implements BackendInterface {

  /**
   * An array of function descriptors to be added to the compiler.
   *
   * @var array
   */
  protected $functions = [];

  /**
   * Generate a list of functions that should be registered with the compiler.
   *
   * @see ::getCallbackFunction()
   *   An anonymous shim function may be introduced using this method that wraps
   *   the supplied callback function for additional processing of arguments and
   *   return values.
   *
   * @return \Generator
   *   An array of function descriptors that should be conveyed to the compiler.
   */
  protected abstract function generateFunctionDescriptors() : \Generator;

  /**
   * Retrieve an absolute file path for the supplied input file.
   *
   * If the supplied context is a theme compiler context, then it will be used
   * to resolve theme-relative file paths.
   *
   * @param \Drupal\compiler\CompilerContextInterface $context
   *   A compiler context used to define a compilation.
   * @param string $path
   *   A path, possibly theme-relative.
   *
   * @throws \RuntimeException
   *   If the resulting absolute file path doesn't exist.
   *
   * @return string
   *   An absolute file path for the supplied input file.
   */
  protected function getAbsoluteFilePath(CompilerContextInterface $context, string $path) {
    if (($result = realpath($path)) === FALSE) {
      throw new \RuntimeException('Unable to resolve file path: ' . var_export($path, TRUE));
    }
    return $result;
  }

  /**
   * Fetch the callback function to use for the supplied descriptor.
   *
   * This method serves to provide a point where the concrete class can wrap (or
   * otherwise modify) the callback function before it is conveyed to the
   * underlying compiler implementation.
   *
   * By default, this method wraps the callback function to facilitate
   * processing of its return value.
   *
   * @param object $descriptor
   *   A function descriptor object created by ::registerFunction().
   *
   * @return callable
   *   The callback function to use for the supplied descriptor.
   */
  protected function getCallbackFunction(object $descriptor) {
    return function ($args) use ($descriptor) {
      if (count($args) !== count($descriptor->params)) {
        throw new \BadFunctionCallException('PHP function parameter mismatch');
      }
      $args = array_map([
        $this,
        'processArgumentValue',
      ], $args);
      $return = call_user_func_array($descriptor->callback, $args);
      $return = $this
        ->processReturnValue($return);
      return $return;
    };
  }

  /**
   * Attempt to retrieve the absolute import path for the compiler.
   *
   * An import path may be defined as a compiler option, 'import_path', whose
   * value must be absolute if not passed a theme compiler context; otherwise
   * the path is theme-relative.
   *
   * @param \Drupal\compiler\CompilerContextInterface $context
   *   A compiler context used to define a compilation.
   *
   * @return string|null
   *   An absolute import path for the compiler on success; NULL otherwise.
   */
  protected function getImportPath(CompilerContextInterface $context) {
    $options = $context
      ->getOptions();
    if (array_key_exists('import_path', $options) && is_string($options['import_path']) && !empty($options['import_path'])) {
      $result = $this
        ->getAbsoluteFilePath($context, $options['import_path']);
    }
    return isset($result) ? $result : NULL;
  }

  /**
   * Retrieve the source code to pass to the compiler.
   *
   * @param \Drupal\compiler\CompilerContextInterface $context
   *   A compiler context used to define a compilation.
   *
   * @return string
   *   The source code to pass to the compiler.
   */
  protected function getInput(CompilerContextInterface $context) {
    $source = [];

    // Iterate through each compiler input to construct the compiler source.
    foreach (new \RecursiveIteratorIterator($context
      ->getInputs()) as $input) {
      $result = $input
        ->get();
      if ($input instanceof CompilerInputFile) {
        $result = $this
          ->getAbsoluteFilePath($context, $result);
        $result = file_get_contents($result);
      }
      elseif (!$input instanceof CompilerInputDirect) {
        throw new \RuntimeException('Unsupported input type');
      }
      $source[] = $result;
    }

    // Concatenate the array of source code into a single string.
    return implode("\r\n", $source);
  }

  /**
   * Process an argument value from the compiler ahead of the callback.
   *
   * This method is responsible for the dynamic type juggling between the two
   * languages to improve the developer experience when writing functions.
   *
   * @param mixed $value
   *   The input value to process.
   *
   * @return mixed
   *   The processed input value.
   */
  public abstract function processArgumentValue($value);

  /**
   * Process a bridge function return value before it's passed to the compiler.
   *
   * This method is responsible for the dynamic type juggling between the two
   * languages to improve the developer experience when writing functions.
   *
   * @param mixed $value
   *   The input value to process.
   *
   * @return mixed
   *   The processed input value.
   */
  public abstract function processReturnValue($value);

  /**
   * {@inheritdoc}
   */
  public function registerFunction(callable $callback, $name = NULL) {

    // Use reflection to facilitate registering the supplied callback function.
    $reflection = new \ReflectionFunction($callback);

    // Attempt to resolve the name of the callback function.
    if (!$reflection
      ->isClosure()) {
      $name = $reflection
        ->getShortName();
    }
    elseif (preg_match(self::IDENT_EXPR, $name) !== 1) {
      throw new \InvalidArgumentException('Bad function name');
    }

    // Fetch a list of parameter names for this function.
    $params = array_map(function ($param) {

      // TODO: We don't yet support optional parameters.
      //
      // Pass-by-reference parameters will never be supported across languages.
      if ($param
        ->isOptional() || $param
        ->isPassedByReference()) {
        throw new \InvalidArgumentException('Optional or pass-by-reference parameters are not supported for callback functions');
      }
      return $param
        ->getName();
    }, $reflection
      ->getParameters());

    // Construct the function's SASS signature using the parameter list.
    $signature = implode(', ', array_map(function ($param) {
      return '$' . $param;
    }, $params));
    $this->functions[$name] = (object) [
      'callback' => $callback,
      'name' => $name,
      'params' => $params,
      'signature' => $signature,
    ];
  }

}

Members

Namesort descending Modifiers Type Description Overrides
BackendBase::$functions protected property An array of function descriptors to be added to the compiler.
BackendBase::generateFunctionDescriptors abstract protected function Generate a list of functions that should be registered with the compiler.
BackendBase::getAbsoluteFilePath protected function Retrieve an absolute file path for the supplied input file.
BackendBase::getCallbackFunction protected function Fetch the callback function to use for the supplied descriptor.
BackendBase::getImportPath protected function Attempt to retrieve the absolute import path for the compiler.
BackendBase::getInput protected function Retrieve the source code to pass to the compiler.
BackendBase::processArgumentValue abstract public function Process an argument value from the compiler ahead of the callback.
BackendBase::processReturnValue abstract public function Process a bridge function return value before it's passed to the compiler.
BackendBase::registerFunction public function Register a PHP callback function bridge that can be used in SCSS code. Overrides BackendInterface::registerFunction
BackendInterface::IDENT_EXPR constant An identifier expression which is a proper subset of both PHP & SCSS.
CompilerPluginInterface::compile public function Compile the provided source context and return the result.