Skip to content

Commit

Permalink
Merge pull request #3 from nimbly/3.0
Browse files Browse the repository at this point in the history
Capsule 3.0
  • Loading branch information
brentscheffler authored Sep 21, 2024
2 parents fe25b8e + 74cc171 commit 997595c
Show file tree
Hide file tree
Showing 29 changed files with 654 additions and 288 deletions.
6 changes: 5 additions & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,8 @@ end_of_line=lf
indent_style=tab
indent_size=4
charset=utf-8
trim_trailing_whitespace=true
trim_trailing_whitespace=true

[*.yml]
indent_style=space
indent_size=2
8 changes: 5 additions & 3 deletions .github/workflows/php.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ jobs:
strategy:
fail-fast: false
matrix:
php-version: [8.0, 8.1, 8.2]
php-version: [8.2, 8.3]

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- name: Setup PHP
uses: shivammathur/setup-php@v2
Expand All @@ -38,7 +38,9 @@ jobs:
- name: Run test suite
run: make coverage

- uses: codecov/codecov-action@v3
- uses: codecov/codecov-action@v4
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
files: ./build/logs/clover.xml
#flags: unittests # optional
Expand Down
113 changes: 108 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,20 +100,123 @@ $response = new Response(200, \json_encode(["foo" => "bar"]), ["Content-Type" =>

### Response Status

Capsule provides a `ResponseStatus` helper class with HTTP response codes as constants and reason phrases.
Capsule provides a `ResponseStatus` enum with HTTP response codes and reason phrases.

```php
$response = new Response(ResponseStatus::NOT_FOUND);
$response = new Response(ResponseStatus::NOT_FOUND));
```

```php
$phrase = ResponseStatus::getPhrase(ResonseStatus::NOT_FOUND);
$phrase = ResponseStatus::NOT_FOUND->getPhrase();

echo $phrase; // Outputs "Not Found"
```

## HTTP Factory (PSR-17)

Capsule includes a set of PSR-17 factory classes to be used to create `Request`, `ServerRequest`, `Response`, `Stream`, `UploadedFile`, and `Uri` instances.
Capsule includes a set of PSR-17 factory classes to be used to create `Request`, `ServerRequest`, `Response`, `Stream`, `UploadedFile`, and `Uri` instances, found in the `Nimbly\Capsule\Factory` namespace. These factories are typically used with other libraries that are PSR-7 agnostic. They're also useful for creating mocked instances in unit testing.

`RequestFactory`, `ServerRequestFactory`, `ResponseFactory`, `StreamFactory`, `UploadedFileFactory`, and `UriFactory`.
### RequestFactory
```php
$requestFactory = new RequestFactory;
$request = $requestFactory->createRequest("get", "https://api.example.com");
```

### ServerRequestFactory
```php
$serverRequestFactory = new ServerRequestFactory;
$serverRequest = $serverRequestFactory->createServerRequest("post", "https://api.example.com/books");
```

In addition, the `ServerRequestFactory` provides several static methods for creating server requests.

#### Creating ServerRequest from PHP globals
You can create a `ServerRequest` instance from the PHP globals space ($_POST, $_GET, $_FILES, $_SERVER, and $_COOKIES).

```php
$serverRequest = ServerRequestFactory::createFromGlobals();
```

#### Creating ServerRequest from another PSR-7 ServerRequest
You can create a Capsule `ServerRequest` instance from another PSR-7 ServerRequest instance:

```php
$serverRequest = ServerRequestFactory::createServerRequestFromPsr7($otherServerRequest);
```

### ResponseFactory

```php
$responseFactory = new ResponseFactory;
$response = $responseFactory->createResponse(404);
```

### StreamFactory

#### Create a stream from string content

```php
$streamFactory = new StreamFactory;
$stream = $streamFactory->createStream(\json_encode($body));
```

#### Create a stream from a file

```php
$streamFactory = new StreamFactory;
$stream = $streamFactory->createStreamFromFile("/reports/q1.pdf");
```

#### Create a stream from any resource

```php
$resource = \fopen("https://example.com/reports/q1.pdf", "r");

$streamFactory = new StreamFactory;
$stream = $streamFactory->createStreamFromResource($resource);
```

Alternatively, these methods are also available statically:

```php
// Create a stream from a string.
$stream = StreamFactory::createFromString(\json_encode($body));

// Create a stream from a local file.
$stream = StreamFactory::createFromFile("/reports/q1.pdf");

// Create a stream from a PHP resource.
$resource = \fopen("https://example.com/reports/q1.pdf", "r");
$stream = StreamFactory::createFromResource($resource);
```

### UploadedFileFactory

#### Create an UploadedFile instance
```php
$uploadedFileFactory = new UploadedFileFactory;

$stream = StreamFactory::createFromFile("/tmp/upload");

$uploadedFile = $uploadedFileFactory->createUploadedFile(
$stream,
$stream->getSize(),
UPLOAD_ERR_OK,
"q1_report.pdf",
"application/pdf"
);
```

### UriFactory

The `UriFactory` allows you to create and parse URIs.

```php
$uriFactory = new UriFactory;
$uri = $uriFactory->createUri("https://api.example.com/v1/books?a=Kurt+Vonnegut");
```

This method is also available statically:

```php
$uri = UriFactory::createFromString("https://api.example.com/v1/books?a=Kurt+Vonnegut");
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
}
],
"require": {
"php": "^8.0",
"php": "^8.2",
"psr/http-message": "^1.0|^2.0",
"psr/http-factory": "^1.0"
},
Expand All @@ -24,7 +24,7 @@
}
},
"require-dev": {
"vimeo/psalm": "^4.0",
"vimeo/psalm": "^5.0",
"phpunit/phpunit": "^9.0",
"symfony/var-dumper": "^4.3"
},
Expand Down
4 changes: 3 additions & 1 deletion psalm.xml
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
<?xml version="1.0"?>
<psalm
errorLevel="2"
errorLevel="3"
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
findUnusedBaselineEntry="true"
findUnusedCode="false"
>
<projectFiles>
<directory name="src" />
Expand Down
3 changes: 3 additions & 0 deletions src/Factory/RequestFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\RequestInterface;

/**
* With this factory you can generate Request instances.
*/
class RequestFactory implements RequestFactoryInterface
{
/**
Expand Down
9 changes: 8 additions & 1 deletion src/Factory/ResponseFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,23 @@
namespace Nimbly\Capsule\Factory;

use Nimbly\Capsule\Response;
use Nimbly\Capsule\ResponseStatus;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;

/**
* With this factory you can generate Response instances.
*/
class ResponseFactory implements ResponseFactoryInterface
{
/**
* @inheritDoc
*/
public function createResponse(int $code = 200, string $reasonPhrase = ""): ResponseInterface
{
return new Response($code, null, [], $reasonPhrase);
return new Response(
statusCode: ResponseStatus::from($code),
reasonPhrase: $reasonPhrase ?: null
);
}
}
18 changes: 6 additions & 12 deletions src/Factory/ServerRequestFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
use Psr\Http\Message\ServerRequestFactoryInterface;
use Psr\Http\Message\ServerRequestInterface;

/**
* With this factory you can generate ServerRequest instances.
*/
class ServerRequestFactory implements ServerRequestFactoryInterface
{
/**
Expand Down Expand Up @@ -65,16 +68,7 @@ public static function createFromGlobals(): ServerRequest
// Get the request body first by getting raw input from php://input.
$body = \file_get_contents("php://input");

// Process the uploaded files into an array<UploadedFile>.
$files = [];

/**
* @var array<string,array{error:int,name:string,size:int,tmp_name:string,type:string}> $_FILES
*/
foreach( $_FILES as $name => $file ){
$files[$name] = UploadedFileFactory::createFromGlobal($file);
}

// Get all request headers
if( \function_exists("getallheaders") ){
$headers = \getallheaders();
}
Expand All @@ -89,7 +83,7 @@ public static function createFromGlobals(): ServerRequest
}

/**
* @psalm-suppress InvalidScalarArgument
* @psalm-suppress InvalidArgument
*/
$serverRequest = new ServerRequest(
$_SERVER["REQUEST_METHOD"] ?? "GET",
Expand All @@ -98,7 +92,7 @@ public static function createFromGlobals(): ServerRequest
$_GET,
\array_change_key_case($headers),
$_COOKIE,
$files,
UploadedFileFactory::createFromGlobals($_FILES),
$_SERVER,
$versionMatch[2] ?? "1.1"
);
Expand Down
3 changes: 3 additions & 0 deletions src/Factory/StreamFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
use Psr\Http\Message\StreamInterface;
use RuntimeException;

/**
* With this factory you can generate various StreamInterface instances.
*/
class StreamFactory implements StreamFactoryInterface
{
/**
Expand Down
54 changes: 47 additions & 7 deletions src/Factory/UploadedFileFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,21 @@
use Psr\Http\Message\UploadedFileInterface;
use RuntimeException;

/**
* With this factory you can generate an UploadedFile instance.
*/
class UploadedFileFactory implements UploadedFileFactoryInterface
{
/**
* @inheritDoc
*/
public function createUploadedFile(
StreamInterface $stream,
int $size = null,
int $error = UPLOAD_ERR_OK,
string $clientFilename = null,
string $clientMediaType = null
): UploadedFileInterface {
StreamInterface $stream,
int $size = null,
int $error = UPLOAD_ERR_OK,
string $clientFilename = null,
string $clientMediaType = null
): UploadedFileInterface {

return new UploadedFile(
$stream,
Expand All @@ -40,11 +43,48 @@ public function createUploadedFile(
public static function createFromGlobal(array $file): UploadedFile
{
return new UploadedFile(
$file["tmp_name"],
StreamFactory::createFromFile($file["tmp_name"], "r"),
$file["name"] ?? null,
$file["type"] ?? null,
$file["size"] ?? 0,
$file["error"] ?? UPLOAD_ERR_OK
);
}

/**
* Create a tree of UploadedFile instances.
*
* @param array<array-key,array{tmp_name:string,name:string,type:string,size:int,error:int}|array{tmp_name:array<string>,name:array<string>,type:array<string>,size:array<int>,error:array<int>}> $files Tree of uploaded files in the PHP $_FILES format.
* @return array<array-key,UploadedFile|array<UploadedFile>>
*/
public static function createFromGlobals(array $files): array
{
$uploaded_files = [];

foreach( $files as $name => $file ){
if( \is_array($file["tmp_name"]) ) {
for( $i = 0; $i < \count($file["tmp_name"]); $i++ ){
/**
* @psalm-suppress PossiblyInvalidArrayAccess
* @psalm-suppress UndefinedMethod
*/
$uploaded_files[$name][] = self::createFromGlobal([
"tmp_name" => $file["tmp_name"][$i],
"name" => $file["name"][$i],
"type" => $file["type"][$i],
"size" => $file["size"][$i],
"error" => $file["error"][$i]
]);
}
}
else {
/**
* @psalm-suppress ArgumentTypeCoercion
*/
$uploaded_files[$name] = self::createFromGlobal($file);
}
}

return $uploaded_files;
}
}
3 changes: 3 additions & 0 deletions src/Factory/UriFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
use Psr\Http\Message\UriInterface;
use RuntimeException;

/**
* With this factory you can generate a Uri instance.
*/
class UriFactory implements UriFactoryInterface
{
/**
Expand Down
4 changes: 4 additions & 0 deletions src/MessageAbstract.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
use Psr\Http\Message\StreamInterface;
use RuntimeException;

/**
* This abstract class provides common functionality between Request, ServerRequest, and Response
* implementations.
*/
abstract class MessageAbstract implements MessageInterface
{
/**
Expand Down
Loading

0 comments on commit 997595c

Please sign in to comment.