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

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.

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] );

Laravels syncWithoutDetaching and additional data

I have Googled my fingers sore, and I can't see anyone discussing this, but I have a suspicion that Laravels syncWithoutDetaching() method doesn't take any parameters for extra data like save(), sync() and attach() does?
Does anybody know this? In the API documentation the method has the following parameters:
array syncWithoutDetaching(Collection|Model|array $ids)
I have trouble adding existing data to a relationship between a Guest and an Event. I need to add status for the guests and what event they are attending, maybe attending or declined.
sync() and syncWithoutDetaching() both don't have a parameter for additional values, you have to pass the additional values as an array with the ids.
According to the docs:
You may also pass additional intermediate table values with the IDs:
$user->roles()->sync([
1 => ['expires' => true],
2,
3
]);
If you look here you can see that syncWithoutDetaching() just calls sync() but passes false as the second argument.
In your case it would be something like this:
$event->guests()->syncWithoutDetaching([
1 => ['attending' => true],
2 => ['attending' => false]
])
I think #remul answer is the best, but it requires additions for people like me who get to this page.
syncWithoutDetaching() - is just an abbreviation for sync() - here. This corresponds to sync($data, false)
The documentation talks about another great method:
If you would like to insert the same intermediate table values with
each of the synced model IDs, you may use the syncWithPivotValues
method
But the documentation does not say that the method accepts the third argument, which just corresponds to the logic of syncWithoutDetaching().
Look here.
If you pass false, the IDs not passed will not be detaching.
I think this is what the question was about.

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.

MVC Putting an action in the most appropriate correct controller

I was just wondering what the best practice approach is for deciding where to create an action/view in certain situations.
If User hasMany Video
where is the best place to create the action/view to show user videos?
So within the Users account page 'My Videos' link do you
just create a users/my_videos action and view.
create videos/my_videos action and view.
or as is most likely you would already have a Controller/Action of videos/index which would have search functionality. Simply use this passing in a user id.
Any thoughts/advice greatly appreciated
Thanks
Leo
One potential option is to do the following:
Since the videos likely have much more code around them than a simple which user has which videos lookup the video list action should be in the VideosController.
In past projects I have (in CakePHP 1.3) used prefix routing to address some of this.
In config/core.php make sure you enable routing.prefixes to include a 'user' prefix.
<?php
... in routes.php ...
Routing.prefixes = array( 'user' );
?>
In the videos controller make an action with the following signature:
<?php
...
public function user_index( $userID = null ){
...
}
?>
and in the views where you link to the list of users videos the html::link call should look similar to the following:
<?php
...
echo $this->Html->link( 'User\'s Videos', array(
'controller' => 'videos',
'action' => 'index',
'prefix' => 'user',
$this->Session->read( 'Auth.User.id' )
));
?>
Of course this assumes you are using the Auth component here to track the logged in user. The Session helper code to read the authenticated user id might need tweaking.
This lets you a) Not worry too much about routing aside from enabling prefix routing and b) will quickly let you have pretty links like so -- site.com/user/videos/index/419
Couple this with some Slug love ( this is the best link for this I have seen - no slug field required on the db layer - http://42pixels.com/blog/slugs-ugly-bugs-pretty-urls )
You could even end up with urls like so quite easily: site.com/user/videos/index/eben-roux
and with just a tiny bit of editing to app/config/routes.php you could eliminate the /index/ portion and the results would be SEO friendly and user friendly in the format:
site.com/user/videos/eben-roux
http://book.cakephp.org/view/945/Routes-Configuration
As always with code you have the two extremes of:
1) Putting everything in a single controller
2) Having every action in a separate controller
The ideal approach will nearly always be somewhere between the two so how to decide what is grouped together and what is separated?
In MVC I tend to look at the Views and see what the commonalities are: as you point out Users have a ref to a collection of Videos in the Model, but would you want both sets of Data in any single View? i.e. In this example is it likely that you would be on a page that both managed user details, and displayed the list of vids? If not then I'd suggest separate controllers.
If either controller would then be extremely simple - e.g. one method, then may be worth considering merging the two.
I like to keeps things separate.
What I'd do is an index action in videos controller, passing user's id as argument and then displaying only current users video.
public function index($id = null){
$this->paginate = array( 'conditions'=> array('Video.user_id' => $id));
$this->set('videos', $this->paginate());
}
My take is that it depends on the responsibility you assign to the controllers.
I would say that something like a User or a Video controller should be concerned with only those entities.
You may want to consider something like a UserDashboard (or something similar but appropriately named) as alluded to by Dunhamzzz in the comments. This can aggegate all the functionality from an "entry" point-of-view. The same way a banner / shortcut / action menu would work.
Your UserDashboard would use whatever data layer / repository is required to get the relevant data (such as the IVideoRepository or IVideoQuery implementation).
Usually when something doesn't feel right it isn't. Try splitting it out and see how it works. You can alsways re-arrange / refactor again later.
Just a thought.
I don't think there's a 'one-rule-fits-all' solution to this question, but I would try to take an approach in which you would determine what the main object is that you're dealing with, and adding the action/view to that object's controller.
In your example I'd say that your main object is a video and that the action you're requiring is a list of video's filtered by a specific property (in this case the user's id, but this could very well be a category, a location, etc.).
One thing I would not do is let your desired URL determine in which controller you put your functionality. URLs are trivially changed with routes.

CakePHP soft deleted data still showing up in model associations

I am using the SoftDeletableBehavior for my addresses model.
This sets it so when you delete an address it doesnt really delete it, instead sets a deleted column in the database to 1.
This also changes your query in the beforeFind function of the Address model to only pull entries where deleted != 1
This works when I am viewing addresses via the addresses controller like /addresses/show/43.
However my Users controller hasMany addresses. So when I am in the users controller and I call $this->find('first'); To get the user, I also get the associated addresses. I am expecting this to NOT give me the (softly) deleted addresses but it DOES. The beforeFilter on the Address Model is never called either.
What is the right way to handle this?
Update.
Apparently when I do this:
$data = $this->User->find('first',array('conditions' => array('User.id' => $id),'recursive' => 2));
I have a $data['User] array and a $data['Address] array (along with others).
But this does not use the beforeFind filter in the Address Model. So I get the wrong data.
But if I do this:
$data = $this->User->find('first',array('conditions' => array('User.id' => $id),'recursive' => 2));
$data['Address'] = $this->User->Address->find('all',array('conditions'=>array('user_id'=>$id)));
Then it does use the beforeFind filter on the Address model and returns the right data.
(Of course in a different format [Address][0][id] vs [Address][0][Address][id])
What I do not understand is if the associated data thats pulled does not use its own model to find its data, then whats the point? Isn't this one of the main purposes of MVC?
Maybe I just don't understand? Or I am missing something?
Can someone please enlighten me?
Can't you put this condition in your association?
<?php
class User extends AppModel {
var $hasMany = array(
'Address' => array(
'conditions' => array('NOT' => array('Address.deleted' => '1')),
)
);
}
?>
Personally, I would add a condition to the find query for users
$this->Users->find->('all',array('conditions'=>array('Address.deleted !='=>'1')));
(haven't tested the code for exact syntax, but this is the general idea)
When you use $this->User->find(...) and include Addresses, you will only see the User model's callbacks fire, because that is the model you are using to drive the find.
This is why you have to use extra conditions in your User model's associations to filter out the soft-deleted Addresses.
If the Address model's callbacks were fired during every find on the User model when recursion scope included Addresses, you'd have no control as to when to include the soft-deleted Addresses and would risk clashes in functionality and param manipulation by those callbacks. Not to mention the serious performance impact this could have.

Resources