Skip to content

Pseudo-routing take two #168

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 54 commits into from
Jul 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
6b95bb4
Update test content sources configuration
caendesilva Jul 3, 2022
6b37b5b
Create Router.php
caendesilva Jul 3, 2022
48c17e3
Reorder methods
caendesilva Jul 3, 2022
c94a57a
Add AbstractPage::getSourceFilePath() method
caendesilva Jul 3, 2022
c3c6e31
Normalize PHPDoc return comment
caendesilva Jul 3, 2022
5708dd7
Create RouteContract.php
caendesilva Jul 3, 2022
06f0bef
Create Route.php
caendesilva Jul 3, 2022
7a4ca30
Add constructor to route contract
caendesilva Jul 3, 2022
44075c8
Make the Route class abstract
caendesilva Jul 3, 2022
55927c4
Add PHPDocs
caendesilva Jul 3, 2022
b9ba52c
Rename method to match related output path helper
caendesilva Jul 3, 2022
89e26f5
Implement the method stubs
caendesilva Jul 3, 2022
f16db95
Create RouterContract.php
caendesilva Jul 3, 2022
4bb0a7a
Add the route collection property
caendesilva Jul 3, 2022
3891ee8
Add Router::discover() method
caendesilva Jul 3, 2022
e440266
Revert "Make the Route class abstract"
caendesilva Jul 3, 2022
36ebe3b
Add unique route key property
caendesilva Jul 3, 2022
78402ca
Implement discover() method
caendesilva Jul 4, 2022
deec964
Move module into the proper namespace
caendesilva Jul 4, 2022
a2f16d7
Create RouterTest.php
caendesilva Jul 4, 2022
65062fa
Add Route constructor
caendesilva Jul 4, 2022
c8a41d6
Construct the route key
caendesilva Jul 4, 2022
be75f4a
Replace makeRouteArray with using the Collection put method
caendesilva Jul 4, 2022
e210bef
Annotate the collection contents type
caendesilva Jul 4, 2022
dbfd3d3
Mark discover() method as internal
caendesilva Jul 4, 2022
3d50bd9
Implement route autodiscovery
caendesilva Jul 4, 2022
859fbbf
Unit test discover() helper method
caendesilva Jul 4, 2022
189e6cf
Add helper to assert a collection contains the given route
caendesilva Jul 4, 2022
e0e5ae8
Test Route autodiscovery
caendesilva Jul 4, 2022
ceaab9c
Make internal discover method protected
caendesilva Jul 4, 2022
9d0284a
Merge autodiscovery tests
caendesilva Jul 4, 2022
28d63de
Replace void return with method chaining
caendesilva Jul 4, 2022
e315dbf
Create discoverPageRoutes() helper to dynamically discover routes
caendesilva Jul 4, 2022
46249b6
Add todo tests
caendesilva Jul 4, 2022
3d7e0ef
Add todo: Convert into Singleton
caendesilva Jul 4, 2022
a2684e5
Create RouteTest.php
caendesilva Jul 4, 2022
c63fb46
Create tests for the Route class
caendesilva Jul 4, 2022
b4d6664
Disable global namespace inspection
caendesilva Jul 4, 2022
f7d453a
Define RouteContract::get() and RouteContract::getOrFail() helpers
caendesilva Jul 4, 2022
150b0fe
Create RouteNotFoundException.php
caendesilva Jul 4, 2022
401b215
Merge branch 'master' into pseudo-routing-take-two
caendesilva Jul 4, 2022
62af091
Create pseudo-singleton helper
caendesilva Jul 4, 2022
d2140a2
Annotate the collection contents type
caendesilva Jul 4, 2022
74db284
Remove unused import
caendesilva Jul 4, 2022
002f69f
Add helper method stubs defined in contract
caendesilva Jul 4, 2022
f53b543
Make return value nullable
caendesilva Jul 4, 2022
b209f1e
Update exception message
caendesilva Jul 4, 2022
5207ee0
Implement Route::get() method
caendesilva Jul 4, 2022
ef3c0e1
Implement Route::getOrFail() method
caendesilva Jul 4, 2022
add1c31
Add covers annotation for the exception
caendesilva Jul 4, 2022
975b4a7
Update Router.php
caendesilva Jul 4, 2022
fd9dbfc
Apply fixes from StyleCI
StyleCIBot Jul 4, 2022
07ef55d
Add missing test for AbstractPage::getSourceFilePath()
caendesilva Jul 4, 2022
df4eee3
Add missing test for Router::getInstance()
caendesilva Jul 4, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions .idea/develop.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/inspectionProfiles/Project_Default.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 8 additions & 2 deletions packages/framework/src/Contracts/AbstractPage.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,14 +99,20 @@ public static function getOutputLocation(string $basename): string
public string $slug;

/** @inheritDoc */
public function getCurrentPagePath(): string
public function getSourcePath(): string
{
return trim(static::getOutputDirectory().'/'.$this->slug, '/');
return static::qualifyBasename($this->slug);
}

/** @inheritDoc */
public function getOutputPath(): string
{
return static::getCurrentPagePath().'.html';
}

/** @inheritDoc */
public function getCurrentPagePath(): string
{
return trim(static::getOutputDirectory().'/'.$this->slug, '/');
}
}
21 changes: 14 additions & 7 deletions packages/framework/src/Contracts/PageContract.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,19 +88,26 @@ public static function qualifyBasename(string $basename): string;
public static function getOutputLocation(string $basename): string;

/**
* Get the URI path relative to the site root.
* Get the path to the source file, relative to the project root.
*
* @example if the compiled page will be saved to _site/docs/index.html,
* then this method will return 'docs/index'
*
* @return string URI path relative to the site root.
* @return string Path relative to the project root.
*/
public function getCurrentPagePath(): string;
public function getSourcePath(): string;

/**
* Get the path where the compiled page will be saved.
*
* @return string Relative to the site output directory.
* @return string Path relative to the site output directory.
*/
public function getOutputPath(): string;

/**
* Get the URI path relative to the site root.
*
* @example if the compiled page will be saved to _site/docs/index.html,
* then this method will return 'docs/index'
*
* @return string URI path relative to the site root.
*/
public function getCurrentPagePath(): string;
}
73 changes: 73 additions & 0 deletions packages/framework/src/Modules/Routing/Route.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

namespace Hyde\Framework\Modules\Routing;

use Hyde\Framework\Contracts\PageContract;

/**
* @see \Hyde\Framework\Testing\Feature\RouteTest
*/
class Route implements RouteContract
{
/**
* The source model for the route.
*
* @var \Hyde\Framework\Contracts\PageContract
*/
protected PageContract $sourceModel;

/**
* The unique route key for the route.
*
* @var string The route key. Generally <output-directory/slug>.
*/
protected string $routeKey;

/** @inheritDoc */
public function __construct(PageContract $sourceModel)
{
$this->sourceModel = $sourceModel;
$this->routeKey = $this->constructRouteKey();
}

/** @inheritDoc */
public function getSourceModel(): PageContract
{
return $this->sourceModel;
}

/** @inheritDoc */
public function getRouteKey(): string
{
return $this->routeKey;
}

/** @inheritDoc */
public function getSourceFilePath(): string
{
return $this->sourceModel->getSourcePath();
}

/** @inheritDoc */
public function getOutputFilePath(): string
{
return $this->sourceModel->getOutputPath();
}

/** @inheritDoc */
public static function get(string $routeKey): ?RouteContract
{
return Router::getInstance()->getRoutes()->get($routeKey);
}

/** @inheritDoc */
public static function getOrFail(string $routeKey): RouteContract
{
return static::get($routeKey) ?? throw new RouteNotFoundException($routeKey);
}

protected function constructRouteKey(): string
{
return $this->sourceModel->getCurrentPagePath();
}
}
61 changes: 61 additions & 0 deletions packages/framework/src/Modules/Routing/RouteContract.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

namespace Hyde\Framework\Modules\Routing;

use Hyde\Framework\Contracts\PageContract;

interface RouteContract
{
/**
* Construct a new Route instance for the given page model.
*
* @param \Hyde\Framework\Contracts\PageContract $sourceModel
*/
public function __construct(PageContract $sourceModel);

/**
* Get the source model for the route.
*
* @return \Hyde\Framework\Contracts\PageContract
*/
public function getSourceModel(): PageContract;

/**
* Get the unique route key for the route.
*
* @return string The route key. Generally <output-directory/slug>.
*/
public function getRouteKey(): string;

/**
* Get the path to the source file.
*
* @return string Path relative to the root of the project.
*/
public function getSourceFilePath(): string;

/**
* Get the path to the output file.
*
* @return string Path relative to the site output directory.
*/
public function getOutputFilePath(): string;

/**
* Get a route from the Router index for the specified route key.
*
* @param string $routeKey
* @return \Hyde\Framework\Modules\Routing\RouteContract|null
*/
public static function get(string $routeKey): ?RouteContract;

/**
* Same as static::get(), but throws an exception if the route key is not found.
*
* @param string $routeKey
* @return \Hyde\Framework\Modules\Routing\RouteContract
*
* @throws \Hyde\Framework\Modules\Routing\RouteNotFoundException
*/
public static function getOrFail(string $routeKey): RouteContract;
}
18 changes: 18 additions & 0 deletions packages/framework/src/Modules/Routing/RouteNotFoundException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Hyde\Framework\Modules\Routing;

class RouteNotFoundException extends \Exception
{
protected $message = 'Route not found.';
protected $code = 404;

public function __construct(?string $routeKey = null)
{
if ($routeKey) {
$this->message = "Route not found: '$routeKey'";
}

parent::__construct($this->message, $this->code);
}
}
99 changes: 99 additions & 0 deletions packages/framework/src/Modules/Routing/Router.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<?php

namespace Hyde\Framework\Modules\Routing;

use Hyde\Framework\Contracts\PageContract;
use Hyde\Framework\Models\Pages\BladePage;
use Hyde\Framework\Models\Pages\DocumentationPage;
use Hyde\Framework\Models\Pages\MarkdownPage;
use Hyde\Framework\Models\Pages\MarkdownPost;
use Illuminate\Support\Collection;

/**
* Pseudo-Router for Hyde.
*
* This is not a router in the traditional sense that it decides where to go.
* Instead, it creates a pre-generated object encapsulating the Hyde autodiscovery.
*
* If successful, this will not only let us emulate Laravel route helpers, but also
* serve as the canonical source of truth for the Hyde autodiscovery process.
*
* The routes defined can then also be used to power the RealtimeCompiler without
* having to reverse-engineer the source file mapping.
*
* Routes cannot be added manually, instead the route index is created using the
* exact same rules as the current autodiscovery process and compiled file output.
*
* The route index shall serve as a multidimensional mapping allowing you to
* determine where a source file will be compiled to, and where a compiled
* file was generated from.
*
* @todo Convert into Singleton.
* As the constructor needs to load and parse every single page,
* this could be a rather expensive operation that would benefit
* from being a singleton. It could also be a good idea to cache
* the parsed route index using the Laravel cache in the future.
*
* @see \Hyde\Framework\Testing\Feature\RouterTest
*/
class Router implements RouterContract
{
/**
* The routes discovered by the router.
*
* @var \Illuminate\Support\Collection<\Hyde\Framework\Modules\Routing\RouteContract>
*/
protected Collection $routes;

/** @inheritDoc */
public function __construct()
{
$this->discoverRoutes();
}

/** @inheritDoc */
public static function getInstance(): RouterContract
{
return new static();
}

/** @inheritDoc */
public function getRoutes(): Collection
{
return $this->routes;
}

protected function discover(PageContract $page): self
{
$route = new Route($page);
$this->routes->put($route->getRouteKey(), $route);

return $this;
}

protected function discoverRoutes(): self
{
$this->routes = new Collection();

$pages = [
BladePage::class,
MarkdownPage::class,
MarkdownPost::class,
DocumentationPage::class,
];

foreach ($pages as $page) {
$this->discoverPageRoutes($page);
}

return $this;
}

protected function discoverPageRoutes(string $pageClass): void
{
/** @var PageContract $pageClass */
$pageClass::all()->each(function ($page) {
$this->discover($page);
});
}
}
27 changes: 27 additions & 0 deletions packages/framework/src/Modules/Routing/RouterContract.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace Hyde\Framework\Modules\Routing;

use Illuminate\Support\Collection;

interface RouterContract
{
/**
* Construct a new Router instance and discover all routes.
*/
public function __construct();

/**
* Get the Singleton instance of the Router.
*
* @return \Hyde\Framework\Modules\Routing\RouterContract
*/
public static function getInstance(): RouterContract;

/**
* Get the routes discovered by the router.
*
* @return \Illuminate\Support\Collection<\Hyde\Framework\Modules\Routing\RouteContract>
*/
public function getRoutes(): Collection;
}
8 changes: 8 additions & 0 deletions packages/framework/tests/Feature/AbstractPageTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,14 @@ public function test_get_output_path_returns_current_page_path_with_html_extensi
$this->assertEquals('foo.html', $page->getOutputPath());
}

public function test_get_source_path_returns_qualified_basename()
{
$this->assertEquals(
MarkdownPage::qualifyBasename('foo'),
(new MarkdownPage(slug: 'foo'))->getSourcePath()
);
}

public function test_markdown_page_implements_page_contract()
{
$this->assertInstanceOf(PageContract::class, new class extends AbstractPage {});
Expand Down
Loading