Indirect Modification of Overloaded Property Laravel MongoDB - php

I am using MongoDB with Laravel. I have a collection called categories which has one document
[
{
"_id": "567dc4b4279871d0068b4568",
"name": "Fashion",
"images": "http://example.com/1.jpg",
"specifics": [
"made"
],
"brands": [
{
"name": "Giordano",
"logo": "http://example.com/"
},
{
"name": "Armani",
"logo": "http://example.com/"
}
],
"updated_at": "2015-12-25 22:40:44",
"created_at": "2015-12-25 22:35:32"
}
]
I am trying to make a function that add specifics to the specifics array in the above document.
Here is how my request body is
HTTP: POST
{
"specifics": [
"material"
]
}
And i am handling this request with the following function
/**
* Add specs to category
* #param string $category_id
* #return Illuminate\Http\JsonResponse
*/
public function addSpecifics($category_id)
{
$category = $this->category->findOrFail($category_id);
$category->specifics[] = $this->request->get('specifics');
$status_code = config('http.UPDATED');
return response()->json($category->save(), $status_code);
}
But when i hit this call, I get error of
ErrorException in CategoryController.php line 101: Indirect
modification of overloaded property App\Category::$specifics has no
effect
Please help me in fixing this.
I am using https://github.com/jenssegers/laravel-mongodb this package for MongoDB.

Due to how accessing model attributes is implemented in Eloquent, when you access $category->specifics, a magic __get() method is called that returns a copy of that attribute's value. Therefore, when you add an element to that copy, you're just changing the copy, not the original attribute's value. That's why you're getting an error saying that whatever you're doing, it won't have any effect.
If you want to add a new element to $category->specifics array, you need to make sure that the magic __set() is used by accessing the attribute in a setter manner, e.g.:
$category->specifics = array_merge($category->specifics, $this->request->get('specifics'));

You can use this method in case that you want to add a single item to the array:
PHP:
$new_id = "1234567";
$object->array_ids = array_merge($object->array_ids, [$new_id]);
This work's for me!

Store your variable in a temp variable:
// Fatal error
$foo = array_shift($this->someProperty);
// Works fine
$tmpArray = $this->someProperty;
$foo = array_shift($tmpArray);

I had the same problem with array inside of my class which extends Model.
This code $this->my_array[$pname]=$v; did not work until I declare protected $my_array = [];

Another simple alternative:
function array_push_overloaded($source, $element) {
$source[] = $element;
return $source;
}
Example:
$category->specifics = array_push_overloaded($category->specifics, $this->request->get('specifics'));

Don't try to directly modify an Eloquent collection, instead cast the collection to an Array and use that array for your modifications.
$model = Model::findOrFail($id);
$array = $model->toArray();
$array['key'] = $value;

Related

unable to store data from $request to mysql using Laravel 5.4

I am passing a POST request with 3 variables
Id (Randomly Generated),
Name (Specified)
Capacity (Specified)
from an Angular Service post. These are in the payload of the POST, and also visible in $request variable in the Laravel resource controller method "store()".
public function store(Request $request)
{
$servers = new Server();
return $request->getContent();
}
Here in Chrome's developer tool>network>POST request>on preview I get this
[{name: "bakar", capacity: 50, id: 8012}]
0: {name: "bakar", capacity: 50, id: 8012}
but when I use
public function store(Request $request)
{
$servers = new Server();
$data = $request->getContent();
$servers->id = $data->id;
$servers->name = $data->name;
$servers->capacity = $data->capacity;
$servers->save();
}
In the above method I got an error exception stating:
" Trying to get property of non-object "
How can I solve this?
Based on your JSON sample, $data should contain an array, which then contains a single object at its first index.
Therefore you cannot do e.g. $data->id because that's assuming that $data has a property called $id...it doesn't, the object at its first index does.
So simply $data[0]->id would allow you to access that index, and then access the $id property of the object at that index. Of course if your request contained multiple items in the array, you might need to use a loop to go through them all and get the values. It depends what you're expecting and what you intended to do with it.
Or, it may be the case that your PHP is correct, and it's your client which is sending the data in the wrong format. It's not clear what the desired outcome actually is.
Edit:
Since it appears that $data is still a JSON string when it arrives in your store() function, you need to decode it. So first write
$data = json_decode($request->getContent());
(instead of $data = $request->getContent();) and then proceed as above.
Complete sample:
public function store(Request $request)
{
$servers = new Server();
$data = json_decode($request->getContent());
$servers->id = $data[0]->id;
$servers->name = $data[0]->name;
$servers->capacity = $data[0]->capacity;
$servers->save();
}

Cannot serialize SimpleXMLElement to Laravel job

So, after doing some studying I have successfully managed to parse some XML that I'm getting via Guzzle via simplexml_load_string. The issue is then when I then subsequently try to dispatch a job for each of the children using the following code I get a "Serialization of 'SimpleXMLElement' is not allowed" error.
$parent = $this->getParent($keywords);
foreach ($parent->children() as $child) {
dispatch(new ProcessChild($event, true), $this->otherVar);
}
So to try and fix this I can use the following trick to convert the XML into an array;
json_decode(json_encode($child))
however, while this does mean I can send the data to the new job, it does mean that, as far as I can work out, I have no way to access the #attributes. An alternative would be something along the lines of the following;
// ParentJob
$parent = $this->getParent($keywords);
foreach ($parent->children() as $child) {
dispatch(new ProcessChild($child->asXML, true), $this->otherVar);
}
// ChildJob
public function __construct($xml, $otherVar)
{
$this->xml = simplexml_load_string($xml);
$this->otherVar = $otherVar;
}
however it still throws a serialization error on the dispatch for some reason that I cannot work out, since it sould only be sending raw XML and not an object.
So my main question is what would be the correct way to pass and child SimpleXMLObject to a job in Laravel 5.3 ?
(short of something like looping through all the nodes/attributes and building my own collection from them)
Converting the XML into JSON that way means loosing data. I suggest keeping the XML if possible.
SimpleXMLElement::asXML() is a method. Do not forget the brackets.
$parent = $this->getParent($keywords);
foreach ($parent->children() as $child) {
dispatch(new ProcessChild($child->asXML(), true), $this->otherVar);
}
Calling it as a property means that SimpleXML tries to interpret it as a child element node. This means it will be an (empty) SimpleXMLElement.
Here is a small example showing the behavior:
$node = new SimpleXMLElement('<foo/>');
var_dump($node->asXml);
var_dump($node->asXml->getName());
var_dump($node->asXml());
Output:
object(SimpleXMLElement)#2 (0) {
}
string(0) ""
string(29) "<?xml version="1.0"?>
<foo/>
"
The SimpleXmlElement can be converted to array as follows:
$xml = <<<'XML'
<root>
<x a="a1">1</x>
<y b="b2">2</y>
<z>3</z>
</root>
XML;
$xe = simplexml_load_string($xml);
$a = $xe->xpath('*');
$a = array_map(function ($e) {
$item = (array) $e;
$item['nodeName'] = $e->getName();
return $item;
}, $a);
// Now `$a` is an array (serializable object)
echo json_encode($a, JSON_PRETTY_PRINT);
Output
[
{
"#attributes": {
"a": "a1"
},
"0": "1",
"nodeName": "x"
},
{
"#attributes": {
"b": "b2"
},
"0": "2",
"nodeName": "y"
},
{
"0": "3",
"nodeName": "z"
}
]
Note, you can get the string value of a SimpleXmlElement by casting it to string:
$item['value'] = (string) $e;
Since xpath method supports relative XPath expressions, the asterisk should work even with namespaced XMLs. Consider using the DOM extension, as it is much more flexible than SimpleXML. In particular, its DOMXPath class allows to register namespaces and use the registered identifiers in the XPath expressions:
$xpath->registerNamespace('myprefix', 'http://example.com/ns');
$xpath->query('/root/myprefix:*');
As it turns out the entire reason it was not working was due to me using simplexml_load_string() on the child jobs constructor, which was turning it into a simpleXMLElement before the job was actually serialized and pushed onto the queue. the correct way to do it was to parse the XML string on the handle method, which is done after the job has been pulled from the queue for actual processing.
Now this works I can simply dispatch the child job with $child->asXML, and parse it when the job is actually being processed, meaning I can still use all the nifty simpleXML features such as attributes().
Example ParentJob:
foreach ($parent->children() as $child) {
dispatch(new ProcessChild($event, true), $this->otherVar);
}
Example ChildJob:
protected $xml;
protected $otherVar;
public function __construct($xml, $otherVar)
{
$this->xml = $xml;
$this->otherVar = $otherVar;
}
public function handle()
{
$child = simplexml_load_string($this->xml);
$attributes = $child->attributes();
}

Set a referenced variable to a newly initialized class

I have a method, which takes a reference
// CarService.php
public function getCars(&$carCollection = null)
{
$promise = // guzzle request for getting all cars would be here
$promise->then(function (ResponseInterface $response) use (&$carCollection) {
$cars= json_decode($response->getBody(), true);
$carCollection= new CarCollection($cars);
});
}
However, when accessing the collection and trying to reuse it, I'm getting the error
Argument 1 passed to {placeholder} must be an instance of {placeholder}, null given
I know that the reason for this is, that the constructor returns nothing, but how can I still assign my variable to a new instance of the CarCollection (which extends Doctrine's ArrayCollection)
I even tried it with a static method as a work around
// CarCollection.php
public static function create(array $cars): CarCollection
{
$carCollection = new CarCollection($cars);
return $carCollection;
}
// CarService.php
public function getCars(&$carCollection = null)
{
$cars = // curl request for getting all cars would be here
$carCollection = CarCollection::create($cars)
}
but it's still null. Why is that? How can I set a referenced variable to a new class?
I access the method like this
$carService = $this->get('tzfrs.vehicle.services.car');
$carCollection = null;
$promises = [
$carService->getCars($carCollection)
];
\GuzzleHttp\Promise\unwrap($promises);
var_dump($carCollection); // null
When I set the reference directly, eg.
// CarService.php
public function getCars(&$carCollection = null)
{
$carCollection = new CarCollection([]);
}
it works without any problems. Seems like the callback is somehow the problem.
Whoever downvoted this, can you please elaborate why and why you voted to close?
I might be misunderstanding the question, but you should be able to modify an object when passing by reference. See here for an example: https://3v4l.org/KtFvZ
In the later example code that you added, you shouldn't pass $carCollection by reference, the & should only be in the method/function defintion, not provided when you call it. I don't think that is your problem though, that should be throwing an error in php7.

Laravel response transformations not working

I have the following
controller function
public function show()
{
$users_id = Request::segment(4);
// $this->user_bank_details_repository->setPresenter(new UserBankAccountPresenter);
$account = $this->user_bank_details_repository->findByField('users_id', $users_id, $columns = ['user_bank_details_id','bank_name','bank_account_number','bank_ifsc_code','beneficiary_name','bank_account_type','bank_branch','created_at']);
if(!$account)
{
return $this->response->noContent();
}
return $this->response->item($account, new UserBankAccountTransformer);
}
Transformer
<?php
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
namespace App\Api\V1\Transformers;
use App\Entities\UserBankDetails;
use League\Fractal\TransformerAbstract;
class UserBankAccountTransformer extends TransformerAbstract {
public function transform(UserBankDetails $bank_details)
{
return [
'id'=> (int) $bank_details->user_bank_details_id,
'bank_name' => $bank_details->bank_name,
'bank_account_number' => (int) $bank_details->bank_account_number,
'bank_account_type' => $bank_details->bank_account_type,
'bank_beneficiary_name' => $bank_details->beneficiary_name,
'bank_branch'=> $bank_details->bank_branch
];
}
}
I am using repository pattern design and using dingo for my REST framework.
I always get the response as
{
"user_bank_details": [
{
"user_bank_details_id": 1,
"bank_name": "jbjb",
"bank_account_number": "939393933939",
"bank_ifsc_code": "ABCD0000047",
"beneficiary_name": "Gaf",
"bank_account_type": "savings",
"bank_branch": "Mad(E)",
"created_at": "2015-12-23 17:05:39"
}
]
}
instead of the transformed json. I dont get any errors as well. Tried using
return $this->response->item($account, new UserBankAccountTransformer::class);
But still the response is not getting transformed. I tried whatever I could but I dont get it worked :(
Even though this question is over a year ago, however for the sake of anyone who would have similar issue, the suspect here is the fact that you used: item instead of collection.
If the $account contains a collection, then it means this answer is correct: that is, this:
return $this->response->item($account, new UserBankAccountTransformer::class);
Should be:
return $this->response->collection($account, new UserBankAccountTransformer::class);
PS: I also Encountered this on Laravel 5.2.* thats why I feel this might help.

When should one pass an object as a parameter vs instantiating?

I am curious about the best practices and any performance or other considerations relating to passing an instance of an object as a parameter to another function in the same class vs creating another instance of that object in the new function. Here's a quick example:
Option 1: Pass both instance of Trainee AND TraineeController to other functions
protected function startTraining($traineeID) {
$traineeController = new TraineeController();
$trainee = $traineeController->findTrainee($traineeID);
$this->initializeTraining($trainee, $traineeController);
$this->doSomeOtherStuffWithTrainee($trainee, $traineeController);
return Redirect::back()->with('trainee', $trainee);
}
protected function initializeTraining($trainee, $traineeController) {
$trainee->blah1 = 'red';
$trainee->blah2 = 'blue';
$propertiesToUpdate = [
'blah1' => $trainee->blah1,
'blah2' => $trainee->blah2
];
$traineeController->updateTrainee($trainee->traineeID, $propertiesToUpdate);
}
Option 2: Pass $trainee ONLY, instantiate a new TaineeController each time
protected function startTraining($traineeID) {
$traineeController = new TraineeController();
$trainee = $traineeController->findTrainee($traineeID);
$this->initializeTraining($trainee);
$this->doSomeOtherStuffWithTrainee($trainee);
return Redirect::back()->with('trainee', $trainee);
}
protected function initializeTraining($trainee) {
$trainee->blah1 = 'red';
$trainee->blah2 = 'blue';
$propertiesToUpdate = [
'blah1' => $trainee->blah1,
'blah2' => $trainee->blah2
];
$traineeController = new TraineeController();
$traineeController->updateTrainee($trainee->traineeID, $propertiesToUpdate);
}
In the above I need to pass $trainee across all functions each time instead of creating a new trainee from $traineeID because some other stuff goes on behind the scenes during the 'training' process that would otherwise be lost before relevant data is saved to the db. However, this is not required for TraineeController - I can either pass it as a parameter or instantiate a new TraineeController as much as I want. Which is the better choice?
I saw this question relating to C#, where the accepted answer was that passing an entire object is usually more efficient and instantiating another one because you are passing by reference. Does this hold true for PHP? Ie is the most efficient approach to pass the entire object by reference to required functions using &?
There is nothing wrong with passing an object as reference, but note that php expects that your function argument needs to expect a reference rather than just passing a variable by reference (php docs). php 5.4.0 will even raise a fatal error if this is not respected:
right:
protected function initializeTraining($trainee, &$traineeController) {}
$this->initializeTraining($trainee, $traineeController);
wrong:
protected function initializeTraining($trainee, $traineeController) {}
$this->initializeTraining($trainee, &$traineeController);
Passing objects by reference will in most cases have better performance than initiating the object again, but passing by reference could become tricky if your object has its own properties:
class TraineeController {
$fooCalled = false;
function foo(){ $this->fooCalled = true; }
function isFooCalled(){ return $this->fooCalled; }
}
$traineeController = new TraineeController();
$traineeController->foo();
//&$traineeController->isFooCalled() will be different from
//new TraineeController()->isFooCalled().

Categories