Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: cherifGsoul/inertia-psr15
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v0.2.0
Choose a base ref
...
head repository: cherifGsoul/inertia-psr15
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: master
Choose a head ref
  • 7 commits
  • 5 files changed
  • 2 contributors

Commits on Jul 4, 2024

  1. Add LazyProp Class

    While working with the Inertia.js documentation, Laravel Inertia class provides lazy method to determine which props will be included only when required
    aiglesiasn committed Jul 4, 2024
    Copy the full SHA
    02aef3f View commit details
  2. Update Inertia render method and add lazy static method

    - In order to use LazyProp - similar to Laravel Inertia backend library - a new lazy static method is created that returns a LazyProp instance.
    
    - Also the render method was change, so if we are not asking for Partial Data, the Lazy props won't be included in the data.
    
    - A couple of tests were added with this new features.
    aiglesiasn committed Jul 4, 2024
    Copy the full SHA
    5d5b7dc View commit details
  3. Copy the full SHA
    eca4ed3 View commit details

Commits on Jul 5, 2024

  1. Merge pull request #4 from aiglesiasn/master

    Add LazyProps capability to Inertia class
    cherifGsoul authored Jul 5, 2024
    Copy the full SHA
    292af3a View commit details

Commits on Jul 10, 2024

  1. Update changeRedirectCode method in InertiaMiddleware

    Continue the improvement of the library based on Inertia Laravel Library and what the documentation states. A 409 with a X-Inertia-Location Header should produce an external redirect in the inertia.js library. But for this to happen we need to remove the X-Inertia header, if not, inertia.js won't handle the external redirect.
    
    Add a test for this path inside changeRedirectCode method of the InertiaMiddleware
    aiglesiasn committed Jul 10, 2024
    Copy the full SHA
    434b6b8 View commit details
  2. Implement Inertia method location

    Laravel Inertia Library has the option to redirect to external url or routes not handled with Inertia, via the location method.
    
    In this method we add the possibility to send the destination as a ResponseInterface, in case we already have a response or a string so a url as a string can be sent.
    
    Status Code option is also added in order to provide the developer the ability to add a 303 if required.
    aiglesiasn committed Jul 10, 2024
    Copy the full SHA
    f3eaa1c View commit details

Commits on Jul 25, 2024

  1. Merge pull request #5 from aiglesiasn/master

    Add ability for External Redirects
    cherifGsoul authored Jul 25, 2024
    Copy the full SHA
    551660e View commit details
Showing with 325 additions and 4 deletions.
  1. +9 −0 src/Middleware/InertiaMiddleware.php
  2. +30 −0 src/Model/LazyProp.php
  3. +37 −1 src/Service/Inertia.php
  4. +32 −1 test/Middleware/InertiaMiddlewareTest.php
  5. +217 −2 test/Service/InertiaTest.php
9 changes: 9 additions & 0 deletions src/Middleware/InertiaMiddleware.php
Original file line number Diff line number Diff line change
@@ -103,6 +103,15 @@ private function changeRedirectCode(Request $request, Response $response): Respo
return $response->withStatus(303);
}

// For External redirects
// https://inertiajs.com/redirects#external-redirects
if (
409 === $response->getStatusCode()
&& $response->hasHeader('X-Inertia-Location')
) {
return $response->withoutHeader('X-Inertia');
}

return $response;
}
}
30 changes: 30 additions & 0 deletions src/Model/LazyProp.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace Cherif\InertiaPsr15\Model;

/**
* NOTE: this is similar to Laravel Inertia\LazyProp
*/
final class LazyProp
{
/**
* @var callable $callback
*/
protected $callback;

public function __construct(
callable $callable
) {
$this->callback = $callable;
}

/**
* @return mixed
*/
public function __invoke()
{
return call_user_func($this->callback);
}
}
38 changes: 37 additions & 1 deletion src/Service/Inertia.php
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@

namespace Cherif\InertiaPsr15\Service;

use Cherif\InertiaPsr15\Model\LazyProp;
use Cherif\InertiaPsr15\Model\Page;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
@@ -42,10 +43,14 @@ public function render(string $component, array $props = [], string $url = null)
$props = ($only && $this->request->getHeaderLine('X-Inertia-Partial-Component') === $component)
? array_intersect_key($props, array_flip((array) $only))
: $props;
} else {
$props = array_filter($props, function ($prop) {
return ! $prop instanceof LazyProp;
});
}

array_walk_recursive($props, function (&$prop) {
if ($prop instanceof \Closure) {
if ($prop instanceof \Closure || $prop instanceof LazyProp ) {
$prop = $prop();
}
});
@@ -85,4 +90,35 @@ private function createResponse(string $data, string $contentType)
->withBody($stream)
->withHeader('Content-Type', $contentType);
}

public static function lazy(callable $callable): LazyProp
{
return new LazyProp($callable);
}

/**
* @param string|ResponseInterface $destination
* @param int $status
* @return ResponseInterface
*/
public function location($destination, int $status = 302): ResponseInterface
{
$response = $this->createResponse('', 'text/html; charset=UTF-8');

// We check if InertiaMiddleware has set up the 'X-Inertia-Location' header, so we handle the response accordingly
if ($this->request->hasHeader('X-Inertia')) {
$response = $response->withStatus(409);
return $response->withHeader(
'X-Inertia-Location',
$destination instanceof ResponseInterface ? $destination->getHeaderLine('Location') : $destination
);
}

if ($destination instanceof ResponseInterface) {
return $destination;
}

$response = $response->withStatus($status);
return $response->withHeader('Location', $destination);
}
}
33 changes: 32 additions & 1 deletion test/Middleware/InertiaMiddlewareTest.php
Original file line number Diff line number Diff line change
@@ -131,4 +131,35 @@ public function testItChangesResponseCodeTo303WhenRedirectHappensForPutPatchDele

$this->assertSame($response->reveal(), $middleware->process($request->reveal(), $handler->reveal()));
}
}

public function testItRemovesInertiaHeaderForExternalRedirects()
{
$factory = $this->prophesize(InertiaFactoryInterface::class);
$inertia = $this->prophesize(InertiaInterface::class);
$inertia->getVersion()->willReturn('12345');

$request = $this->prophesize(ServerRequestInterface::class);
$request->withAttribute(InertiaMiddleware::INERTIA_ATTRIBUTE, $inertia->reveal())->willReturn($request);
$request->hasHeader('X-Inertia')->willReturn(true);
$request->getHeader('X-Inertia-Version')->willReturn('12345');
$request->getMethod()->willReturn('POST');

$factory->fromRequest($request)->willReturn($inertia);

$response = $this->prophesize(ResponseInterface::class);

$response->withAddedHeader('Vary', 'Accept')->willReturn($response);
$response->withAddedHeader('X-Inertia', 'true')->willReturn($response);
$response->hasHeader('X-Inertia-Location')->willReturn(true);
$response->getStatusCode()->willReturn(409);
$response->withoutHeader('X-Inertia')->shouldBeCalled();
$response->withoutHeader('X-Inertia')->willReturn($response);

$handler = $this->prophesize(RequestHandlerInterface::class);
$handler->handle(Argument::that([$request, 'reveal']))->willReturn($response);

$middleware = new InertiaMiddleware($factory->reveal());

$this->assertSame($response->reveal(), $middleware->process($request->reveal(), $handler->reveal()));
}
}
219 changes: 217 additions & 2 deletions test/Service/InertiaTest.php
Original file line number Diff line number Diff line change
@@ -181,8 +181,8 @@ public function testRenderReturnResponseWithRequestedUrl()
$returnedResponse = $inertia->render(
'component',
[
'key1' => fn() => 'value1',
'key2' => fn() => 'value2'
'key1' => 'value1',
'key2' => 'value2'
]
,'/test/url'
);
@@ -192,4 +192,219 @@ public function testRenderReturnResponseWithRequestedUrl()
$this->assertSame($validJson, $jsonResponse);
}

public function testRenderReturnResponseWithoutLazyProps()
{
$request = $this->prophesize(ServerRequestInterface::class);
$request->hasHeader('X-Inertia')->willReturn(true);
$request->hasHeader('X-Inertia-Partial-Data')->willReturn(false);
$invalidJson = '{"component":"component","props":{"key1":"value1","key2":"value2"},"url":"callback()","version":null}';
$validJson = '{"component":"component","props":{"key2":"value2"},"url":"callback()","version":null}';
$jsonResponse = null;

$uri = $this->prophesize(UriInterface::class);
$request->getUri()->willReturn(Argument::that([$uri, 'reveal']));

$response = $this->prophesize(ResponseInterface::class);
$responseFactory = $this->prophesize(ResponseFactoryInterface::class);
$responseFactory->createResponse()->willReturn($response);

$stream = $this->prophesize(StreamInterface::class);
$streamFactory = $this->prophesize(StreamFactoryInterface::class);
$streamFactory->createStream(Argument::type('string'))->will(function ($args) use (&$jsonResponse, $stream){
$jsonResponse = $args[0];
return $stream;
});

$rootViewProvider = $this->prophesize(RootViewProviderInterface::class);

$response->withBody($stream->reveal())->willReturn($response);
$response->withHeader('X-Inertia', true)->willReturn($response);
$response->withHeader('Content-Type', 'application/json')->willReturn($response);

$inertia = new Inertia(
$request->reveal(),
$responseFactory->reveal(),
$streamFactory->reveal(),
$rootViewProvider->reveal()
);

$returnedResponse = $inertia->render(
'component',
[
'key1' => Inertia::lazy(fn() => 'value1'),
'key2' => fn() => 'value2'
]
);

$this->assertInstanceOf(ResponseInterface::class, $returnedResponse);
$this->assertNotSame($invalidJson, $jsonResponse);
$this->assertSame($validJson, $jsonResponse);
}

public function testRenderReturnResponseWithLazyProps()
{
$request = $this->prophesize(ServerRequestInterface::class);
$request->hasHeader('X-Inertia')->willReturn(true);
$request->hasHeader('X-Inertia-Partial-Data')->willReturn(true);
$request->getHeaderLine('X-Inertia-Partial-Component')->willReturn('component');
$request->getHeaderLine('X-Inertia-Partial-Data')->willReturn('key1');
$invalidJson = '{"component":"component","props":{"key2":"value2"},"url":"callback()","version":null}';
$validJson = '{"component":"component","props":{"key1":"value1"},"url":"callback()","version":null}';
$jsonResponse = null;

$uri = $this->prophesize(UriInterface::class);
$request->getUri()->willReturn(Argument::that([$uri, 'reveal']));

$response = $this->prophesize(ResponseInterface::class);
$responseFactory = $this->prophesize(ResponseFactoryInterface::class);
$responseFactory->createResponse()->willReturn($response);

$stream = $this->prophesize(StreamInterface::class);
$streamFactory = $this->prophesize(StreamFactoryInterface::class);
$streamFactory->createStream(Argument::type('string'))->will(function ($args) use (&$jsonResponse, $stream){
$jsonResponse = $args[0];
return $stream;
});

$rootViewProvider = $this->prophesize(RootViewProviderInterface::class);

$response->withBody($stream->reveal())->willReturn($response);
$response->withHeader('X-Inertia', true)->willReturn($response);
$response->withHeader('Content-Type', 'application/json')->willReturn($response);

$inertia = new Inertia(
$request->reveal(),
$responseFactory->reveal(),
$streamFactory->reveal(),
$rootViewProvider->reveal()
);

$returnedResponse = $inertia->render(
'component',
[
'key1' => Inertia::lazy(fn() => 'value1'),
'key2' => fn() => 'value2'
]
);

$this->assertInstanceOf(ResponseInterface::class, $returnedResponse);
$this->assertNotSame($invalidJson, $jsonResponse);
$this->assertSame($validJson, $jsonResponse);
}

public function testLocationReturnResponseWithLocationAsStringWithNotExistingInertiaHeader()
{
$request = $this->prophesize(ServerRequestInterface::class);
$htmlResponse = null;

$response = $this->prophesize(ResponseInterface::class);
$responseFactory = $this->prophesize(ResponseFactoryInterface::class);
$responseFactory->createResponse()->willReturn($response);

$stream = $this->prophesize(StreamInterface::class);
$streamFactory = $this->prophesize(StreamFactoryInterface::class);
$streamFactory->createStream(Argument::type('string'))->will(function () use ($stream){
return $stream;
});

$rootViewProvider = $this->prophesize(RootViewProviderInterface::class);

$response->withBody($stream->reveal())->willReturn($response);
$response->withHeader('X-Inertia', true)->willReturn($response);
$response->withHeader('Content-Type', 'text/html; charset=UTF-8')->willReturn($response);
$response->withStatus(302)->willReturn($response);
$response->withHeader('Location', 'new-location')->willReturn($response);

$inertia = new Inertia(
$request->reveal(),
$responseFactory->reveal(),
$streamFactory->reveal(),
$rootViewProvider->reveal()
);


$returnedResponse = $inertia->location('new-location');

$this->assertInstanceOf(ResponseInterface::class, $returnedResponse);
$this->assertNotSame('', $htmlResponse);
}

public function testLocationReturnResponseWithLocationAsStringWithExistingInertiaHeader()
{
$request = $this->prophesize(ServerRequestInterface::class);
$request->hasHeader('X-Inertia')->willReturn(true);
$htmlResponse = null;

$response = $this->prophesize(ResponseInterface::class);
$responseFactory = $this->prophesize(ResponseFactoryInterface::class);
$responseFactory->createResponse()->willReturn($response);

$stream = $this->prophesize(StreamInterface::class);
$streamFactory = $this->prophesize(StreamFactoryInterface::class);
$streamFactory->createStream(Argument::type('string'))->will(function () use ($stream){
return $stream;
});

$rootViewProvider = $this->prophesize(RootViewProviderInterface::class);

$response->withBody($stream->reveal())->willReturn($response);
$response->withHeader('X-Inertia', true)->willReturn($response);
$response->withHeader('Content-Type', 'text/html; charset=UTF-8')->willReturn($response);
$response->withStatus(409)->willReturn($response);
$response->withHeader('X-Inertia-Location', 'new-location')->willReturn($response);

$inertia = new Inertia(
$request->reveal(),
$responseFactory->reveal(),
$streamFactory->reveal(),
$rootViewProvider->reveal()
);

$returnedResponse = $inertia->location('new-location');

$this->assertInstanceOf(ResponseInterface::class, $returnedResponse);
$this->assertNotSame('', $htmlResponse);
}

public function testLocationReturnResponseWithLocationAsResponseInterfaceWithExistingInertiaHeader()
{
$request = $this->prophesize(ServerRequestInterface::class);
$request->hasHeader('X-Inertia')->willReturn(true);
$htmlResponse = null;

$response = $this->prophesize(ResponseInterface::class);
$responseFactory = $this->prophesize(ResponseFactoryInterface::class);
$responseFactory->createResponse()->willReturn($response);

$stream = $this->prophesize(StreamInterface::class);
$streamFactory = $this->prophesize(StreamFactoryInterface::class);
$streamFactory->createStream(Argument::type('string'))->will(function () use ($stream){
return $stream;
});

$rootViewProvider = $this->prophesize(RootViewProviderInterface::class);

$response->withBody($stream->reveal())->willReturn($response);
$response->withHeader('X-Inertia', true)->willReturn($response);
$response->withHeader('Content-Type', 'text/html; charset=UTF-8')->willReturn($response);

$locationResponse = $this->prophesize(ResponseInterface::class);
$locationResponse->getHeaderLine('Location')->willReturn('new-location');

$response->withStatus(409)->willReturn($response);
$response->withHeader('X-Inertia-Location', $locationResponse instanceof ResponseInterface ? $locationResponse->getHeaderLine('Location') : $locationResponse)->willReturn($response);

$inertia = new Inertia(
$request->reveal(),
$responseFactory->reveal(),
$streamFactory->reveal(),
$rootViewProvider->reveal()
);

$returnedResponse = $inertia->location($locationResponse);

$this->assertInstanceOf(ResponseInterface::class, $returnedResponse);
$this->assertNotSame('', $htmlResponse);
}

}