Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
vendor/
.phpunit.result.cache
composer.lock
/vendor
/composer.lock
/.phpunit.result.cache
/.php_cs.cache
/coverage
/infection.log
8 changes: 8 additions & 0 deletions .php_cs.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

$finder = PhpCsFixer\Finder::create()
->in(__DIR__.'/src')
->in(__DIR__.'/tests');

return TiMacDonald\styles($finder);

52 changes: 47 additions & 5 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"name": "timacdonald/multiformat-response-objects",
"description": "A response object that handles multiple response formats within the one controller",
"license": "MIT",
"keywords": [
"multiformat",
"responsable",
"response objects",
"laravel"
],
"license": "MIT",
"authors": [
{
"name": "Tim MacDonald",
Expand All @@ -17,22 +17,64 @@
],
"require": {
"php": "^7.2",
"illuminate/support": "5.8.*",
"illuminate/http": "5.8.*",
"symfony/mime": "^4.3"
"illuminate/support": "5.8.*",
"symfony/mime": "^5.1"
},
"require-dev": {
"ergebnis/composer-normalize": "^2.7",
"infection/infection": "^0.17.2",
"orchestra/testbench": "^3.5",
"phpstan/phpstan": "^0.12.38",
"phpunit/phpunit": "^8.0",
"orchestra/testbench": "^3.5"
"timacdonald/php-style": "dev-master",
"vimeo/psalm": "^3.15"
},
"config": {
"preferred-install": "dist",
"sort-packages": true
},
"extra": {
"laravel": {
"providers": [
"TiMacDonald\\Multiformat\\MultiformatResponseServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"TiMacDonald\\MultiFormat\\": "src"
"TiMacDonald\\Multiformat\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
},
"minimum-stability": "stable",
"prefer-stable": true,
"scripts": {
"fix": [
"clear",
"@composer normalize",
"./vendor/bin/php-cs-fixer fix"
],
"lint": [
"clear",
"@composer normalize --dry-run",
"./vendor/bin/php-cs-fixer fix --dry-run",
"./vendor/bin/psalm --threads=8",
"./vendor/bin/phpstan analyse"
],
"test": [
"clear",
"./vendor/bin/phpunit",
"./vendor/bin/infection --threads=8"
]
},
"support": {
"issues": "https://github.com/timacdonald/multiformat-response-objects/issues",
"source": "https://github.com/timacdonald/multiformat-response-objects/releases/latest",
"docs": "https://github.com/timacdonald/multiformat-response-objects/blob/master/readme.md"
}
}
13 changes: 13 additions & 0 deletions infection.json.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"source": {
"directories": [
"src"
]
},
"logs": {
"text": "infection.log"
},
"mutators": {
"@default": true
}
}
7 changes: 7 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
parameters:
checkMissingIterableValueType: false
level: max
paths:
- src
- tests
ignoreErrors:
15 changes: 0 additions & 15 deletions phpunit.xml

This file was deleted.

19 changes: 19 additions & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.1/phpunit.xsd"
bootstrap="vendor/autoload.php"
executionOrder="depends,defects"
beStrictAboutOutputDuringTests="true"
verbose="true">
<testsuites>
<testsuite name="default">
<directory suffix="Test.php">tests</directory>
</testsuite>
</testsuites>

<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">src</directory>
</whitelist>
</filter>
</phpunit>
30 changes: 30 additions & 0 deletions psalm.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?xml version="1.0"?>
<psalm
errorLevel="1"
strictBinaryOperands="true"
resolveFromConfigFile="true"
allowStringToStandInForClass="true"
ignoreInternalFunctionNullReturn="false"
findUnusedVariablesAndParams="true"
ensureArrayStringOffsetsExist="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"
>
<projectFiles>
<directory name="src" />
<directory name="tests" />
<ignoreFiles>
<directory name="vendor" />
</ignoreFiles>
</projectFiles>

<issueHandlers>
<PropertyNotSetInConstructor>
<errorLevel type="suppress">
<file name="tests/MultiformatResponseTest.php" />
</errorLevel>
</PropertyNotSetInConstructor>
</issueHandlers>
</psalm>

38 changes: 31 additions & 7 deletions readme.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Multi-format Response Object for Laravel
# Multiformat response object for Laravel

[![Latest Stable Version](https://poser.pugx.org/timacdonald/multiformat-response-objects/v/stable)](https://packagist.org/packages/timacdonald/multiformat-response-objects) [![Total Downloads](https://poser.pugx.org/timacdonald/multiformat-response-objects/downloads)](https://packagist.org/packages/timacdonald/multiformat-response-objects) [![License](https://poser.pugx.org/timacdonald/multiformat-response-objects/license)](https://packagist.org/packages/timacdonald/multiformat-response-objects)
[![Total Downloads](https://poser.pugx.org/timacdonald/multiformat-response-objects/downloads)](https://packagist.org/packages/timacdonald/multiformat-response-objects) [![License](https://poser.pugx.org/timacdonald/multiformat-response-objects/license)](https://packagist.org/packages/timacdonald/multiformat-response-objects)

In some situations you may want to support multiple return formats (HTML, JSON, CSV, XLSX) for the one endpoint and controller. This package gives you a base class that helps you return different formats of the same data. It supports specifying the return format as a file extension or as an `Accept` header. It also allows you to have shared and format specific logic, all while sharing the same route and controller.

## Installation
## Installationasdf

You can install using [composer](https://getcomposer.org/) from [Packagist](https://packagist.org/packages/timacdonald/multiformat-response-objects)

Expand All @@ -14,9 +14,11 @@ $ composer require timacdonald/multiformat-response-objects

## Getting started

This package is designed to help if you have ever created a controller that looks like this...
This package is designed to help if you have ever created two different controllers just to provide different formats (HTML / JSON) but the controllers have a lot of shared logic, or if you have ever created a controller that looks like this...

```php
<?php

class UserController
{
public function index(Request $request, CsvWriter $csvWriter)
Expand Down Expand Up @@ -69,6 +71,8 @@ This package cleans up this style of controller. Let me show you how...
The first step to refactoring the controller is to replace the format specific logic with the response object. You will no doubt do this step last, but I think it is easier to demonstrate it this way.

```php
<?php

class UserController
{
public function index(Request $request, CsvWriter $csvWriter, )
Expand All @@ -91,7 +95,9 @@ In order to support a particular response format, you need to add a correspondin
You can type hint these methods and the dependencies will be resolved from the container. In our example we are supporting HTML and CSV formats.

```php
use TiMacDonald\MultiFormat\Response;
<?php

use TiMacDonald\Multiformat\Response;

class UserResponse extends Response
{
Expand Down Expand Up @@ -156,7 +162,7 @@ In order to support a format, you create a `to{Format}Response` method, where `{
- HTML: `toHtmlResponse()`
- XLSX: `toXlsxResponse()`

### Dependency Injection
### Dependency injection

As mentioned previously, the format method will be called by the container, allowing you to resolve **format specific dependencies** from the container. As seen in the basic usage example, the html format has no dependencies, however the csv format has a `CsvWriter` dependency.

Expand All @@ -167,6 +173,8 @@ It is possible to set a default response format, either from the calling control
### In the controller

```php
<?php

class UserController
{
public function index()
Expand All @@ -182,6 +190,8 @@ class UserController
### In the response object

```php
<?php

class UserResponse extends Response
{
protected $defaultFormat = 'csv';
Expand All @@ -197,6 +207,8 @@ If there is a situation where the mime type you want to support is not being con
Look at `audio/mpeg` for example. There are several extensions associated with this content type.

```php
<?php

'audio/mpeg' => ['mpga', 'mp2', 'mp2a', 'mp3', 'm2a', 'm3a'],
```

Expand All @@ -205,6 +217,8 @@ This package will resolve the first match, i.e. `mpga` as the format type. If yo
### In the controller

```php
<?php

class UserController
{
public function index()
Expand All @@ -222,6 +236,8 @@ class UserController
### In the response object

```php
<?php

class UserResponse extends Response
{
protected $formatOverrides = [
Expand All @@ -239,6 +255,8 @@ The above would result in `toMp3Response` being called if the Accept header is `
If you are wanting to embrace file extensions as a way of specifying response formats, you should explicilty specify the allowed formats in your routes file. This package does not provide any routing helpers (yet), but here is an example of how you can do it currently.

```php
<?php

Route::get('users{extension?}', [
'as' => 'users.index',
'uses' => 'UserController@index',
Expand All @@ -261,6 +279,8 @@ This route will be able to respond to the following urls and formats in the resp
That's cool. Not everyone loves it. You don't have to use the `make` method. Just add your own contructor and set your class attributes as you like!

```php
<?php

class UserResponse extends Response
{
/**
Expand All @@ -279,7 +299,11 @@ class UserResponse extends Response
return new UserResponse($query);
```

## The Journey
## Coming soon

- Handling of API versioning

## The journey

You've read the readme, you've seen the code, now read the journey. If you wanna see how I came to this solution, you can read my blog post: https://timacdonald.me/versatile-response-objects-laravel/. Warning: it's a bit of a rant.

Expand Down
23 changes: 23 additions & 0 deletions src/ApiFallbackExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace TiMacDonald\Multiformat;

class ApiFallbackExtension
{
/**
* @var string
*/
private $value;

public function __construct(string $value)
{
$this->value = $value;
}

public function value(): string
{
return $this->value;
}
}
12 changes: 12 additions & 0 deletions src/BaseMultiformatResponse.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace TiMacDonald\Multiformat;

use Illuminate\Contracts\Support\Responsable;

class BaseMultiformatResponse implements Responsable
{
use Multiformat;
}
12 changes: 12 additions & 0 deletions src/Contracts/Extension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace TiMacDonald\Multiformat\Contracts;

use Illuminate\Http\Request;

interface Extension
{
public function parse(Request $request): ?string;
}
12 changes: 12 additions & 0 deletions src/Contracts/ExtensionGuesser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace TiMacDonald\Multiformat\Contracts;

use Illuminate\Http\Request;

interface ExtensionGuesser
{
public function guess(Request $request): ?string;
}
Loading