Skip to content
30 changes: 29 additions & 1 deletion resources/js/wayfinder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export type QueryParams = Record<

type Method = "get" | "post" | "put" | "delete" | "patch" | "head";

let urlDefaults = {};

export type RouteDefinition<TMethod extends Method | Method[]> = {
url: string;
} & (TMethod extends Method[] ? { methods: TMethod } : { method: TMethod });
Expand All @@ -23,7 +25,7 @@ export type RouteFormDefinition<TMethod extends Method> = {
export type RouteQueryOptions = {
query?: QueryParams;
mergeQuery?: QueryParams;
}
};

export const queryParams = (options?: RouteQueryOptions) => {
if (!options || (!options.query && !options.mergeQuery)) {
Expand Down Expand Up @@ -91,6 +93,32 @@ export const queryParams = (options?: RouteQueryOptions) => {
return str.length > 0 ? `?${str}` : "";
};

export const setUrlDefaults = (params: Record<string, unknown>) => {
urlDefaults = params;
};

export const addUrlDefault = (
key: string,
value: string | number | boolean,
) => {
urlDefaults[key] = value;
};

export const applyUrlDefaults = <T>(existing: T): T => {
const existingParams = { ...existing };

for (const key in urlDefaults) {
if (
existingParams[key] === undefined &&
urlDefaults[key] !== undefined
) {
existingParams[key] = urlDefaults[key];
}
}

return existingParams;
};

export const validateParameters = (
args: Record<string, unknown> | undefined,
optional: string[],
Expand Down
2 changes: 2 additions & 0 deletions resources/method.blade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
@endforeach
}
}

args = applyUrlDefaults(args)
@endif

@if ($parameters->where('optional')->isNotEmpty())
Expand Down
17 changes: 15 additions & 2 deletions src/GenerateCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Illuminate\Routing\Router;
use Illuminate\Routing\UrlGenerator;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\Str;
use Illuminate\View\Factory;
use ReflectionProperty;
Expand Down Expand Up @@ -54,7 +55,9 @@ public function handle()
$this->forcedScheme = (new ReflectionProperty($this->url, 'forceScheme'))->getValue($this->url);
$this->forcedRoot = (new ReflectionProperty($this->url, 'forcedRoot'))->getValue($this->url);

$routes = collect($this->router->getRoutes())->map(function (BaseRoute $route) {
$globalUrlDefaults = collect(URL::getDefaultParameters())->map(fn ($v) => is_scalar($v) || is_null($v) ? $v : '');

$routes = collect($this->router->getRoutes())->map(function (BaseRoute $route) use ($globalUrlDefaults) {
$defaults = collect($this->router->gatherRouteMiddleware($route))->map(function ($middleware) {
if ($middleware instanceof \Closure) {
return [];
Expand All @@ -65,7 +68,7 @@ public function handle()
return $this->urlDefaults[$middleware];
})->flatMap(fn ($r) => $r);

return new Route($route, $defaults, $this->forcedScheme, $this->forcedRoot);
return new Route($route, $globalUrlDefaults->merge($defaults), $this->forcedScheme, $this->forcedRoot);
});

if (! $this->option('skip-actions')) {
Expand Down Expand Up @@ -228,6 +231,10 @@ private function appendCommonImports(Collection $routes, string $path, string $n
$imports[] = 'type RouteFormDefinition';
}

if ($routes->contains(fn (Route $route) => $route->parameters()->isNotEmpty())) {
$imports[] = 'applyUrlDefaults';
}

if ($routes->contains(fn (Route $route) => $route->parameters()->contains(fn (Parameter $parameter) => $parameter->optional))) {
$imports[] = 'validateParameters';
}
Expand Down Expand Up @@ -340,6 +347,12 @@ private function getDefaultsForMiddleware(string $middleware)
}

$methodContents = str($methodContents)->after('{')->beforeLast('}')->trim();

return $this->extractUrlDefaults($methodContents);
}

private function extractUrlDefaults(string $methodContents): array
{
$tokens = token_get_all('<?php '.$methodContents);
$foundUrlFacade = false;
$defaults = [];
Expand Down
24 changes: 24 additions & 0 deletions tests/DefaultParameters.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { expect, test } from "vitest";
import {
defaultParametersDomain,
fixedDomain,
} from "../workbench/resources/js/actions/App/Http/Controllers/DomainController";
import { setUrlDefaults } from "../workbench/resources/js/wayfinder";

test("it can generate urls without default parameters set", () => {
expect(fixedDomain.url({ param: "foo" })).toBe(
"//example.test/fixed-domain/foo",
);
});

test("it can generate urls with default URL parameters set on backend and frontend", () => {
setUrlDefaults({
defaultDomain: "tim.macdonald",
});

expect(
defaultParametersDomain.url({
param: "foo",
}),
).toBe("//tim.macdonald.au/default-parameters-domain/foo");
});
27 changes: 17 additions & 10 deletions tests/DomainController.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import { expect, test } from "vitest";
import { dynamicDomain, fixedDomain } from "../workbench/resources/js/actions/App/Http/Controllers/DomainController";
import {
defaultParametersDomain,
fixedDomain,
} from "../workbench/resources/js/actions/App/Http/Controllers/DomainController";

test('can generate fixed domain urls', () => {
expect(fixedDomain.url({ param: 'foo' })).toBe('//example.test/fixed-domain/foo')
})
test("can generate fixed domain urls", () => {
expect(fixedDomain.url({ param: "foo" })).toBe(
"//example.test/fixed-domain/foo",
);
});

test('can generate dynamic domain urls', () => {
expect(dynamicDomain.url({
domain: 'tim.macdonald',
param: 'foo',
})).toBe('//tim.macdonald.au/dynamic-domain/foo')
})
test("can generate dynamic domain urls", () => {
expect(
defaultParametersDomain.url({
defaultDomain: "tim.macdonald",
param: "foo",
}),
).toBe("//tim.macdonald.au/default-parameters-domain/foo");
});
2 changes: 1 addition & 1 deletion workbench/app/Http/Controllers/DomainController.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public function fixedDomain()
//
}

public function dynamicDomain()
public function defaultParametersDomain()
{
//
}
Expand Down
5 changes: 5 additions & 0 deletions workbench/app/Providers/WorkbenchServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace App\Providers;

use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\ServiceProvider;

class WorkbenchServiceProvider extends ServiceProvider
Expand All @@ -20,6 +21,10 @@ public function register(): void
'throw' => false,
],
]);

URL::defaults([
'defaultDomain' => 'tim.macdonald',
]);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion workbench/routes/web.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
Route::get('/parameter-names/{SCREAMING_SNAKE_CASE}/screaming-snake', [ParameterNameController::class, 'screamingSnake']);

Route::domain('example.test')->get('/fixed-domain/{param}', [DomainController::class, 'fixedDomain']);
Route::domain('{domain}.au')->get('/dynamic-domain/{param}', [DomainController::class, 'dynamicDomain']);
Route::domain('{defaultDomain}.au')->get('/default-parameters-domain/{param}', [DomainController::class, 'defaultParametersDomain']);

Route::get('/nested/controller', [NestedController::class, 'nested']);

Expand Down