Issue with Integers passing data from Vue to Laravel using FormData - laravel

I am successfully updating a database using Vue 2 to a Laravel 8 Controller using Axios. However, I am stuck when attempting to pass an integer to my database.
My database has a column, 'number_of_searches' and it must be an integer.
Laravel Migration looks like this:
$table->integer('number_of_searches')->nullable();
And the model looks something like this:
class Product extends Model
{
protected $fillable = [
'product_title',
'number_of_searches' => 'integer',
];
}
My Vue updateProduct() function used FormData and appends the values coming from the form. It looks like this:
updateProduct(product){
let data = new FormData();
data.append('_method', 'PATCH');
data.append('product_title', product.product_title);
data.append('number_of_searches', product.number_of_searches);
axios.post('/api-route-to-update/product_id/', data)
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
}
My update controller looks like this:
public function update(Request $request, $product_id){
$product = Product::findOrFail($product_id);
$product->update($request->all());
$product->save();
}
I can have as many input fields as I need and it works perfectly as long as they are strings. However, when I use a number input field in my component such as:
<input v-model="product.number_of_searches" type="number" min="1" max="999">
The generated json that will pass from axios into my controller looks like this:
{ "id": 5, "product_title": "The Product Title will work great", "number_of_searches": "222"}
You will notice that 'number_of_searches' is passed as a string, hence my database fails because it is the wrong datatype, it requires an integer. After reading the documentation and other threads, it seems that FormData will always return strings and that I must just deal with this on the server side.
So what I did is, I went into my back-end updateProduct() method and attempted to modify the Request.
First I tried a few methods such as:
//this one
$requestData = $request->all();
$requestData['number_of_searches'] = 123;
//also this one
$request->merge(['number_of_searches' => 123]);
//and this
$data = $request->all();
$data['number_of_searches'] = 123;
After countless hours, I am unable to modify the original request. After doing some research, it seems that requests are protected and cannot be modified, which makes sense. Therefore I attempted to create a new request that clones $request->all(), like this:
$new_request = new Request($request->all());
$new_request->merge(['number_of_searches' => 123]);
But I have failed to force to override 'number_of_searched'
My question is:
Should I stay away from FormData completely in this case? What method do you suggest to pass forms that have integers or floats or other datatypes through axios or fetch? Or what am I doing wrong? I find it hard to believe that FormData would only send strings (making parseInt useless before using axios). I'm sure I am doing something wrong from origin.
On the other hand, maybe I need to completely change my approach in my Controller when receiving the data. I am working on an app with a lot of fields and I love $request->all() because it simplifies what I am trying to do. I wouldn't mind using intval on the server side and that's it, but it seems overly complicated.

On the Vue side, you can use the number modifier on v-model to make sure it's not casting the value to a string:
v-model.number="product.number_of_searches"
On the request side, you can use $request->merge to override the value in a request
$request->merge([
'number_of_searches' => (int) $request->get('number_of_searches');
]);
At the model side in the updating hook within the boot method you can ensure the value is being casted as an int when saving:
static::updating(function ($model) {
$model->number_of_searches = (int) $model->number_of_searches;
});
This should give you the end to end.

Related

laravel api without respond

I have a issue, after i eliminate cors policy on laravel i sending some json data to check respond. But nothing happens...
I sending request by axios using react.js, i sending json data collected from state.
and now i trying to collect that data by laravel, but that is hardest patch.
already try something like that:
$content='test';
return Response::$content;
or just echo 'test' but nothing comes...
My code is inside controller.
class testRequest extends Controller
{
public function show(Request $request)
{
//$data = $request->json()->all();
// $experience = $data->experience;
$content='test';
return Response::$content;
}
}
for now i expect to get respond like 'test' but after that i will need to send a link to file path for respond.
the Response::$content is just wrong... the :: operator is used to access static member functions or attributes of the Response class... you should do something like this:
return Response::json(['test' => $content]);
or
return response()->json(['test' => $content]);
in order to respond with a JSON document.

How can I Implement Zend Framework 3 + Ajax Pagination?

Does anyone know how to implement Ajax Pagination with Zend Framework 3 (zf3)?
I used Doctrine ORM to retrieve data from database.
Sure. The same as a normal GET request really, only will you respond dynamically because it concerns an xml http request.
Take, for example, the following indexAction
use Zend\View\Model\JsonModel;
// class, other use-statements, etc
public function indexAction()
{
$page = $this->params()->fromQuery('page', 1); // get page from GET, default page 1
/** #var QueryBuilder $qb */
$qb = $this->getObjectManager()->createQueryBuilder();
$qb->select('u')
->from(User::class, 'u')
->orderBy('u.createdAt', 'DESC');
$paginator = new Paginator(new OrmAdapter(new OrmPaginator($qb)));
$paginator->setCurrentPageNumber($page);
$paginator->setItemCountPerPage(25);
if ($this->getRequest()->isXmlHttpRequest()) {
return new JsonModel([
'paginator' => $paginator,
'queryParams' => $this->params()->fromQuery(),
]);
}
return [
'paginator' => $paginator,
'queryParams' => $this->params()->fromQuery(),
];
}
Here you would normally end up at the bottom most return statement for a standard GET request. In case of an ajax type request, the statement $this->getRequest()->isXmlHttpRequest() returns true and you know it's something send via, let's say $.ajax / $.get / $.post (usually, unless native JS or something similar). In these cases you want to respond with just the data, not a completely rendered views. This is when you return the JsonModel.
To make sure it works as intended, you must also have the JsonViewStrategy enabled in your configuration. You might wish to enable this in your global.php instead of just a module, like below, to enable it everywhere:
'view_manager' => [
//...
'strategies' => [
'ViewJsonStrategy',
],
],
The only things left to do, then, are client-side things with JavaScript. Like making sure you update the pagination, page contents, etc. Maybe a URI anchor...

Delete Method in Axios, Laravel and VueJS

I am trying to send a delete request via axios to laravel as follow:
axios.delete('api/users/' + this.checkedNames)
.then((response) => {
console.log(response)
}, (error) => {
// error callback
})
Now from axios documentation I read that for delete request we should be using a configObject so the above could be rewritten as so:
axios.delete('api/users/', {params: {ids:
this.checkedNames})
.then((response) => {
console.log(response)
}, (error) => {
// error callback
})
I have then Route::resource('users', 'UsersController'); in api.php so the default route for deleting is:
DELETE| api/users/{user}| users.destroy
and the controller's method is:
|App\Http\Controllers\UsersController#destroy
I am able to delete as expected a user when I pass a single id let's say api/users/12, it gets deleted correctly but when I try to pass the array above things get complicated.
if I try as per axios documentation axios.delete('api/users/', {params: {id: this.checkedNames}}) it looks I am sending this http://lorillacrud.dev/api/users?id[]=21&id[]=20 but I get a 405 method not allowed.
if I try axios.delete('api/users/' + this.checkedNames ) I get http://lorillacrud.dev/api/users/20,21 so in my destroy method I could grab the ids and delete, but I am wondering if this is the correct way to do it?
update
I seemed I made it work but I am not understanding so any help still appreciated to make a sense of what I am actually making work!
So, if change to:
axios.delete('api/users/destroy', {params: {'id': this.checkedNames})
and in my destroy method:
if ($request->id) {
foreach ($request->id as $id) {
User::destroy($id);
}
}
User::destroy($id);
}
So...
// not deletes the user
axios.delete('api/users/destroy', {params: {id: id}})
// ok deletes the users when using request->id in a for loop in the destroy laravel method.
axios.delete('api/users/destroy', {params: {ids: this.checkedNames}})
// ok deletes one user
axios.delete('api/users/' + id)
sorry guys but I have a lot of confusion why and what !!!
The route name is user.destroy why does it work when I pass an array and it does not when I pass a single value, why viceversa the route with method delete will not delete when pass an array ???
Any difference between using api/users/destroy vs api/users only?
Thanks for any help on this!
I also experienced the same problem. This works for me:
deletePost: function(id) {
axios.post('/posts/'+id,{_method: 'delete'})
}
Using axios.post() instead of axios.delete, and sending _method "delete"
It is because of the method signatures. The default delete route when using Resource expects a single parameter. So when doing:
axios.delete('api/users', {params: {'id': this.checkedNames})
you are missing a required parameter. The route definition is
Route::delete('api/users/{id}', 'UserController#destroy');
// You are missing `id` here. So it won't work.
Usually, if you are going to stray away from the default behavior, it is recommended to create your own function. So you could leave the default destroy($id) function as is to delete a single entry and write a new function that will delete many. Start by adding a route for it
Route::delete('api/users', 'UserController#deleteMany');
Then define the function to handle it
public function deleteMany(Request $request)
{
try
{
User::whereIn('id', $request->id)->delete(); // $request->id MUST be an array
return response()->json('users deleted');
}
catch (Exception $e) {
return response()->json($e->getMessage(), 500);
}
}
To summarise, your problem came from route definition. Your route from Axios did not match the route definition from Laravel, hence the 405.
I was having issue to send data as model while making delete request. I found a fix as follows:
deleteCall (itemId, jsonModel) {
return api.delete(`/users/${itemId}/accounts/`, {data: jsonModel})
},
Deleting users in array
Other good option, is to convert javascript array to string, and pass it has the required parameter, instead of passing object. Here the example:
In Vue.js 2.5.17+
//Use the javascript method JSON.stringify to convert the array into string:
axios.delete('api/users/' + JSON.stringify(this.checkedNames))
In Laravel 5.3+
//Resource default route (you don't need to create, it already exists)
Route::delete('api/users/{id}', 'UserController#destroy');
//In laravel delete method, convert the parameter to original array format
public function destroy($id)
{
User::destroy(json_decode($id); //converting and deleting users in array 'id'
}
Deleting single user by id
Just pass the id. You don't need to convert it.
In Vue.js 2.5.17+
axios.delete('api/users/' + id)
In Laravel 5.3+
You can name the parameter as you wish: user, id, item ,...
In Laravel 5.6+ < is named as $id //this can be the id or the user object
In Laravel 5.7+ > is named as $user //this can be the id or the user object
public function destroy($id)
{
User::destroy($id);
}
axios.post('/myentity/839', {
_method: 'DELETE'
})
.then( response => {
//handle success
})
.catch( error => {
//handle failure
})
https://www.mikehealy.com.au/deleting-with-axios-and-laravel/

Laravel Model Get Method Decoding JSON

Hi there below is what I'm storing in my db but when I use my get method in my model I have to use json_decode twice when formating my data why is this happening and can I have it just use it once somehow.
json exactly in db:
"[{\"id\":\"1\",\"country\":\"New Zealand\",\"shipping_rate\":\"1\"},{\"id\":\"2\",\"country\":\"Australia\",\"shipping_rate\":\"2\"}]"
Model Get Method:
public function getshippingAttribute()
{
return $this->attributes['shipping'] ? json_decode(json_decode($this->attributes['shipping'])) : [];
}
The problem is not clear enough from your question but the Laravel offers a builtin mechanism for attribute casting (Since v-5.1). In this case, in your model, just declare a $casts property for example:
protected $casts = [
'shipping' => 'array',
// more ...
];
Because of the $casts property given above, whenever you'll write (create/update) a model, you don't need to explicitly use json_encode to convert the array to json string, Laravel will do it for you and also, when you'll retrieve the model (single/collection), the shipping attribute will be automatically converted back to an array so you don't need to use json_decode for working with the attribute.
Regarding the response, that will be also handled by laravel if you don't convert it to json manually (when returning a model/collection). This will possibly solve your problem.
public function getshippingAttribute()
{
return $this->attributes['shipping'] ? json_decode($this->attributes['shipping']) : [];
}
Try return json response
public function getshippingAttribute()
{
return response()->json($this->attributes['shipping'])
}

simple json response with cakephp

I trying to pass some json to a controller in cakePHP 2.5 and returning it again just to make sure it is all going through fine.
However I getting no response content back. Just a 200 success. From reading the docs I am under the impression that if I pass some json then the responseHandler will the return json as the response.
Not sure what I am missing.
Data being passed
var neworderSer = $(this).sortable("serialize");
which gives
item[]=4&item[]=3&item[]=6&item[]=5&item[]=7
appController.php
public $components = array(
'DebugKit.Toolbar',
'Search.Prg',
'Session',
'Auth',
'Session',
'RequestHandler'
);
index.ctp
$.ajax({
url: "/btstadmin/pages/reorder",
type: "post",
dataType:"json",
data: neworderSer,
success: function(feedback) {
notify('Reordered pages');
},
error: function(e) {
notify('Reordered pages failed', {
status: 'error'
});
}
});
PagesController.php
public function reorder() {
$this->request->onlyAllow('ajax');
$data = $this->request->data;
$this->autoRender = false;
$this->set('_serialize', 'data');
}
UPDATE:
I have now added the following to the routes.php
Router::parseExtensions('json', 'xml');
and I have updated my controller to
$data = $this->request->data;
$this->set("status", "OK");
$this->set("message", "You are good");
$this->set("content", $data);
$this->set("_serialize", array("status", "message", "content"));
All now works perfectly.
A proper Accept header or an extension should to be supplied
In order for the request handler to be able to pick the correct view, you need to either send the appropriate Accept header (application/json), or supply an extension, in your case .json. And in order for extensions to be recognized at all, extension parsing needs to be enabled.
See http://book.cakephp.org/...views.html#enabling-data-views-in-your-application
The view only serializes view vars
The JSON view only auto-serializes view variables, and from the code you are showing it doesn't look like you'd ever set a view variable named data.
See http://book.cakephp.org/...views.html#using-data-views-with-the-serialize-key
The view needs to be rendered
You shouldn't disable auto rendering unless you have a good reason, and in your case also finally invoke Controller:render() manually. Currently your action will not even try to render anything at all.
CakeRequest::onlyAllow() is for HTTP methods
CakeRequest::onlyAllow() (which btw is deprecated as of CakePHP 2.5) is for specifying the allowed HTTP methods, ie GET, POST, PUT, etc. While using any of the available detectors like for example ajax will work, you probably shouldn't rely on it.
Long story short
Your reorder() method should look more like this:
public function reorder() {
if(!$this->request->is('ajax')) {
throw new BadRequestException();
}
$this->set('data', $this->request->data);
$this->set('_serialize', 'data');
}
And finally, in case you don't want/can't use the Accept header, you need to append the .json extension to the URL of the AJAX request:
url: "/btstadmin/pages/reorder.json"
and consequently enable extension parsing in your routes.php like:
Router::parseExtensions('json');
ps
See Cakephp REST API remove the necessity of .format for ways to use the JSON view without using extensions.
Output your json data
public function reorder() {
$this->request->onlyAllow('ajax');
$data = $this->request->data;
$this->autoRender = false;
$this->set('_serialize', 'data');
echo json_encode($data);
}

Resources