migmag-1.0.x-dev/tests/modules/migmag_predictable_uuid/src/PredictableUuid.php
tests/modules/migmag_predictable_uuid/src/PredictableUuid.php
<?php namespace Drupal\migmag_predictable_uuid; use Drupal\Component\Uuid\Php as DefaultGenerator; use Drupal\Core\File\FileSystemInterface; use Drupal\Core\State\StateInterface; // cspell:ignore fqcns /** * A predictable UUID generator. */ class PredictableUuid extends DefaultGenerator { /** * Key of the state storing how many times a predictable UUID was generated. * * @const string */ const LAST_SUFFIX_STATE_KEY = 'pathauto_test_uuid_generator.last'; /** * Key of the state where the watches classes are stored. * * @const string */ const WATCHED_CLASSES_STATE_KEY = 'pathauto_test_uuid_generator.watch'; /** * The state service. * * @var \Drupal\Core\State\StateInterface */ protected $state; /** * The file system service. * * @var \Drupal\Core\File\FileSystemInterface */ protected $fileSystem; /** * Constructs a UuidTestGenerator instance. * * @param \Drupal\Core\State\StateInterface $state * The state service. * @param \Drupal\Core\File\FileSystemInterface $file_system * The file system service. */ public function __construct(StateInterface $state, FileSystemInterface $file_system) { $this->state = $state; $this->fileSystem = $file_system; } /** * {@inheritdoc} */ public function generate() { if (empty($watch = $this->state->get(self::WATCHED_CLASSES_STATE_KEY, []))) { return parent::generate(); } $trace_files = array_reduce( debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), function (array $carry, array $trace) { // $trace['file'] might be empty if we have closures. if (!empty($trace['file'])) { $carry[] = $trace['file']; } return $carry; }, [] ); foreach ($watch as $prefix => $watched_files_and_fqcns) { foreach ((array) $watched_files_and_fqcns as $watched_file_or_fqcn) { $reflection_class = NULL; $file = file_exists($watched_file_or_fqcn) ? $watched_file_or_fqcn : NULL; if ( $file && strpos($watched_file_or_fqcn, DRUPAL_ROOT) !== 0 ) { $file = $this->fileSystem->realpath($watched_file_or_fqcn); } if ($file && !file_exists($file)) { continue; } if (!$file) { try { $reflection_class = new \ReflectionClass($watched_file_or_fqcn); $file = $reflection_class->getFileName(); } catch (\ReflectionException $e) { continue; } } if (in_array($file, $trace_files, TRUE)) { return $this->generateFromTemplate($prefix); } } } return parent::generate(); } /** * Generates a UUID with the given prefix or UUID template. * * UUID templates should end with 12 zeros and must look like a valid v4 UUID. * * @param string $template_or_prefix * The uuid template or string to use. * * @return string * A generated, predictable UUID. */ protected function generateFromTemplate(string $template_or_prefix): string { $current = ($current = (int) $this->state->get(self::LAST_SUFFIX_STATE_KEY . '.' . $template_or_prefix, 0)) >= PHP_INT_MAX ? 0 : $current + 1; $this->state->set(self::LAST_SUFFIX_STATE_KEY . '.' . $template_or_prefix, (int) $current); if (self::validateUuidMask($template_or_prefix)) { $hex_current = dechex($current); return substr($template_or_prefix, 0, strlen($hex_current) * -1) . (string) $hex_current; } return $template_or_prefix . $current; } /** * Checks whether the given string is a UUID template. * * UUID templates should end with 12 zeros and must look like a valid v4 UUID, * e.g '01234567-89ab-4cde-f012-00000000000' or * 'aaaaaaaa-bbbb-4ccc-dddd-000000000000' are valid templates, but 'foo', * 'aaaaaaa-bbb-cccc-dddd-00000000' or 'foo0bar0-baz0-foo0-bar0-baz0foo0bar' * aren't. * * @param string $template_or_prefix * The string to check. * * @return bool * Whether the given string is a UUID template. */ protected static function validateUuidMask(string $template_or_prefix): bool { return preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[0-9a-f]{4}-0{12}$/', $template_or_prefix); } }