Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

codercotton's avatar

404 on Spark route

I have a simple Laravel Spark app, just built this past week so on latest or close to latest packages, and am trying to copy the /docs code from laravel.com, which is on GitHub. These are basically copied directly from that repository, with version array changed.

The issue is that visiting /docs/VERSION returns a 404. When visiting the root /docs, it hits the showRootPage() function and forwards to /docs/0.1 appropriately - but this is the route that ends in the 404. All the docs/0.1/xyz routes work fine - for instance the file /resources/docs/0.1/documentation.md exists and URL /docs/0.1/documentation works as expected. Only the docs/VERSION route is having an issue.

I see code in the show() method to send this to a default, but it never gets there... You can see I have a dd() in the show() method on DocsController. It never reaches this point when returning the 404, so it seems like a routing issue.

Any ideas as to the problem would be much appreciated!

Here is my routes/web.php

<?php

// srvAudit.com
Route::get('/', 'WelcomeController@show');
Route::get('/blog', 'WelcomeController@blog');
Route::get('/support', 'WelcomeController@support');

// The docs
define('DEFAULT_VERSION', '0.1');
function markdown($text)
{
    return (new ParsedownExtra)->text($text);
}
Route::get('docs', 'DocsController@showRootPage');
Route::get('docs/{version}/{page?}', 'DocsController@show');

// The app
Route::get('/home', 'SessionController@index');
Route::get('/sessions', 'SessionController@listSessions');
Route::get('/sessions/{id}', 'SessionController@getSession');
Route::get('/sessions/server/{id}', 'SessionController@getServers');
Route::get('/sessions/user/{id}', 'SessionController@getUser');

Here are the pertinent routes

root@312fedf0dc89:/var/www/test# artisan route:list|grep docs
|        | GET|HEAD | docs                                            |                | App\Http\Controllers\DocsController@showRootPage                                           | web,hasTeam            |
|        | GET|HEAD | docs/{version}/{page?}                          |                | App\Http\Controllers\DocsController@show                                                   | web,hasTeam            |

DocsController.php

<?php

namespace App\Http\Controllers;

use App\Documentation;
use Symfony\Component\DomCrawler\Crawler;

class DocsController extends Controller
{
    /**
     * The documentation repository.
     *
     * @var Documentation
     */
    protected $docs;

    /**
     * Create a new controller instance.
     *
     * @param  Documentation  $docs
     * @return void
     */
    public function __construct(Documentation $docs)
    {
        $this->docs = $docs;
    }

    /**
     * Show the root documentation page (/docs).
     *
     * @return Response
     */
    public function showRootPage()
    {
        return redirect('docs/'.DEFAULT_VERSION);
    }

    /**
     * Show a documentation page.
     *
     * @param  string $version
     * @param  string|null $page
     * @return Response
     */
    public function show($version, $page = null)
    {
dd($version);
        if (! $this->isVersion($version)) {
            return redirect('docs/'.DEFAULT_VERSION.'/'.$page, 301);
        }

        if (! defined('CURRENT_VERSION')) {
            define('CURRENT_VERSION', $version);
        }

        $sectionPage = $page ?: 'srvAudit';
        $content = $this->docs->get($version, $sectionPage);

        if (is_null($content)) {
            abort(404);
        }

        $title = (new Crawler($content))->filterXPath('//h1');

        $section = '';

        if ($this->docs->sectionExists($version, $page)) {
            $section .= '/'.$page;
        } elseif (! is_null($page)) {
            return redirect('/docs/'.$version);
        }

        $canonical = null;

        if ($this->docs->sectionExists(DEFAULT_VERSION, $sectionPage)) {
            $canonical = 'docs/'.DEFAULT_VERSION.'/'.$sectionPage;
        }

        return view('docs', [
            'title' => count($title) ? $title->text() : null,
            'index' => $this->docs->getIndex($version),
            'content' => $content,
            'currentVersion' => $version,
            'versions' => Documentation::getDocVersions(),
            'currentSection' => $section,
            'canonical' => $canonical,
        ]);
    }

    /**
     * Determine if the given URL segment is a valid version.
     *
     * @param  string  $version
     * @return bool
     */
    protected function isVersion($version)
    {
        return in_array($version, array_keys(Documentation::getDocVersions()));
    }
}

And the Documentation.php model

<?php

namespace App;

use Illuminate\Filesystem\Filesystem;
use Illuminate\Contracts\Cache\Repository as Cache;

class Documentation
{
    /**
     * The filesystem implementation.
     *
     * @var Filesystem
     */
    protected $files;

    /**
     * The cache implementation.
     *
     * @var Cache
     */
    protected $cache;

    /**
     * Create a new documentation instance.
     *
     * @param  Filesystem  $files
     * @param  Cache  $cache
     * @return void
     */
    public function __construct(Filesystem $files, Cache $cache)
    {
        $this->files = $files;
        $this->cache = $cache;
    }

    /**
     * Get the documentation index page.
     *
     * @param  string  $version
     * @return string
     */
    public function getIndex($version)
    {
        return $this->cache->remember('docs.'.$version.'.index', 5, function () use ($version) {
            $path = base_path('resources/docs/'.$version.'/srvAudit.md');

            if ($this->files->exists($path)) {
                return $this->replaceLinks($version, markdown($this->files->get($path)));
            }

            return null;
        });
    }

    /**
     * Get the given documentation page.
     *
     * @param  string  $version
     * @param  string  $page
     * @return string
     */
    public function get($version, $page)
    {
        return $this->cache->remember('docs.'.$version.'.'.$page, 5, function () use ($version, $page) {
            $path = base_path('resources/docs/'.$version.'/'.$page.'.md');

            if ($this->files->exists($path)) {
                return $this->replaceLinks($version, markdown($this->files->get($path)));
            }

            return null;
        });
    }

    /**
     * Replace the version place-holder in links.
     *
     * @param  string  $version
     * @param  string  $content
     * @return string
     */
    public static function replaceLinks($version, $content)
    {
        return str_replace('{{version}}', $version, $content);
    }

    /**
     * Check if the given section exists.
     *
     * @param  string  $version
     * @param  string  $page
     * @return boolean
     */
    public function sectionExists($version, $page)
    {
        return $this->files->exists(
            base_path('resources/docs/'.$version.'/'.$page.'.md')
        );
    }

    /**
     * Get the publicly available versions of the documentation
     *
     * @return array
     */
    public static function getDocVersions()
    {
        return [
            '0.1' => '0.1',
        ];
    }
}
0 likes
0 replies

Please or to participate in this conversation.