[ 'rangeSlug' => [Range::class, 'slug'] ], 'products.tagfilter' => [ 'tagSlug' => [Tag::class, 'slug'] ], 'product' => [ 'productSlug' => [Product::class, 'slug'] ], ]; /** * Execute the job. * * @return void * @phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh */ public function handle() { // @phpcs:enable $this->assertRouteBinderIsCorrect(); $siteMap = []; $routeCollection = Route::getRoutes(); /** @var \Illuminate\Routing\Route $route */ foreach ($routeCollection as $route) { if ($this->routeShouldBeIgnored($route)) { continue; } if (!$this->routeHandlesGetMethod($route)) { continue; } /** @var array to be populated with models from DB to generate a route */ $rParams = []; /** @var \array [lluminate\Routing\Route::signatureParameters] parameters signature from the controller */ $params = $route->signatureParameters(); /** @var \array [Illuminate\Routing\Route::parameterNames] parameters name from the registered route */ $pNames = $route->parameterNames(); $hasModels = false; foreach ($params as $param) { /** @var ReflectionClass|null $name */ $name = $param->getType() && !$param->getType()->isBuiltin() ? new ReflectionClass($param->getType()->getName()) : null; /** @var string $pName $parameter name on route uri */ $pName = ''; // If the route has parameters and the controller accepts it. if (count($pNames) and \array_key_exists($param->getPosition(), $pNames)) { // Get the controller parameter name for the route. $pName = $pNames[$param->getPosition()]; } // If We have a custom binder for this route. if ($this->hasRouteBinderParam($route, $pName)) { $hasModels = true; $this->processWithRouteBinderParam($rParams, $route, $pName); continue; } // If This route takes a model as param. if ($name and $name->isSubclassOf(Model::class)) { $hasModels = true; /** @var \string */ $modelClassName = $name->getName(); $this->setParamsForModel($rParams, $modelClassName, $pName); continue; } }//end foreach // If the route has no dynamic models as parameters. if (!count($params) or !$hasModels) { $siteMap[] = [ route($route->getName()), Url::CHANGE_FREQUENCY_MONTHLY, 0.1 ]; } else { // If the route has dynamic models as parameters. $this->genRoute($route, $siteMap, $rParams); } }//end foreach $sm = Sitemap::create(); foreach ($siteMap as $smRules) { $sm->add(Url::create($smRules[0]) ->setLastModificationDate(Carbon::yesterday()) ->setChangeFrequency($smRules[1]) ->setPriority($smRules[2])); } $sm->writeToFile(\public_path('sitemap.xml')); } /** * Process route with binderParam * * @param array $rParams * @param RoutingRoute $route * @param string $pName * @return void * @throws Exception When unhandled class. */ private function processWithRouteBinderParam(array &$rParams, RoutingRoute $route, string $pName) { $o = $this->getRouteBinderParam($route, $pName); /** @var \string */ $modelClassName = $o[0]; /** @var \string */ $routeParamName = $o[1]; $rfLCls = (new ReflectionClass($modelClassName)); switch (true) { case $rfLCls->isSubclassOf(Model::class): $this->setParamsForModel($rParams, $modelClassName, $pName, $routeParamName); break; case $rfLCls->isSubclassOf(BaseEnum::class): $this->setParamForEnum($rParams, $rfLCls->getName(), $pName); break; default: throw new Exception(sprintf('Unhandled class type %s', $rfLCls->getName())); } } /** * Set route param Array for Enums * * @param array $rParams * @param string $enumClassName * @param string $pName * @return void */ private function setParamForEnum(array &$rParams, string $enumClassName, string $pName) { foreach (forward_static_call(sprintf('%s::toArray', $enumClassName)) as $enum) { if (!array_key_exists($pName, $rParams)) { $rParams[$pName] = []; } $rParams[$pName][] = $enum->value; } } /** * Set route param Array for Models * * @param array $rParams * @param string $modelClassName * @param string $pName * @param string $routeParamName * @return void */ private function setParamsForModel( array &$rParams, string $modelClassName, string $pName, string $routeParamName = null ) { $i = 0; $limit = 10; $modelTable = (new ReflectionClass($modelClassName))->newInstanceWithoutConstructor()->getTable(); $q = $modelClassName::query(); $q = Schema::hasColumn($modelTable, 'status') ? $q->where('status', true) : $q; $q = Schema::hasColumn($modelTable, 'star_moment') ? $q->where('star_moment', true) : $q; do { $results = $q->offset($i * $limit)->limit($limit)->get(); $length = count($results); $results->map(function (Model $model) use (&$rParams, $pName, $routeParamName) { if (!array_key_exists($pName, $rParams)) { $rParams[$pName] = []; } if ($routeParamName) { $rParams[$pName][] = $model[$routeParamName]; } else { $rParams[$pName][] = $model; } }); $i++; } while ($length); } /** * Recursively generate routes for all params * * @param RoutingRoute $route * @param array $siteMap * @param array $params * @return void */ private function genRoute(RoutingRoute $route, array &$siteMap, array $params) { $filtered = false; foreach ($params as $pName => $param) { if (is_array($param)) { collect($param)->map(function ($p) use ($route, &$siteMap, $params, $pName) { $this->genRoute($route, $siteMap, array_merge($params, [$pName => $p])); }); return; } $filtered = array_key_last($params) === $pName ? true : false; } if ($filtered) { $siteMap[] = [ route($route->getName(), $params), Url::CHANGE_FREQUENCY_DAILY, 0.1 ]; } } /** * Check if $route exists in routesBinder * * @param RoutingRoute $route * @return boolean */ private function hasRouteBinder(RoutingRoute $route): bool { return array_key_exists($route->getName(), $this->routesBinder); } /** * Check if $route exists in routesBinder and has $param * * @param RoutingRoute $route * @param string $param * @return boolean */ private function hasRouteBinderParam(RoutingRoute $route, string $param): bool { if ($this->hasRouteBinder($route)) { return array_key_exists($param, $this->getRouteBinder($route)); } return false; } /** * Get the routeBinder * * @param RoutingRoute $route * @return array */ private function getRouteBinder(RoutingRoute $route): array { return $this->routesBinder[$route->getName()]; } /** * Get the routeBinder param * * @param RoutingRoute $route * @param string $param * @return array */ private function getRouteBinderParam(RoutingRoute $route, string $param): array { return $this->routesBinder[$route->getName()][$param]; } /** * Assert that routeBinder is correct * * @throws Exception When route does not exist | incomplrte parameters | parameter is not a model class. * @return void */ private function assertRouteBinderIsCorrect() { /** @var \Illuminate\Routing\RouteCollection */ $routeCollection = Route::getRoutes(); foreach ($this->routesBinder as $routeName => $params) { if (!$routeCollection->hasNamedRoute($routeName)) { throw new Exception(sprintf('Route %s does not exists', $routeName)); } foreach ($params as $pName => $p) { if (count($p) != 2) { throw new Exception(sprintf( 'Route %s parameter %s does not have Model classname and parameter name', $routeName, $pName )); } $rfCls = (new ReflectionClass($p[0]))->newInstanceWithoutConstructor(); if (is_subclass_of($rfCls, BaseEnum::class)) { return; } if (!is_subclass_of($rfCls, Model::class)) { throw new Exception(sprintf( 'Route "%s" parameter "%s" first element must be a child of "%s"', $routeName, $pName, Model::class )); } if (!Schema::hasColumn($rfCls->getTable(), $p[1])) { throw new Exception(sprintf( '"%s" parameter "%s" second element "%s" must refer to and existing database column of "%s"', $routeName, $pName, $p[1], $p[0] )); } }//end foreach }//end foreach } /** * Check if the route handles the HTTP GET method * * @param RoutingRoute $route * @return boolean */ private function routeHandlesGetMethod(RoutingRoute $route): bool { return in_array('GET', $route->methods()); } /** * Check if the actual route uri start with * a partial uri from the table * Check also if it uses middleware auth * * @param RoutingRoute $route * @return boolean */ private function routeShouldBeIgnored(RoutingRoute $route): bool { foreach ($this->ignoreRoutes as $iRoute) { if (strpos($route->uri(), ltrim($iRoute, '/')) === 0) { return true; } } foreach ($route->middleware() as $middleware) { if (strpos($middleware, 'auth') === 0) { return true; } } return false; } }