> */ public static function getRulesFromDocBlock( Type $type, string $prefix, ): array { $rules = []; if ($type instanceof Nullable) { $rules[$prefix] = ['nullable']; $rules = array_merge($rules, self::getRulesFromDocBlock($type->getActualType(), $prefix)); } else { $rules[$prefix] = ['required']; } if ($type instanceof AbstractList) { $rules[$prefix][] = 'array'; $valueType = $type->getValueType(); $rules = array_merge($rules, self::getRulesFromDocBlock($valueType, $prefix . '.*')); } if ($type instanceof Boolean) { $rules[$prefix][] = 'boolean'; } elseif ($type instanceof Float_ || $type instanceof Integer) { $rules[$prefix][] = 'numeric'; } elseif ($type instanceof Object_) { $paramsSub = self::getParametersMeta($type->getFqsen()->__toString()); $rules = array_merge( $rules, self::infer($paramsSub, $prefix . '.'), ); } return $rules; } /** * @param class-string $class * @return array */ public static function getParametersMeta(string $class): array { if (array_key_exists($class, self::$cache)) { return self::$cache[$class]; } $reflection = new ReflectionClass($class); $constructor = $reflection->getConstructor(); try { $docblockParams = (DocBlockFactory::createInstance())->create( $constructor->getDocComment(), (new ContextFactory())->createFromReflector($constructor), )->getTagsByName('param'); } catch (\Exception) { $docblockParams = []; } self::$cache[$class] = array_map( fn(ReflectionParameter $p) => new ParameterMeta( $p, array_find( $docblockParams, fn(Tag $tag) => $tag instanceof Param ? $tag->getVariableName() == $p->getName() : false, ), ), $constructor->getParameters(), ); return self::$cache[$class]; } /** * @param array $parameters * @return array> */ public static function infer(array $parameters, string $prefix): array { $rules = []; foreach ($parameters as $parameter) { foreach (self::buildParameterRule($parameter, $prefix) as $key => $newRules) { $rules[$key] = $newRules; } } return $rules; } /** * @return array> */ public static function buildParameterRule(ParameterMeta $parameter, string $prefix): array { $type = $parameter->reflection->getType(); $root = $prefix . $parameter->reflection->getName(); if (empty($type)) { return [$root => $parameter->reflection->isOptional() ? ['sometimes'] : ['required']]; } $rules = [$root => []]; if ($parameter->reflection->isOptional()) { $rules[$root][] = 'sometimes'; } elseif ($type->allowsNull()) { $rules[$root][] = 'nullable'; } else { $rules[$root][] = 'required'; } if ($type instanceof ReflectionUnionType) { //TODO: handle ReflectionUnionType return $rules; } if ($type instanceof ReflectionNamedType && $name = $type->getName()) { if ($globalRules = Config::getRules($name)) { foreach ($globalRules($parameter->reflection, $parameter->tag->getType()) as $scopedPrefix => $values) { $realPrefix = $root . $scopedPrefix; $rules[$realPrefix] = array_values(array_unique(array_merge($rules[$realPrefix] ?? [], $values))); } } if ($name === 'string') { } elseif ($name === 'bool') { $rules[$root][] = 'boolean'; } elseif ($name === 'int' || $name === 'float') { $rules[$root][] = 'numeric'; } elseif ($name === 'array') { $rules[$root][] = 'array'; } elseif (enum_exists($name)) { $ref = new ReflectionClass($name); if ($ref->isSubclassOf(BackedEnum::class)) { $rules[$root][] = Rule::enum($name); } } else { $paramsSub = self::getParametersMeta($type->getName()); $rules = array_merge( $rules, self::infer($paramsSub, $root . '.'), ); } } foreach ($parameter->reflection->getAttributes(CastWith::class) as $attr) { $mapperClass = $attr->newInstance()->class; if (method_exists($mapperClass, 'rules')) { $subRules = App::call("$mapperClass@rules"); foreach ($subRules as $key => &$value) { $path = empty($key) ? $root : ($root . '.' . $key); $rules[$path] = array_values(array_unique(array_merge($rules[$path] ?? [], $value))); } } } if ($parameter->tag instanceof Param) { $docblockRules = self::getRulesFromDocBlock( $parameter->tag->getType(), $prefix . $parameter->reflection->getName(), ); foreach ($docblockRules as $key => &$values) { $rules[$key] = array_values(array_unique(array_merge($rules[$key] ?? [], $values))); } } return $rules; } }