/users/2). * Uses @urlParam values specified by caller, otherwise just uses '1'. * * @param string $uri * @param array $urlParameters Dictionary of url params and example values * * @return string */ public static function replaceUrlParameterPlaceholdersWithValues(string $uri, array $urlParameters): string { if (empty($urlParameters)) { return $uri; } if (self::isLumen()) { $boundUri = ''; $possibilities = (new Std)->parse($uri); // See https://github.com/nikic/FastRoute#overriding-the-route-parser-and-dispatcher $possibilityWithAllSegmentsPresent = end($possibilities); foreach ($possibilityWithAllSegmentsPresent as $part) { if (!is_array($part)) { // It's just a path segment, not a URL parameter' $boundUri .= $part; continue; } $name = $part[0]; $boundUri .= $urlParameters[$name]; } return $boundUri; } foreach ($urlParameters as $parameterName => $example) { $uri = preg_replace('#\{' . $parameterName . '\??}#', $example, $uri); } // Remove unbound optional parameters with nothing $uri = preg_replace('#{([^/]+\?)}#', '', $uri); // Replace any unbound non-optional parameters with '1' $uri = preg_replace('#{([^/]+)}#', '1', $uri); return $uri; } public static function getRouteClassAndMethodNames(Route $route): array { $action = $route->getAction(); $uses = $action['uses']; if ($uses !== null) { if (is_array($uses)) { return $uses; } elseif (is_string($uses)) { return explode('@', $uses); } elseif (static::isInvokableObject($uses)) { return [$uses, '__invoke']; } } if (array_key_exists(0, $action) && array_key_exists(1, $action)) { return [ 0 => $action[0], 1 => $action[1], ]; } throw new Exception("Couldn't get class and method names for route " . c::getRouteRepresentation($route) . '.'); } public static function deleteDirectoryAndContents(string $dir, ?string $workingDir = null): void { if (class_exists(LocalFilesystemAdapter::class)) { // Flysystem 2+ $workingDir ??= getcwd(); $adapter = new LocalFilesystemAdapter($workingDir); $fs = new Filesystem($adapter); $dir = str_replace($workingDir, '', $dir); $fs->deleteDirectory($dir); } else { // v1 $adapter = new \League\Flysystem\Adapter\Local($workingDir ?: getcwd()); $fs = new Filesystem($adapter); $dir = str_replace($adapter->getPathPrefix(), '', $dir); $fs->deleteDir($dir); } } public static function listDirectoryContents(string $dir) { if (class_exists(LocalFilesystemAdapter::class)) { // Flysystem 2+ $adapter = new LocalFilesystemAdapter(getcwd()); $fs = new Filesystem($adapter); return $fs->listContents($dir); } else { // v1 $adapter = new \League\Flysystem\Adapter\Local(getcwd()); // @phpstan-ignore-line $fs = new Filesystem($adapter); // @phpstan-ignore-line $dir = str_replace($adapter->getPathPrefix(), '', $dir); // @phpstan-ignore-line return $fs->listContents($dir); } } public static function copyDirectory(string $src, string $dest): void { if (!is_dir($src)) return; // If the destination directory does not exist create it if (!is_dir($dest)) { if (!mkdir($dest, 0777, true)) { // If the destination directory could not be created stop processing throw new Exception("Failed to create target directory: $dest"); } } // Open the source directory to read in files $i = new DirectoryIterator($src); foreach ($i as $f) { if ($f->isFile()) { copy($f->getRealPath(), "$dest/" . $f->getFilename()); } else if (!$f->isDot() && $f->isDir()) { self::copyDirectory($f->getRealPath(), "$dest/$f"); } } } public static function deleteFilesMatching(string $dir, callable $condition): void { if (class_exists(LocalFilesystemAdapter::class)) { // Flysystem 2+ $adapter = new LocalFilesystemAdapter(getcwd()); $fs = new Filesystem($adapter); $contents = $fs->listContents(ltrim($dir, '/')); } else { // v1 $adapter = new \League\Flysystem\Adapter\Local(getcwd()); // @phpstan-ignore-line $fs = new Filesystem($adapter); // @phpstan-ignore-line $dir = str_replace($adapter->getPathPrefix(), '', $dir); // @phpstan-ignore-line $contents = $fs->listContents(ltrim($dir, '/')); } foreach ($contents as $file) { // Flysystem v1 had items as arrays; v2 has objects. // v2 allows ArrayAccess, but when we drop v1 support (Laravel <9), we should switch to methods if ($file['type'] == 'file' && $condition($file) === true) { $fs->delete($file['path']); } } } /** * @param mixed $value * * @return bool */ public static function isInvokableObject($value): bool { return is_object($value) && method_exists($value, '__invoke'); } /** * Returns the route method or closure as an instance of ReflectionMethod or ReflectionFunction * * @param array $routeControllerAndMethod * * @return ReflectionFunctionAbstract * @throws ReflectionException * */ public static function getReflectedRouteMethod(array $routeControllerAndMethod): ReflectionFunctionAbstract { [$class, $method] = $routeControllerAndMethod; if ($class instanceof Closure) { return new ReflectionFunction($class); } return (new ReflectionClass($class))->getMethod($method); } public static function isArrayType(string $typeName) { return Str::endsWith($typeName, '[]'); } public static function getBaseTypeFromArrayType(string $typeName) { return substr($typeName, 0, -2); } public static function getModelFactory(string $modelName, array $states = [], array $relations = []) { // Factories are usually defined without the leading \ in the class name, // but the user might write it that way in a comment. Let's be safe. $modelName = ltrim($modelName, '\\'); if (method_exists($modelName, 'factory')) { // Laravel 8 type factory /** @var \Illuminate\Database\Eloquent\Factories\Factory $factory */ $factory = call_user_func_array([$modelName, 'factory'], []); foreach ($states as $state) { if (method_exists(get_class($factory), $state)) { $factory = $factory->$state(); } } foreach ($relations as $relation) { // Support nested relations; see https://github.com/knuckleswtf/scribe/pull/364 for a detailed example // Example: App\Models\Author with=posts.categories $relationChain = explode('.', $relation); $relationVector = array_shift($relationChain); $relationModel = get_class((new $modelName())->{$relationVector}()->getModel()); $relationType = get_class((new $modelName())->{$relationVector}()); $factoryChain = empty($relationChain) ? call_user_func_array([$relationModel, 'factory'], []) : Utils::getModelFactory($relationModel, $states, $relationChain); if ($relationType === BelongsToMany::class) { $pivot = method_exists($factory, 'pivot' . $relationVector) ? $factory->{'pivot' . $relationVector}() : []; $factory = $factory->hasAttached($factoryChain, $pivot, $relationVector); } else { $factory = $factory->has($factoryChain, $relationVector); } } } else { try { $factory = factory($modelName); } catch (Throwable $e) { if (Str::contains($e->getMessage(), "Call to undefined function Knuckles\\Scribe\\Tools\\factory()")) { throw CouldntFindFactory::forModel($modelName); } else { throw $e; } } if (count($states)) { $factory = $factory->states($states); } } return $factory; } public static function isLumen(): bool { // See https://github.com/laravel/lumen-framework/blob/99330e6ca2198e228f5894cf84d843c2a539a250/src/Application.php#L163 $app = app(); if ($app && is_callable([$app, 'version']) && Str::startsWith($app->version(), 'Lumen') ) { return true; } return false; } }