Skip to content

Commit

Permalink
[10.x] Fixes singleton and api singletons creatable|destryoable|only|…
Browse files Browse the repository at this point in the history
…except combinations (#47098)

* Fixes singleton and api singletons when combined with creatable|destroyable|only|except options

* Reverts change

* More tests

* Adds more tests

* Make API singleton 'only' behavior consistent with API resource

---------

Co-authored-by: Jess Archer <jess@jessarcher.com>
  • Loading branch information
nunomaduro and jessarcher authored May 18, 2023
1 parent 2b6e410 commit 4b3bd09
Show file tree
Hide file tree
Showing 4 changed files with 432 additions and 24 deletions.
27 changes: 7 additions & 20 deletions src/Illuminate/Routing/ResourceRegistrar.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

namespace Illuminate\Routing;

use Illuminate\Support\Arr;
use Illuminate\Support\Str;

class ResourceRegistrar
Expand Down Expand Up @@ -149,6 +148,12 @@ public function singleton($name, $controller, array $options = [])

$defaults = $this->singletonResourceDefaults;

if (isset($options['creatable'])) {
$defaults = array_merge($defaults, ['create', 'store', 'destroy']);
} elseif (isset($options['destroyable'])) {
$defaults = array_merge($defaults, ['destroy']);
}

$collection = new RouteCollection;

$resourceMethods = $this->getResourceMethods($defaults, $options);
Expand Down Expand Up @@ -249,25 +254,7 @@ protected function getResourceMethods($defaults, $options)
$methods = array_diff($methods, (array) $options['except']);
}

if (isset($options['creatable'])) {
$methods = isset($options['apiSingleton'])
? array_merge(['store', 'destroy'], $methods)
: array_merge(['create', 'store', 'destroy'], $methods);

return $this->getResourceMethods(
$methods, array_values(Arr::except($options, ['creatable']))
);
}

if (isset($options['destroyable'])) {
$methods = array_merge(['destroy'], $methods);

return $this->getResourceMethods(
$methods, array_values(Arr::except($options, ['destroyable']))
);
}

return $methods;
return array_values($methods);
}

/**
Expand Down
3 changes: 1 addition & 2 deletions src/Illuminate/Routing/Router.php
Original file line number Diff line number Diff line change
Expand Up @@ -427,15 +427,14 @@ public function apiSingletons(array $singletons, array $options = [])
*/
public function apiSingleton($name, $controller, array $options = [])
{
$only = ['show', 'update', 'destroy'];
$only = ['store', 'show', 'update', 'destroy'];

if (isset($options['except'])) {
$only = array_diff($only, (array) $options['except']);
}

return $this->singleton($name, $controller, array_merge([
'only' => $only,
'apiSingleton' => true,
], $options));
}

Expand Down
264 changes: 262 additions & 2 deletions tests/Integration/Routing/RouteSingletonTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,58 @@ public function testCreatableSingleton()
$this->assertSame('singleton destroy', $response->getContent());
}

public function testCreatableSingletonOnly()
{
Route::singleton('avatar', CreatableSingletonTestController::class)->creatable()->only('show');

$response = $this->get('/avatar');
$this->assertEquals(200, $response->getStatusCode());

$response = $this->get('/avatar/create');
$this->assertEquals(404, $response->getStatusCode());

$response = $this->post('/avatar');
$this->assertEquals(405, $response->getStatusCode());

$response = $this->get('/avatar/edit');
$this->assertEquals(404, $response->getStatusCode());

$response = $this->put('/avatar');
$this->assertEquals(405, $response->getStatusCode());

$response = $this->patch('/avatar');
$this->assertEquals(405, $response->getStatusCode());

$response = $this->delete('/avatar');
$this->assertEquals(405, $response->getStatusCode());
}

public function testCreatableSingletonExcept()
{
Route::singleton('avatar', CreatableSingletonTestController::class)->creatable()->except('show');

$response = $this->get('/avatar');
$this->assertEquals(405, $response->getStatusCode());

$response = $this->get('/avatar/create');
$this->assertEquals(200, $response->getStatusCode());

$response = $this->post('/avatar');
$this->assertEquals(200, $response->getStatusCode());

$response = $this->get('/avatar/edit');
$this->assertEquals(200, $response->getStatusCode());

$response = $this->put('/avatar');
$this->assertEquals(200, $response->getStatusCode());

$response = $this->patch('/avatar');
$this->assertEquals(200, $response->getStatusCode());

$response = $this->delete('/avatar');
$this->assertEquals(200, $response->getStatusCode());
}

public function testDestroyableSingleton()
{
Route::singleton('avatar', CreatableSingletonTestController::class)->destroyable();
Expand All @@ -87,15 +139,93 @@ public function testDestroyableSingleton()
$this->assertSame('singleton destroy', $response->getContent());
}

public function testDestroyableSingletonOnly()
{
Route::singleton('avatar', SingletonTestController::class)->destroyable()->only('destroy');

$response = $this->get('/avatar');
$this->assertEquals(405, $response->getStatusCode());

$response = $this->get('/avatar/create');
$this->assertEquals(404, $response->getStatusCode());

$response = $this->post('/avatar');
$this->assertEquals(405, $response->getStatusCode());

$response = $this->get('/avatar/edit');
$this->assertEquals(404, $response->getStatusCode());

$response = $this->put('/avatar');
$this->assertEquals(405, $response->getStatusCode());

$response = $this->patch('/avatar');
$this->assertEquals(405, $response->getStatusCode());

$response = $this->delete('/avatar');
$this->assertEquals(200, $response->getStatusCode());
}

public function testDestroyableSingletonExcept()
{
Route::singleton('avatar', SingletonTestController::class)->destroyable()->except('destroy');

$response = $this->get('/avatar');
$this->assertEquals(200, $response->getStatusCode());

$response = $this->get('/avatar/create');
$this->assertEquals(404, $response->getStatusCode());

$response = $this->post('/avatar');
$this->assertEquals(405, $response->getStatusCode());

$response = $this->get('/avatar/edit');
$this->assertEquals(200, $response->getStatusCode());

$response = $this->put('/avatar');
$this->assertEquals(200, $response->getStatusCode());

$response = $this->patch('/avatar');
$this->assertEquals(200, $response->getStatusCode());

$response = $this->delete('/avatar');
$this->assertEquals(405, $response->getStatusCode());
}

public function testCreatableDestroyableSingletonOnlyExceptTest()
{
Route::singleton('avatar', SingletonTestController::class)->creatable()->destroyable()->only(['show'])->except(['destroy']);

$response = $this->get('/avatar');
$this->assertEquals(200, $response->getStatusCode());

$response = $this->get('/avatar/create');
$this->assertEquals(404, $response->getStatusCode());

$response = $this->post('/avatar');
$this->assertEquals(405, $response->getStatusCode());

$response = $this->get('/avatar/edit');
$this->assertEquals(404, $response->getStatusCode());

$response = $this->put('/avatar');
$this->assertEquals(405, $response->getStatusCode());

$response = $this->patch('/avatar');
$this->assertEquals(405, $response->getStatusCode());

$response = $this->delete('/avatar');
$this->assertEquals(405, $response->getStatusCode());
}

public function testApiSingleton()
{
Route::apiSingleton('avatar', SingletonTestController::class);

$response = $this->get('/avatar/create');
$this->assertEquals(404, $response->getStatusCode());

$response = $this->post('/avatar/store');
$this->assertEquals(404, $response->getStatusCode());
$response = $this->post('/avatar');
$this->assertEquals(405, $response->getStatusCode());

$this->assertSame('http://localhost/avatar', route('avatar.update'));
$response = $this->put('/avatar');
Expand All @@ -121,6 +251,58 @@ public function testCreatableApiSingleton()
$this->assertSame('singleton update', $response->getContent());
}

public function testCreatableApiSingletonOnly()
{
Route::apiSingleton('avatar', CreatableSingletonTestController::class)->creatable()->only(['create', 'store']);

$response = $this->get('/avatar');
$this->assertEquals(405, $response->getStatusCode());

$response = $this->get('/avatar/create');
$this->assertEquals(200, $response->getStatusCode());

$response = $this->post('/avatar');
$this->assertEquals(200, $response->getStatusCode());

$response = $this->get('/avatar/edit');
$this->assertEquals(404, $response->getStatusCode());

$response = $this->put('/avatar');
$this->assertEquals(405, $response->getStatusCode());

$response = $this->patch('/avatar');
$this->assertEquals(405, $response->getStatusCode());

$response = $this->delete('/avatar');
$this->assertEquals(405, $response->getStatusCode());
}

public function testCreatableApiSingletonExcept()
{
Route::apiSingleton('avatar', CreatableSingletonTestController::class)->creatable()->except(['create', 'store']);

$response = $this->get('/avatar');
$this->assertEquals(200, $response->getStatusCode());

$response = $this->get('/avatar/create');
$this->assertEquals(404, $response->getStatusCode());

$response = $this->post('/avatar');
$this->assertEquals(405, $response->getStatusCode());

$response = $this->get('/avatar/edit');
$this->assertEquals(404, $response->getStatusCode());

$response = $this->put('/avatar');
$this->assertEquals(200, $response->getStatusCode());

$response = $this->patch('/avatar');
$this->assertEquals(200, $response->getStatusCode());

$response = $this->delete('/avatar');
$this->assertEquals(200, $response->getStatusCode());
}

public function testDestroyableApiSingleton()
{
Route::apiSingleton('avatar', CreatableSingletonTestController::class)->destroyable();
Expand All @@ -141,6 +323,84 @@ public function testDestroyableApiSingleton()
$this->assertSame('singleton destroy', $response->getContent());
}

public function testDestroyableApiSingletonOnly()
{
Route::apiSingleton('avatar', CreatableSingletonTestController::class)->destroyable()->only(['destroy']);

$response = $this->get('/avatar');
$this->assertEquals(405, $response->getStatusCode());

$response = $this->get('/avatar/create');
$this->assertEquals(404, $response->getStatusCode());

$response = $this->post('/avatar');
$this->assertEquals(405, $response->getStatusCode());

$response = $this->get('/avatar/edit');
$this->assertEquals(404, $response->getStatusCode());

$response = $this->put('/avatar');
$this->assertEquals(405, $response->getStatusCode());

$response = $this->patch('/avatar');
$this->assertEquals(405, $response->getStatusCode());

$response = $this->delete('/avatar');
$this->assertEquals(200, $response->getStatusCode());
}

public function testDestroyableApiSingletonExcept()
{
Route::apiSingleton('avatar', CreatableSingletonTestController::class)->destroyable()->except(['destroy', 'show']);

$response = $this->get('/avatar');
$this->assertEquals(405, $response->getStatusCode());

$response = $this->get('/avatar/create');
$this->assertEquals(404, $response->getStatusCode());

$response = $this->post('/avatar');
$this->assertEquals(405, $response->getStatusCode());

$response = $this->get('/avatar/edit');
$this->assertEquals(404, $response->getStatusCode());

$response = $this->put('/avatar');
$this->assertEquals(200, $response->getStatusCode());

$response = $this->patch('/avatar');
$this->assertEquals(200, $response->getStatusCode());

$response = $this->delete('/avatar');
$this->assertEquals(405, $response->getStatusCode());
}

public function testCreatableDestroyableApiSingletonOnlyExceptTest()
{
Route::apiSingleton('avatar', CreatableSingletonTestController::class)->creatable()->destroyable()->only(['show'])->except(['destroy']);

$response = $this->get('/avatar');
$this->assertEquals(200, $response->getStatusCode());

$response = $this->get('/avatar/create');
$this->assertEquals(404, $response->getStatusCode());

$response = $this->post('/avatar');
$this->assertEquals(405, $response->getStatusCode());

$response = $this->get('/avatar/edit');
$this->assertEquals(404, $response->getStatusCode());

$response = $this->put('/avatar');
$this->assertEquals(405, $response->getStatusCode());

$response = $this->patch('/avatar');
$this->assertEquals(405, $response->getStatusCode());

$response = $this->delete('/avatar');
$this->assertEquals(405, $response->getStatusCode());
}

public function testSingletonOnly()
{
Route::singleton('avatar', SingletonTestController::class)->only('show');
Expand Down
Loading

0 comments on commit 4b3bd09

Please sign in to comment.