Organizing Controllers in laravel - php

I recently dove into the world of laravel (version 5.4). While initially confused, the concept of MVC makes a lot of sense in writing large applications. Applications that you want to be easily understood by outside developers.
Using laravel for this has greatly simplified coding in PHP and has made the language fun again. However, beyond dividing code into its respective models, views, and controllers, what happens if we need to divide controllers to prevent them from growing too large?
A solution that I have found to this is to define one controller each folder and then fill that controller with traits that further add functionalities to the controller. (All-caps = folder):
CONTROLLER
HOME
Controller.php
TRAITS
additionalFunctionality1.php
additionalFunctionality2.php
additionalFunctionality3.php
...
ADMIN
Controller.php
TRAITS
additionalFunctionality1.php
additionalFunctionality2.php
additionalFunctionality3.php
...
Within routes/web.php I woud initialize everything as so:
Route::namespace('Home')->group(function () {
Route::get('home', 'Controller.php#loadPage');
Route::post('test', 'Controller.php#fun1');
Route::post('test2', 'Controller.php#fun2');
Route::post('test3', 'Controller.php#fun3');
});
Route::namespace('Admin')->group(function () {
Route::get('Admin', 'Controller.php#loadPage');
Route::post('test', 'Controller.php#fun1');
Route::post('test2', 'Controller.php#fun2');
Route::post('test3', 'Controller.php#fun3');
});
With me being new to laravel, this seems like a simple and elegant way to organize my logic. It is however something I do not see while researching laravel controller organization.
The Question
Is there an issue, both in the short-run and in the long-run, of organizing my data like this? What is a better alternative?
Example Controller:
<?php
namespace App\Http\Controllers\Message;
use DB;
use Auth;
use Request;
use FileHelper;
use App\Http\Controllers\Message\Traits\MessageTypes;
use App\Http\Controllers\Controller;
class MessageController extends Controller
{
// Traits that are used within the message controller
use FileHelper, MessageTypes;
/**
* #var array $data Everything about the message is stored here
*/
protected $data = []; // everything about the message
/**
* #var booloean/array $sendableData Additional data that is registered through the send function
*/
protected $sendableData = false;
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('auth');
$this->middleware('access');
}
/**
* Enable sendableData by passing data to the variable
*
* #param array $data Addition data that needs to registrered
* #return MessageController
*/
protected function send ($data = []) {
// enable sendableData by passing data to the variable
$this->sendableData = $data;
return $this;
}
/**
* Enable sendableData by passing data to the variable
*
* #param string $type The type of message that we will serve to the view
* #return MessageController
*/
protected function serve ($type = "message") {
$this->ss();
$this->setData(array_merge($this->sendableData, $this->status[$type]));
$this->data->id = DB::table('messages')->insertGetId((array) $this->data);
}
/**
* Set the data of the message to be used to send or construct a message
* Note that this function turns "(array) $data" into "(object) $data"
*
* #param array $extend Override default settings
* #return MessageController
*/
protected function setData(array $extend = []) {
$defaults = [
"lobby" => Request::get('lobbyid'),
"type" => "text",
"subtype" => null,
"body" => null,
"time" => date("g:ia"),
"user" => Auth::User()->username,
"userid" => Auth::User()->id,
"day" => date("j"),
"month" => date("M"),
"timestamp" => time(),
"private" => Request::get('isPrivate') ? "1" : "0",
"name" => Request::get('displayname'),
"kicker" => null
];
$this->data = (object) array_merge($defaults, $extend);
// because a closure can not be saved in the database we will remove it after we need it
unset($this->data->message);
return $this;
}
/**
* Send out a response for PHP
*
* #return string
*/
public function build() {
if($this->data->type == "file") {
$filesize = #filesize("uploads/" . $this->data->lobby . "/" . $this->data->body);
$this->data->filesize = $this->human_filesize($filesize, 2);
}
// do not send unneccessary data
unset($this->data->body, $this->data->time, $this->data->kicker, $this->data->name, $this->data->timestamp);
return $this->data;
}
/**
* Send out a usable response for an AJAX request
*
* #return object
*/
public function json() {
return json_encode($this->build());
}
}
?>

Laravel architecture is simple enough for any size of the application.
Laravel provides several mechanisms for developers to tackle the fatty controllers in your Application.
Use Middlewares for authentications.
Use Requests for validations and manipulating data.
Use Policy for your aplication roles.
Use Repository for writing your database queries.
Use Transformers for your APIs to transform data.
It depends on your application. if it is too large and have different Modules or functionalities then you should use a modular approach.
A nice package is available for making independent modules here
Hope this helps.

I think you should do a little differently ! First you should use your traits at the same levels as the controllers since traits are not controllers, your tree should look more like :
Http
Controller
Controller.php
Home
YourControllers
Admin
Your admin controllers
Traits
Your Traits
Next your routes need to be more like that :
Route::group(['prefix' => 'home'], function()
{
Route::get('/', 'Home\YourController#index')->name('home.index');
}
Route::group(['prefix' => 'admin', 'middleware' => ['admin']], function()
{
Route::get('/', 'Admin\DashboardController#index')->name('dashboard.index');
}
You can use many kink or routes like :
Route::post('/action', 'yourControllers#store')->name('controller.store');
Route::patch('/action', 'yourControllers#update')->name('controller.update');
Route::resource('/action', 'yourController');
The Resource route creates automatically the most used your, like post, patch, edit, index.. You just need to write the action and the controller called with its action. You can check out your toutes with this command : php artisan route:list
Laravel also has many automated features, like the creation of a controller with this command : php artisan make:controller YourController.
For the routes the prefix creates portions of url, for example all the routes inside the route group with the prefix 'admin' will lool like : www.yourwebsite.com/admin/theroute, and can also be blocked for some users with a middleware.
To get familiar with laravel i suggest you follow the laravel 5.4 tutorial from scratch by Jeffrey Way on Laracasts, he's awesome at explaining and showing how laravel works. Here is a link : https://laracasts.com/series/laravel-from-scratch-2017
Hope it helps, ask me if you want to know anything else or have some precisions, i'll try to answer you !

Related

laravel- how to pass data to starter kit - dashboard

im new to laravel, im using laravel starter kit - i added "profile resource" simalar to "chirps" on
this bootcamp : laravel bootcamp with all relations 1:1 and so on.
but now im trying to pass the profile data to dashboard, fields like "about" "skills".
i have a working query, but i cant find way to use it in dashboard.
thiss the "profile-controler-index-method" its working but i only know how to use it with "index.vue" of "profile.vue" i couldnt use it with "dashboard.vue" because i just need the data not the whole vue script :
public function index()
{
return Inertia::render('Profile/Index', [
'Profile' => profile::with('user:id,name')->get(),
]);
}
and thiss my query :
DB::select('select * from profiles where user_id = 23');
after many loging trying to find the controller of dashboard or somthing helpfull, i found this file that gets loaded everytime i refresh "dashboard" "RouteServiceProvider"
/**
* The path to the "home" route for your application.
*
* Typically, users are redirected here after authentication.
*
* #var string
*/
public const HOME = '/dashboard';
/**
* Define your route model bindings, pattern filters, and other route configuration.
*
* #return void
*/
public function boot()
{
Log::info('infooo im here 1');
$this->configureRateLimiting();
$this->routes(function () {
Route::middleware('api')
->prefix('api')
->group(base_path('routes/api.php'));
Route::middleware('web')
->group(base_path('routes/web.php'));
});
}
==============Edit :
i just found out that i can render any page i want in the index method.. whaaat..
public function index()
{
return Inertia::render('Dashboard', [
'Profile' => profile::with('user:id,name')->get(),
]);
}
so if i can add some parameter to the index method .. i can render diffrent pages is this okey?
its so confusing these router and render terms..
i think i solve it, but im not sure if thiss right .. if you have better way plz answer.
i redirect the route
Route::get('/dashboard', function () {
//return Inertia::render('Dashboard');
return redirect('dashboards');
})->middleware(['auth', 'verified'])->name('dashboard');
to this route
Route::resource('dashboards', DashboardController::class)
->only(['index'])
->middleware(['auth', 'verified']);
now i have "dashboards" instead of "dashboard" and i can pass through it but something seems not right ..

how to pass parameters to listener in laravel?

I registered LogConnectionFailed like this:
protected $listen = [
Registered::class => [
SendEmailVerificationNotification::class,
],
'Illuminate\Http\Client\Events\ConnectionFailed' => [
'App\Listeners\LogConnectionFailed',
],
];
The ConnectionFailed event is fired if no response is received for a given request.
my class {
public function send() {
$response = Http::get('http://example.com');
}
}
I need to The name of the class and the method in which this happened and duration time call http client in LogConnectionFailed class.
This is not possible through normal parameter passing, so I utilized PHP native function debug_backtrace() and hacked through it.
The logic is that when the listener wants to handle the event, we get the callback trace and filter through the call stack frames until we find one of our watching location.
The code is this:
use Illuminate\Support\Str;
use Illuminate\Http\Client\Events\ConnectionFailed;
class LogConnectionFailed
{
/**
* Create the event listener.
*
* #return void
*/
public function __construct()
{
//
}
public function handle(ConnectionFailed $event)
{
$backtraceCollection = Collect(debug_backtrace());
$callerClass = $backtraceCollection->first(function($value, $key) use ($event){
$class = $value['class'] ?? '';
return $this->classIsWatched($class);
});
if ($callerClass) {
// Store in DB or do some other stuff.
dd([
'class' => $callerClass['class'],
'function' => $callerClass['function'],
'line' => $callerClass['line'],
]);
} else {
dd("should skip. Not Watching classes.");
}
}
private function classIsWatched(string $className): bool
{
return Str::is(
['App\Http\Controllers\*', 'App\MyClass'],
$className
);
}
}
Here take note at the array inside the function classIsWatched:
['App\Http\Controllers\*', 'App\MyClass']
These are the classes or directories we will watch, which means if the ConnectionFailed due to some calls from these classes, they will be captured, else they will be skipped. This gives you the flexibility to just filter out and watch certain locations inside your application.
Note that we can also use wildcards * for simplifying the path inclusions. For example App\Http\Controllers\Api\EventController is watched too.
For example if I have this class inside the App path:
namespace App;
use Illuminate\Support\Facades\Http;
class MyClass
{
public static function callEvent()
{
$response = Http::get('http://example.com');
}
}
due to any reason if a ConnectionFailed event dispatches, the output of handle method will be:
array:3 [▼
"class" => "App\MyClass"
"function" => "callEvent"
"line" => 11
]
this will give you the class name, function name and even the line which event was raised there. You can simply replace the dd() inside the handle method of the listener and do what you want to do with the data.
About the Http Call duration, no accurate solution comes to my mind, but you can have a rough estimation using this approach:
dd(microtime(true) - LARAVEL_START);
add the above code inside the handle method too, and this gives you the time difference from the moment that the application started and till you got this point (Http request failed and you got inside this listener).

Using TypoScriptFrontendController features in AuthenticationService? / Save Data to User in Authenticator?

So I use a Service Class (extends from TYPO3\CMS\Core\Authentication\AuthenticationService) to authenticate our Frontend Users using OAuth2. These Services are automatically instantiated and called via Typos own Middleware: FrontendUserAuthenticator.
In this class I used to save data from the authentication result to $GLOBALS['TSFE']->fe_user using setKey('ses', 'key', 'data'), which seems is not possible anymore since v10. How would I go about still doing this?
The documentation is sparse
https://docs.typo3.org/c/typo3/cms-core/master/en-us/Changelog/9.4/Deprecation-85878-EidUtilityAndVariousTSFEMethods.html
https://docs.typo3.org/m/typo3/reference-coreapi/10.4/en-us/ApiOverview/Context/Index.html
I've tried the following:
constructor injecting the TSFE using DI
class FrontendOAuthService extends AuthenticationService
{
public function __construct(TypoScriptFrontendController $TSFE) {
=> LogicException: TypoScriptFrontendController was tried to be injected before initial creation
changing the Middlewares order to have it instantiate before the Auth Middleware
(packages/extension_name/Configuration/RequestMiddlewares.php)
return [
'frontend' => [
'typo3/cms-frontend/tsfe' => [
'disabled' => true,
],
'vendor/extension_name/frontend-oauth' => [
'target' => \TYPO3\CMS\Frontend\Middleware\TypoScriptFrontendInitialization::class,
'before' => [
'typo3/cms-frontend/authentication',
],
'after' => [
'typo3/cms-frontend/eid',
'typo3/cms-frontend/page-argument-validator',
],
],
],
];
=> UnexpectedValueException: Your dependencies have cycles. That will not work out.
instantiating the TSFE myself
/** #var ObjectManager $objectManager */
$objectManager = GeneralUtility::makeInstance(ObjectManager::class);
/** #var DealerService $dealerService */
$lang = $site->getDefaultLanguage();
$siteLanguage = $objectManager->get(SiteLanguage::class, $lang->getLanguageId(), $lang->getLocale(), $lang->getBase(), []);
/** #var TypoScriptFrontendController $TSFE */
$TSFE = $objectManager->get(
TypoScriptFrontendController::class,
GeneralUtility::makeInstance(Context::class),
$site,
$siteLanguage,
GeneralUtility::_GP('no_cache'),
GeneralUtility::_GP('cHash')
);
=> the $TSFE->fe_user is an emptystring ("")
using the UserAspect
/** #var Context $context */
$context = GeneralUtility::makeInstance(Context::class);
$feUser = $context->getAspect('frontend.user');
$feUser->set...
=> Aspects are read-only
adding vars to the user data in the getUser method of the AuthenticationService
(packages/extension_name/Classes/Service/FrontendOAuthService.php)
public function getUser()
{
$user = allBusinessCodeHere();
$user['my_own_key'] = 'myData';
return $user;
=> is not propagated to the UserAspect(frontend.user) nor the $TSFE->fe_user
I'm out of ideas guys.
I had a similar problem when i wanted to use redirects with record links.
I ended up disabling the original redirect middleware and adding my own with a mocked version of tsfe.
The extension can be found here:
https://github.com/BenjaminBeck/bdm_middleware_redirect_with_tsfe
Late to the party, but I had the same issue and was able to solve it:
https://docs.typo3.org/c/typo3/cms-core/master/en-us/Changelog/10.0/Breaking-88540-ChangedRequestWorkflowForFrontendRequests.html states:
Storing session data from a Frontend User Session / Anonymous session
is now triggered within the Frontend User
(frontend-user-authenticator) Middleware, at a later point - once the
page was generated. Up until TYPO3 v9, this was part of the
RequestHandler logic right after content was put together. This was
due to legacy reasons of the previous hook execution order. Migration
Consider using a PSR-15 middleware instead of using a hook, or
explicitly call storeSessionData() within the PHP hook if necessary.
In my MyAuthenticationService extends AbstractAuthenticationService in method getUser() I set $_SESSION['myvendor/myextension/accessToken'] to the token received by the external oauth service. In my SaveSessionMiddleware I save this token to the FrontendUserAuthentication object using setKey() which by then is available:
EXT:myextension/Configuration/RequestMiddlewares.php
return [
'frontend' => [
'myvendor/myextension/save-session-middleware' => [
'target' => \MyVendor\MyExtension\Middleware\SaveSessionMiddleware::class,
'after' => [
'typo3/cms-frontend/authentication',
],
]
]
];
EXT:myextension/Classes/Middleware/SaveSessionMiddleware.php
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication;
class SaveSessionMiddleware implements MiddlewareInterface {
/**
* #param ServerRequestInterface $request
* #param RequestHandlerInterface $handler
* #return ResponseInterface
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface {
if (!empty($_SESSION['myvendor/myextension/accessToken'])) {
$this->getFrontendUserAuthentication()->setKey(
'ses',
'myvendor/myextension/accessToken',
$_SESSION['myvendor/myextension/accessToken']);
unset($_SESSION['myvendor/myextension/accessToken']);
}
return $handler->handle($request);
}
private function getFrontendUserAuthentication(): FrontendUserAuthentication {
return $GLOBALS['TSFE']->fe_user;
}
}

Wrapping JSON response in an abstracted way in PHP / Laravel

I am making a REST API that will return different JSON responses depending on which type of User is making the call.
There is a single endpoint: example.com/api/v1/collect that uses Laravel's API authentication to get the User model with $user = auth()->guard('api')->user();.
Each User will belong to a Type.
If User 1 (type_id 1) makes the call, the response will look like:
{
"example": 1,
"success": true,
"data" : [
...
]
}
If User 2 (type_id 2) makes the call, the response can be different, depending on the user's type. It could look like:
{
"example": 2,
"response" : [
...
],
"custom": "string",
"success": 200
}
The ... is the data that we are sending back (for example a list of Post titles) and it will always be the same, but the "envelope" (or wrapper) around it would be specific to each user (or type of user).
So far, I've found two solutions to wrap that ... in an abstracted way:
Solution 1: Using Laravel Blade
// Api\V1\ApiController.php
$data = $user->posts->pluck('title');
// Each type of user will have a different blade filename
// There could be around a 100 types which will result in a 100 blade files
// The filename is stored in the database
$filename = $user->type->filename; // returns 'customBladeTemplate'
// Return a JSON response after passing the $data to the view
return response()->json([
view($filename, compact('data'))->render(),
]);
Using a blade file for each type of user allows me to wrap the data like this:
// resources/views/customBladeTemplate.blade.php
// This filename has to match the one in the database column
{
"example": 1,
"success": true,
"data" : [
{!! $data !!}
]
}
That will output a JSON response for the User 1 (example 1)
Solution 2: Using Laravel response macros
// Api\V1\ApiController.php
$data = $user->posts->pluck('title');
// Each type of user will have a different macro name
// There could be around a 100 types which will result in a 100 different macros
// The macro name is stored in the database
$macroName = $user->type->macro_name; // returns 'customMacroName'
return response()->{macroName}($data);
Creating a Macro for each type of user, using the macro name from the DB:
// App\Providers\AppServiceProvider.php
use Illuminate\Http\Response;
public function boot()
{
Response::macro('customMacroName', function ($data) {
return Response::json([
'example' => 2,
'response' => $data,
'custom' => 'string',
'success' => 200,
]);
});
}
That macro will output a JSON response for the User 2 (example 2)
Both options work fine but I am still wondering:
Is there another (possibly better) way to do it?
Are those two solutions valid or can they be enhanced?
Which of those two solutions seem to be better and why?
Edit: The $data is not actually coming from an eloquent model, it is rather from a serialized JSON column (JSON casting) - which means I can't use the Laravel API resources
If you are looking for the response formatting you should go with the Laravel API Resources
Based on your requirement(data formate different for two type of users), you can create two different Api Resource classes.
AdminResource & UserResource.
Here you have more flixibility on controlling fields or orgnizing data.
Here is how you can define the resource class:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\Resource;
class UserResource extends Resource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request
* #return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
}
And you can use this as:
use App\User;
use App\Http\Resources\UserResource;
Route::get('/user', function () {
return new UserResource(User::find(1));
});
If you would like to include with condition check in with user type, you can create a common function called renderJson($userType, $data) and place this in your parent class or can wrap with traits, all depends on your application architecture.
Here you can find laravel documentation for API Resource: https://laravel.com/docs/5.8/eloquent-resources
Edited:
With Laravel API Resource, you not only parse the modal object, you can parse any arrayble object.
Essentially they are just simple objects with one very important job
to do — transform your objects (interesting I said objects and not
models). To do this out of the box, all you have to do is instantiate
the Resource (collection or individual) with an Arrayable object. If
you did nothing else but generate a standard Resource and pass in an
Arrayable object the Resource would transform that object
automatically, and because Models are Arrayable this is where I got
caught out because if you create a resource collection and instantiate
it with a collection of models then the models get toArray'd and not
their corresponding resource.
Src: https://medium.com/#dinotedesco/laravel-api-resources-what-if-you-want-to-manipulate-your-models-before-transformation-8982846ad22c
So in your case if you can just collect() the json data and pass to api resource.
You could use middlewares to change what the response looks like.
With middleware you could change the response after the execution of your regular code, without having to take this into account in the controller itself. Using the below code you modify the response AFTER it has been executed.
<?php
namespace App\Http\Middleware;
use Closure;
class AfterMiddleware
{
public function handle($request, Closure $next)
{
// Calls the controller and processes the request.
$response = $next($request);
// Here you can retrieve the user and modify the response however you want.
// Some example code:
$user = Auth::user();
if ($user->type == 1) {
... //Change response for user type 1
}
if ($user->type == 2) {
... //Change response for user type 2
}
// Etc...
return $response;
}
}
Reference: https://laravel.com/docs/5.8/middleware
Depends on how different the responses are from each other. I'm inclined to take inventory of each type's common features and build a response array as appropriate. This could be done in the controller or a helper function and then returned using Laravel's JSON response type.
$response = [];
// results common to all types
$response['example'] = $example;
$response['success'] = $success;
// customized results for specific types
if (in_array($type, [1, 3, 4, 5, ...])) {
$response['data'] = $dotdotdot;
}
if (in_array($type, [2, 6, 7, 8, ...])) {
$response['response'] = $dotdotdot;
$response['custom'] = $custom;
}
return response()->json($response);
I don't know if this is what you are looking for. I had something similiar a few months ago and fixed it with json files. As json is amazingly fast and you can create thousands of types.
Sorry for my bad english i will fix it after the weekend :-)
Let's get started.
First the user logs in using laravel passport or api routes.
Second the api calls a controller.(class). I will create a class based on your info.
let's say the api calls the ApiController and the method handle
use Illuminate\Http\Request;
class ApiController
{
public function __construct()
{
}
/**
* Handle the incoming request
*
* #param Request $request
*/
public function handle(Request $request)
{
//first let's find the correct format
$type = $requets->user()->type; //lets say type_1
$config = $this->getUserType($type);
//i don't know where you data comes from but let's say $data is your data.
$data = json_encode(['some' => "data", 'to' => "return"]);
//lets fill the data
$response = $this->fillDataInConfig($data, $config);
return response()->json($response);
}
/**
* Find the user type config by type name
*
* #param string $type
* #return object
*/
private function getUserType(string $type) : string
{
//let store them in the local storage
$config = \Storage::disk('local')->get("api_types/$type.json");
return json_decode($config);
}
/**
* Fill the data
*
* #param mixed $data
* #param object $config
* #return object
*/
private function fillDataInConfig($data, object $config) : object
{
//as for your example. The reusl//
// {
// "example": 2,
// "response" : *responseData*, <===== where the response should be
// "custom": "string",
// "success": 200
// }
foreach($config as $index => $value){
if($value === '*responseData*'){
$config->{$idnex} = $data;
}
}
//the data is filled in the response index
return $config;
}
}

creation of unit tests with users

I want to test the next method of my controller
function index(){
if(Auth::User()->can('view_roles'))
{
$roles = Role::all();
return response()->json(['data' => $roles], 200);
}
return response()->json(['Not_authorized'], 401);
}
it is already configured for authentication (tymondesigns / jwt-auth) and the management of roles (spatie / laravel-permission), testing with postman works, I just want to do it in an automated way.
This is the test code, if I remove the conditional function of the controller the TEST passes, but I would like to do a test using a user but I have no idea how to do it.
public function testIndexRole()
{
$this->json('GET', '/role')->seeJson([
'name' => 'admin',
'name' => 'secratary'
]);
}
Depends on what kind of app are you building.
A - Using Laravel for the entire app
If your using Laravel for frontend/backend, well to simulate a logged-in user you could use the awesome Laravel Dusk package, made by the Laravel team. You can check the documentation here.
This package has some helpful methods to mock login sessions amongs a lot more of other things, you can use:
$this->browse(function ($first, $second) {
$first->loginAs(User::find(1))
->visit('/home');
});
That way you hit an endpoint with a logged-in user of id=1. And a lot more of stuff.
B - Using Laravel as a backend
Now, this is mainly how I use Laravel.
To identify a user that hits an endpoint, the request must send an access_token. This token helps your app to identify the user. So, you will need to make and API call to that endpoint attaching the token.
I made a couple of helper functions to simply reuse this in every Test class. I wrote a Utils trait that is being used in the TestCase.php and given this class is extended by the rest of the Test classes it will be available everywhere.
1. Create the helper methods.
path/to/your/project/ tests/Utils.php
Trait Utils {
/**
* Make an API call as a User
*
* #param $user
* #param $method
* #param $uri
* #param array $data
* #param array $headers
* #return TestResponse
*/
protected function apiAs($user, $method, $uri, array $data = [], array $headers = []): TestResponse
{
$headers = array_merge([
'Authorization' => 'Bearer ' . \JWTAuth::fromUser($user),
'Accept' => 'application/json'
], $headers);
return $this->api($method, $uri, $data, $headers);
}
protected function api($method, $uri, array $data = [], array $headers = [])
{
return $this->json($method, $uri, $data, $headers);
}
}
2. Make them available.
Then in your TestCase.php use the trait:
path/to/your/project/tests/TestCase.php
abstract class TestCase extends BaseTestCase
{
use CreatesApplication, Utils; // <-- note `Utils`
// the rest of the code
3. Use them.
So now you can do API calls from your test methods:
/**
* #test
* Test for: Role index
*/
public function a_test_for_role_index()
{
/** Given a registered user */
$user = factory(User::class)->create(['name' => 'John Doe']);
/** When the user makes the request */
$response = $this->apiAs($user,'GET', '/role');
/** Then he should see the data */
$response
->assertStatus(200)
->assertJsonFragment(['name' => 'admin'])
->assertJsonFragment(['name' => 'secretary']);
}
Side note
check that on top of the test methods there is a #test annotation, this indicates Laravel that the method is a test. You can do this or prefix your tests names with test_

Categories