Skip to content

Commit 412c1d0

Browse files
authored
Merge branch 'master' into add-log-bootstrapper
2 parents c180c2c + 3b42c9e commit 412c1d0

33 files changed

+1165
-157
lines changed

.github/workflows/validate.yml

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,8 @@ jobs:
88
runs-on: ubuntu-latest
99
steps:
1010
- uses: actions/checkout@v2
11-
- name: Check for todo0
12-
run: '! grep -r "todo0" --exclude-dir=workflows .'
13-
if: always()
14-
- name: Check for todo1
15-
run: '! grep -r "todo1" --exclude-dir=workflows .'
16-
if: always()
17-
- name: Check for todo2
18-
run: '! grep -r "todo2" --exclude-dir=workflows .'
11+
- name: Check for priority todos
12+
run: '! grep -r "todo[0-9]" --exclude-dir=workflows .'
1913
if: always()
2014
- name: Check for non-todo skip()s in tests
2115
run: '! grep -r "skip(" --exclude-dir=workflows tests/ | grep -v "todo"'

.php-cs-fixer.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
'blank_line_before_statement' => [
2222
'statements' => ['return']
2323
],
24-
'braces' => true,
2524
'cast_spaces' => true,
2625
'class_definition' => true,
2726
'concat_space' => [

CONTRIBUTING.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,9 @@ If you need to rebuild the container for any reason (e.g. a change in `Dockerfil
4343
## PHPStan
4444

4545
Use `composer phpstan` to run our phpstan suite.
46+
47+
## PhpStorm
48+
49+
Create `.env` with `PROJECT_PATH=/full/path/to/this/directory`. Configure a Docker-based interpreter for tests (with exec, not run).
50+
51+
If you want to use XDebug, use `composer docker-rebuild-with-xdebug`.

Dockerfile

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,16 @@ RUN echo "apc.enable_cli=1" >> "$PHP_INI_DIR/php.ini"
3030
# Only used on GHA
3131
COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer
3232

33+
# Conditionally install and configure Xdebug (last step for faster rebuilds)
34+
ARG XDEBUG_ENABLED=false
35+
RUN if [ "$XDEBUG_ENABLED" = "true" ]; then \
36+
pecl install xdebug && docker-php-ext-enable xdebug && \
37+
echo "xdebug.mode=debug" >> "$PHP_INI_DIR/conf.d/docker-php-ext-xdebug.ini" && \
38+
echo "xdebug.start_with_request=yes" >> "$PHP_INI_DIR/conf.d/docker-php-ext-xdebug.ini" && \
39+
echo "xdebug.client_host=host.docker.internal" >> "$PHP_INI_DIR/conf.d/docker-php-ext-xdebug.ini" && \
40+
echo "xdebug.client_port=9003" >> "$PHP_INI_DIR/conf.d/docker-php-ext-xdebug.ini" && \
41+
echo "xdebug.discover_client_host=true" >> "$PHP_INI_DIR/conf.d/docker-php-ext-xdebug.ini" && \
42+
echo "xdebug.log=/var/log/xdebug.log" >> "$PHP_INI_DIR/conf.d/docker-php-ext-xdebug.ini"; \
43+
fi
44+
3345
WORKDIR /var/www/html

assets/config.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,14 @@
9393
* Identification middleware tenancy recognizes as path identification middleware.
9494
*
9595
* This is used for determining if a path identification middleware is used
96-
* during operations specific to path identification, e.g. forgetting the tenant parameter in ForgetTenantParameter.
96+
* during operations specific to path identification.
97+
*
98+
* This is used for forgetting the tenant parameter using the ForgetTenantParameter listener.
99+
* The listener only has an effect when path identification middleware
100+
* is used in the global middleware stack and certain other conditions are met.
97101
*
98102
* If you're using a custom path identification middleware, add it here.
99103
*
100-
* @see \Stancl\Tenancy\Actions\CloneRoutesAsTenant
101104
* @see \Stancl\Tenancy\Listeners\ForgetTenantParameter
102105
*/
103106
'path_identification_middleware' => [

composer.json

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
"illuminate/support": "^12.0",
2222
"laravel/tinker": "^2.0",
2323
"ramsey/uuid": "^4.7.3",
24-
"stancl/jobpipeline": "2.0.0-rc5",
24+
"stancl/jobpipeline": "2.0.0-rc6",
2525
"stancl/virtualcolumn": "^1.5.0",
2626
"spatie/invade": "*",
2727
"laravel/prompts": "0.*"
@@ -63,7 +63,14 @@
6363
"docker-up": "docker compose up -d",
6464
"docker-down": "docker compose down",
6565
"docker-restart": "docker compose down && docker compose up -d",
66-
"docker-rebuild": "PHP_VERSION=8.4 docker compose up -d --no-deps --build",
66+
"docker-rebuild": [
67+
"Composer\\Config::disableProcessTimeout",
68+
"PHP_VERSION=8.4 docker compose up -d --no-deps --build"
69+
],
70+
"docker-rebuild-with-xdebug": [
71+
"Composer\\Config::disableProcessTimeout",
72+
"PHP_VERSION=8.4 XDEBUG_ENABLED=true docker compose up -d --no-deps --build"
73+
],
6774
"docker-m1": "ln -s docker-compose-m1.override.yml docker-compose.override.yml",
6875
"testbench-unlink": "rm ./vendor/orchestra/testbench-core/laravel/vendor",
6976
"testbench-link": "ln -s /var/www/html/vendor ./vendor/orchestra/testbench-core/laravel/vendor",
@@ -72,10 +79,22 @@
7279
"phpstan": "vendor/bin/phpstan --memory-limit=256M",
7380
"phpstan-pro": "vendor/bin/phpstan --memory-limit=256M --pro",
7481
"cs": "PHP_CS_FIXER_IGNORE_ENV=1 php-cs-fixer fix --config=.php-cs-fixer.php",
75-
"test": "./test --no-coverage",
76-
"test-full": "./test",
77-
"act": "act -j tests --matrix 'laravel:^11.0'",
78-
"act-input": "act -j tests --matrix 'laravel:^11.0' --input"
82+
"test": [
83+
"Composer\\Config::disableProcessTimeout",
84+
"./test --no-coverage"
85+
],
86+
"test-full": [
87+
"Composer\\Config::disableProcessTimeout",
88+
"./test"
89+
],
90+
"act": [
91+
"Composer\\Config::disableProcessTimeout",
92+
"act -j tests --matrix 'laravel:^11.0'"
93+
],
94+
"act-input": [
95+
"Composer\\Config::disableProcessTimeout",
96+
"act -j tests --matrix 'laravel:^11.0' --input"
97+
]
7998
},
8099
"minimum-stability": "dev",
81100
"prefer-stable": true,

docker-compose.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ services:
22
test:
33
build:
44
context: .
5+
args:
6+
XDEBUG_ENABLED: ${XDEBUG_ENABLED:-false}
57
depends_on:
68
mysql:
79
condition: service_healthy
@@ -18,7 +20,8 @@ services:
1820
dynamodb:
1921
condition: service_healthy
2022
volumes:
21-
- .:/var/www/html:cached
23+
- .:${PROJECT_PATH:-$PWD}:cached
24+
working_dir: ${PROJECT_PATH:-$PWD}
2225
environment:
2326
DOCKER: 1
2427
DB_PASSWORD: password
@@ -30,6 +33,8 @@ services:
3033
TENANCY_TEST_SQLSRV_HOST: mssql
3134
TENANCY_TEST_SQLSRV_USERNAME: sa
3235
TENANCY_TEST_SQLSRV_PASSWORD: P@ssword
36+
extra_hosts:
37+
- "host.docker.internal:host-gateway"
3338
stdin_open: true
3439
tty: true
3540
mysql:

src/Actions/CloneRoutesAsTenant.php

Lines changed: 70 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@
3030
* By providing a callback to shouldClone(), you can change how it's determined if a route should be cloned if you don't want to use middleware flags.
3131
*
3232
* Cloned routes are prefixed with '/{tenant}', flagged with 'tenant' middleware, and have their names prefixed with 'tenant.'.
33+
*
34+
* The addition of the tenant parameter can be controlled using addTenantParameter(true|false). Note that if you decide to disable
35+
* tenant parameter addition, the routes MUST differ in domains. This can be controlled using the domain(string|null) method. The
36+
* default behavior is to NOT set any domains on cloned routes -- unless specified otherwise using that method.
37+
*
3338
* The parameter name and prefix can be changed e.g. to `/{team}` and `team.` by configuring the path resolver (tenantParameterName and tenantRouteNamePrefix).
3439
* Routes with names that are already prefixed won't be cloned - but that's just the default behavior.
3540
* The cloneUsing() method allows you to completely override the default behavior and customize how the cloned routes will be defined.
@@ -43,8 +48,8 @@
4348
*
4449
* Example usage:
4550
* ```
46-
* Route::get('/foo', fn () => true)->name('foo')->middleware('clone');
47-
* Route::get('/bar', fn () => true)->name('bar')->middleware('universal');
51+
* Route::get('/foo', ...)->name('foo')->middleware('clone');
52+
* Route::get('/bar', ...)->name('bar')->middleware('universal');
4853
*
4954
* $cloneAction = app(CloneRoutesAsTenant::class);
5055
*
@@ -54,10 +59,20 @@
5459
* // Clone bar route as /{tenant}/bar and name it tenant.bar ('universal' middleware won't be present in the cloned route)
5560
* $cloneAction->cloneRoutesWithMiddleware(['universal'])->handle();
5661
*
57-
* Route::get('/baz', fn () => true)->name('baz');
62+
* Route::get('/baz', ...)->name('baz');
5863
*
5964
* // Clone baz route as /{tenant}/bar and name it tenant.baz ('universal' middleware won't be present in the cloned route)
6065
* $cloneAction->cloneRoute('baz')->handle();
66+
*
67+
* Route::domain('central.localhost')->get('/no-tenant-parameter', ...)->name('no-tenant-parameter')->middleware('clone');
68+
*
69+
* // Clone baz route as /no-tenant-parameter and name it tenant.no-tenant-parameter (the route won't have the tenant parameter)
70+
* // This can be useful with domain identification. Importantly, the original route MUST have a domain set. The domain of the
71+
* // cloned route can be customized using domain(string|null). By default, the cloned route will not be scoped to a domain,
72+
* // unless a domain() call is used. It's important to keep in mind that:
73+
* // 1. When addTenantParameter(false) is used, the paths will be the same, thus domains must differ.
74+
* // 2. If the original route (with the same path) has no domain, the cloned route will never be used due to registration order.
75+
* $cloneAction->addTenantParameter(false)->cloneRoutesWithMiddleware(['clone'])->cloneRoute('no-tenant-parameter')->handle();
6176
* ```
6277
*
6378
* Calling handle() will also clear the $routesToClone array.
@@ -70,6 +85,8 @@
7085
class CloneRoutesAsTenant
7186
{
7287
protected array $routesToClone = [];
88+
protected bool $addTenantParameter = true;
89+
protected string|null $domain = null;
7390
protected Closure|null $cloneUsing = null; // The callback should accept Route instance or the route name (string)
7491
protected Closure|null $shouldClone = null;
7592
protected array $cloneRoutesWithMiddleware = ['clone'];
@@ -78,6 +95,7 @@ public function __construct(
7895
protected Router $router,
7996
) {}
8097

98+
/** Clone routes. This resets routesToClone() but not other config. */
8199
public function handle(): void
82100
{
83101
// If no routes were specified using cloneRoute(), get all routes
@@ -102,36 +120,64 @@ public function handle(): void
102120
$route = $this->router->getRoutes()->getByName($route);
103121
}
104122

105-
$this->copyMiscRouteProperties($route, $this->createNewRoute($route));
123+
$this->createNewRoute($route);
106124
}
107125

108126
// Clean up the routesToClone array after cloning so that subsequent calls aren't affected
109127
$this->routesToClone = [];
110128

111129
$this->router->getRoutes()->refreshNameLookups();
130+
$this->router->getRoutes()->refreshActionLookups();
112131
}
113132

133+
/**
134+
* Should a tenant parameter be added to the cloned route.
135+
*
136+
* The name of the parameter is controlled using PathTenantResolver::tenantParameterName().
137+
*/
138+
public function addTenantParameter(bool $addTenantParameter): static
139+
{
140+
$this->addTenantParameter = $addTenantParameter;
141+
142+
return $this;
143+
}
144+
145+
/** The domain the cloned route should use. Set to null if it shouldn't be scoped to a domain. */
146+
public function domain(string|null $domain): static
147+
{
148+
$this->domain = $domain;
149+
150+
return $this;
151+
}
152+
153+
/** Provide a custom callback for cloning routes, instead of the default behavior. */
114154
public function cloneUsing(Closure|null $cloneUsing): static
115155
{
116156
$this->cloneUsing = $cloneUsing;
117157

118158
return $this;
119159
}
120160

161+
/** Specify which middleware should serve as "flags" telling this action to clone those routes. */
121162
public function cloneRoutesWithMiddleware(array $middleware): static
122163
{
123164
$this->cloneRoutesWithMiddleware = $middleware;
124165

125166
return $this;
126167
}
127168

169+
/**
170+
* Provide a custom callback for determining whether a route should be cloned.
171+
* Overrides the default middleware-based detection.
172+
* */
128173
public function shouldClone(Closure|null $shouldClone): static
129174
{
130175
$this->shouldClone = $shouldClone;
131176

132177
return $this;
133178
}
134179

180+
/** Clone an individual route. */
135181
public function cloneRoute(Route|string $route): static
136182
{
137183
$this->routesToClone[] = $route;
@@ -171,28 +217,31 @@ protected function createNewRoute(Route $route): Route
171217
$action->put('as', PathTenantResolver::tenantRouteNamePrefix() . $name);
172218
}
173219

174-
$action
175-
->put('middleware', $middleware)
176-
->put('prefix', $prefix . '/{' . PathTenantResolver::tenantParameterName() . '}');
220+
if ($this->domain) {
221+
$action->put('domain', $this->domain);
222+
} elseif ($action->offsetExists('domain')) {
223+
$action->offsetUnset('domain');
224+
}
225+
226+
$action->put('middleware', $middleware);
227+
228+
if ($this->addTenantParameter) {
229+
$action->put('prefix', $prefix . '/{' . PathTenantResolver::tenantParameterName() . '}');
230+
}
177231

178232
/** @var Route $newRoute */
179233
$newRoute = $this->router->$method($uri, $action->toArray());
180234

181-
return $newRoute;
182-
}
183-
184-
/**
185-
* Copy misc properties of the original route to the new route.
186-
*/
187-
protected function copyMiscRouteProperties(Route $originalRoute, Route $newRoute): void
188-
{
235+
// Copy misc properties of the original route to the new route.
189236
$newRoute
190-
->setBindingFields($originalRoute->bindingFields())
191-
->setFallback($originalRoute->isFallback)
192-
->setWheres($originalRoute->wheres)
193-
->block($originalRoute->locksFor(), $originalRoute->waitsFor())
194-
->withTrashed($originalRoute->allowsTrashedBindings())
195-
->setDefaults($originalRoute->defaults);
237+
->setBindingFields($route->bindingFields())
238+
->setFallback($route->isFallback)
239+
->setWheres($route->wheres)
240+
->block($route->locksFor(), $route->waitsFor())
241+
->withTrashed($route->allowsTrashedBindings())
242+
->setDefaults($route->defaults);
243+
244+
return $newRoute;
196245
}
197246

198247
/** Removes top-level cloneRoutesWithMiddleware and adds 'tenant' middleware. */

0 commit comments

Comments
 (0)