zend expressive - middleware checks if there is a next one available? - mezzio

In my routes.global.php I've this one in routes.
[
'name' => 'test',
'path' => '/test',
'middleware' => [App\Action\Test1Action::class, App\Action\Test2Action::class],
'allowed_methods' => ['GET'],
],
And I've this return $next($request, new JsonResponse($data)); at the end of Test1Action class so it will send the data to the next action.
But is there a way inside Test1Action to check if there's another action after?
Maybe there's another way so I can do the above return if there is one after or return the json response rite away.
return new JsonResponse($data);
That way I can either use Test1Action alone or plug it in before other action.
I tried few options but didn't work. Any help will be great. Thanks.

Maybe you can check $next if it's null or not. But it might always be set, I've never tried it. However in Expressive 2 it's always set and it changes to Delegates. Also the last middleware will always be the NotFoundHandler.
Since you develop your application yourself you know the order of middleware and Actions. I would let Test1Action middleware do it's thing, add the result to the $request as an attribute and let the next middleware figure out if the data was set or not. If the data wasn't set than skip it and execute the next middleware in line. It makes it a lot easier.

Related

Is there a one time link generation Laravel?

Is it possible to create a one time link in Laravel? Once you open the link it expires?
I have created a Temporary Signed Link, but I can open it multiple times. How do I counter it?
There is this package that can help you
https://github.com/linkeys-app/signed-url/
This will generate a link valid for 24hours and for just one click .
$link = \Linkeys\UrlSigner\Facade\UrlSigner::generate('https://www.example.com/invitation', ['foo' => 'bar'], '+24 hours', 1);
The first time the link is clicked, the route will work like normal. The second time, since the link only has a single click, an exception will be thrown. Of course, passing null instead of '+24 hours' to the expiry parameter will create links of an indefinite lifetime.
There maybe a package that provides a functionality like this... always worth looking on Packagelist before building something rather generic like this from scratch. But, it's also not a hard one to build from scratch.
First you'll need database persistence, so create a model and a migration called UniqueLink. In the migration you should include a string field called "slug", a string field called path, and a timestamp field called "used_at."
Next create a controller with a single __invoke(string $slug) method. In the method look up the $link = UniqueLink::where('slug', $slug)->first(); Update the models' used_at parameter like so $link->update(['used_at' => Carbon::now()]);
Then return a redirect()->to($link->path);
Add a route to your routes file like this Route::get('/unique-link/{slug}', UniqueLinkController::class);
Now you'll just need to create a method to add these links to the db which create a slug (you could use a UUID from Str::uuid() or come up with something more custom) and a path that the link should take someone. Over all a pretty straight forward functionality.
You could track when the URL is visited at least once and mark it as such for the user if you really want to, or you could reduce the expiry down to a few mins.
URL::temporarySignedRoute( 'foobar', now()->addMinutes(2), ['user' => 100] );

How to feature test more complicated cases on Laravel using PHPUnit

I'm using Laravel for my project and I'm new to unit/feature testing so I was wondering what is the best way to approach more complicated feature cases when writing tests?
Let's take this test example:
// tests/Feature/UserConnectionsTest.php
public function testSucceedIfConnectAuthorised()
{
$connection = factory(Connection::class)->make([
'sender_id' => 1,
'receiver_id' => 2,
'accepted' => false,
'connection_id' => 5,
]);
$user = factory(User::class)->make([
'id' => 1,
]);
$response = $this->actingAs($user)->post(
'/app/connection-request/accept',
[
'accept' => true,
'request_id' => $connection->id,
]
);
$response->assertLocation('/')->assertStatus(200);
}
So we've got this situation where we have some connection system between two users. There is a Connection entry in the DB created by one of the users. Now to make it a successful connection the second user has to approve it. The problem is within the UserController accepting this through connectionRequest:
// app/Http/Controllers/Frontend/UserController.php
public function connectionRequest(Request $request)
{
// we check if the user isn't trying to accept the connection
// that he initiated himself
$connection = $this->repository->GetConnectionById($request->get('request_id'));
$receiver_id = $connection->receiver_id;
$current_user_id = auth()->user()->id;
if ($receiver_id !== $current_user_id) {
abort(403);
}
[...]
}
// app/Http/Repositories/Frontend/UserRepository.php
public function GetConnectionById($id)
{
return Connection::where('id', $id)->first();
}
So we've got this fake (factory created) connection in the test function and then we unfortunately are using its fake id to run a check within the real DB among real connections, which is not what we want :(
Researching I found an idea of creating interfaces so then we can provide a different method bodies depending if we're testing or not. Like here for GetConnectionById() making it easy to fake answers to for the testing case. And that seems OK, but:
for one it looks like a kind of overhead, besides writing tests I have to make the "real" code more complicated itself for the sole purpose of testing.
and second thing, I read all that Laravel documentation has to say about testing, and there is no one place where they mention using of interfaces, so that makes me wonder too if that's the only way and the best way of solving this problem.
I will try to help you, when someone start with testing it is not easy at all, specially if you don't have a strong framework (or even a framework at all).
So, let me try help you:
It is really important to differentiate Unit testing vs Feature testing. You are correctly using Feature test, because you want to test business logic instead of a class directly.
When you test, my personal recommendation is always create a second DB to only use with tests. It must be completely empty all the time.
So, for you to achieve this, you have to define the correct environment variables in phpunit.xml, so you don't have to do magic for this to work when you only run tests.
Also, use RefreshDatabase trait. So, each time you run a test, it is going to delete everything, migrate your tables again and run the test.
You should always create what you need to have as mandatory for your test to run. For example, if you are testing if a user can cancel an order he/she created, you only need to have a product, a user and an invoice associated with the product and user. You do not need to have notifications created or anything not related to this. You must have what you expect to have in the real case scenario, but nothing extra, so you can truly test that it fully works with the minimum stuff.
You can run seeders if your setup is "big", so you should be using setup method.
Remember to NEVER mock core code, like request or controllers or anything similar. If you are mocking any of these, you are doing something wrong. (You will learn this with experience, once you truly know how to test).
When you write tests names, remember to never use if and must and similar wording, instead use when and should. For example, your test testSucceedIfConnectAuthorised should be named testShouldSucceedWhenConnectAuthorised.
This tip is super personal: do not use RepositoryPattern in Laravel, it is an anti-pattern. It is not the worst thing to use, but I recommend having a Service class (do not confuse with a Service Provider, the class I mean is a normal class, it is still called Service) to achieve what you want. But still, you can google about this and Laravel and you will see everyone discourages this pattern in Laravel.
One last tip, Connection::where('id', $id)->first() is exactly the same as Connection::find($id).
I forgot to add that, you should always hardcode your URLs (like you did in your test) because if you rely on route('url.name') and the name matches but the real URL is /api/asdasdasd, you will never test that the URL is the one you want. So congrats there ! A lot of people do not do this and that is wrong.
So, to help you in your case, I will assume you have a clear database (database without tables, RefreshDatabase trait will handle this for you).
I would have your first test as this:
public function testShouldSucceedWhenConnectAuthorised()
{
/**
* I have no idea how your relations are, but I hope
* you get the main idea with this. Just create what
* you should expect to have when you have this
* test case
*/
$connection = factory(Connection::class)->create([
'sender_id' => factory(Sender::class)->create()->id,
'receiver_id' => factory(Reciever::class)->create()->id,
'accepted' => false,
'connection_id' => factory(Connection::class)->create()->id,
]);
$response = $this->actingAs(factory(User::class)->create())
->post(
'/app/connection-request/accept',
[
'accept' => true,
'request_id' => $connection->id
]
);
$response->assertLocation('/')
->assertOk();
}
Then, you should not change anything except phpunit.xml environment variables pointing to your testing DB (locally) and it should work without you changing anything in your code.

Skip first index of array validation rule?

Good Evening Devs,
I'm trying to skip the first index of the array while applying validation rule and this is what I tried so far
$validatedData = Validator::make($request->all(),([
'inventories.0' => 'bail',
'inventories' => 'required|array|filled',
'quantities.0' => 'bail',
'quantities.*' => 'required|array|filled',
'required.0' => 'bail',
'required.*' => 'required|array|filled',
]));
But it's not working, any ideas?
I'm trying to add multiple dynamic fields, but want to skip the first index of it.
Please review the picture given below to get the clear picture of the problem.
try this:
$validatedData = Validator::make($request->except(['inventories[0],quantities[0],required[0]']),([
'inventories.*' => 'required|array|filled',
'quantities.*' => 'required|array|filled',
'required.*' => 'required|array|filled',
]));
Bail is not used for skipping an entry. But it may be used for skipping validation logic.
for example,
'phone' => 'bail|numeric|unique:users'
In this case, if somehow the entered phone number is not numeric, it will not check the third validation (i.e. whether the phone number is unique in 'users' table or not).
For your case, you should not use "$request->all()". You should use "request()->except(['inventories[0], quantities[0], required[0]'])" instead
This is perhaps, not the best practice. You're trying to allow the presentation layer to have a direct influence over the data / logic layer of your application. It would probably be better to only send over the data you want to validate rather than sending over everything and they tying to get your validation (and other logic) to ignore the first array element.
Is it an api call or a standard web form you are submitting? If it is an api call, can you not build up your data of only the rows you want to send over, before you make the call?
This will keep your logic layer much cleaner, and allow you to change the ui much easier without affecting the logic, and it being tightly coupled.
Just a suggestion.

How to add / remove elements from array that is in Request

My request looks like this
Array
(
[name] => Eugene A
[address] => Array
(
[billing] => Array
(
[address] => aaa
)
[shipping] => Array
(
[address] => bbb
)
)
)
I need to delete the shipping address. But how?
I can only delete both addresses,
$request->request->remove('address');
but I don't want it.
I want to delete only shipping address, like so
$request->request->remove('address.shipping');
But it is not working for me
Laravel 5.6
Update
Why do I need it?
Easy. I have abstracted out my Form Request validation into a class that is a child to Illuminate\Foundation\Http\FormRequest.
I actually have few classes for validation. I call them one by one in a controller like so:
app()->make(CustomerPostRequest::class); // validate Customer information
app()->make(AddressSaveRequest::class); // validate Addresses
Why?
Now I can Mock this requests in unit-tests, and I can have my validation abstracted out. And I can use Address validation in many places.
But Now I need more flexibility. Why?
Because AddressSaveRequest rule looks like this
public function rules(): array
{
return [
'address.*.address' => [
'bail',
'required',
'string',
],
...
It validates all addresses.
But sometimes I don't want to validate shipping address, if the the chech_box - ship_to_the_same_address is ticked.
But I have my Address validator abstracted in separate file and it is used in many places. There are places where ship_to_the_same_address tick box is not presented.
Thus I cannot use 'required_unless:ship_to_same_address,yes',
And I cannot use
app()->makeWith(AddressSaveRequest::class, ['ship_to_the_same_address ' => 'yes']);
Because Taylor said ...when calling makeWith. In my opinion it should make a new instance each time this method is called because the given parameter array is dynamic.. And it does, and it does not work correctly with app()->instance(AddressSaveRequest::class, $addressSaveRequest); and cannot be mocked in unit tests.
Why Taylor decided it - I seriously don't know.
PS
And yes, I know that mocking requests is not recommended.
If you were trying to add or remove inputs from the Request itself:
You can add data to the request pretty easily by merging it in and letting Laravel handle which data source is being used:
$request->merge(['input' => 'value']);
That will merge in the input named input into the input source for the Request.
For removing inputs you could try to replace all the inputs without that particular input in the replacement:
$request->replace($request->except('address.shipping'));
Just one idea to try.
Try this:
$request->except(['address.shipping']);
Details: Laravel Request
Laravel has a helper method called array_forget, which does exactly what it sounds like:
$requestArray = $request->all();
$newArray = array_forget($requestArray, 'address.shipping')
Documentation
After the edit to the main question with why some inputs of the request are to be deleted, my main answer isn't correct anymore. User Lagbox has the correct answer for the question that was asked.
However, I would like to note that another solution would be to have seperate Request classes with validation. One for placing an order (assuming it is a system where someone can order stuff) where ship_to_same_address is present and another one for things like updating your account, like PlaceOrderRequest and UpdateAccountRequest classes.

Event::listen not catching laravel.query

I am pretty new to Laravel. I am working on a REST api and was trying to look at the queries that were being generated from the models. In my routes I have a route group set up.
Route::(["before" => "auth", function()
{
Route::model("juror_subject", "JurorSubject");
Route::get("juror_subject", [
"as" => "juror_subject/index"
"uses" => "JurorSubjectController#index"
]);
});
I wanted to see what query was actually being run. I was watching a video by Jeffrey Way and he mentions that you can use Event::listen to see the query like so.
In routes:
Event::listen('laravel.query', function($sql){
var_dump($sql);
});
However, when I load the url:
localhost:8080/api/juror_subject
It returns the json response and never seems to fire the laravel.query event.
Am I missing some element that is needed to get event listeners to work properly? Is the type of routing I am using not firing a query? If so, how would I go about dumping the queries using a route group?
laravel.query is for Laravel 3 , use illuminate.query instead, also check this answer for more details.
Moreover, call DB::getQueryLog() to get all ran queries ( no need for listener), or use this package which is pretty neat.

Resources