When creating new models, I am trying to use the optional() helper to assign values when they are set. However, when the user object isn't set in this context, I receive this error: "Undefined property: stdClass::$user"
$this->events()->create([
'category_a' => optional($event)->categoryA,
'category_b' => optional($event)->categoryB,
'user' => optional($event->user)->accountId,
]);
To clarify, the $event is always set, however it can contain different values. Sometimes category_a is set, sometimes category_b is set. The optional helper seems to do the trick here. But, when the object is deeper than one level, it throws an error.
So how do I work with the optional helper correctly using deeper than one level objects?
The optional() helper normally is used to avoid errors generated by accesing propeties or methods on null objects, for example:
Fatal error: Call to a member function foo() on null
As the documentation specifies:
The optional function accepts any argument and allows you to access properties or call methods on that object.If the given object is null, properties and methods will return null instead of causing an error.
A practical example
In your code you are accesing a relation called address from your user model:
return $user->address->street;
If by any chance your $user->address is null and you try to check the street, this will return a fatal error, here is where optional() comes into play, by using the helper you can explicitly say $user->address might be null, so don't show me an error if that's the case:
return optional($user->address)->street;
By doing this you will get null instead of the fatal error.
In my opinion this makes the code more readable, a equivalent could be $user->address ? $user->address->street : null, but as you can see is by far more verbose.
In your case, as the comments said an option is to nest optional() helpers, but this is not maintanable if you have multiple levels. I'd recommend you to make a function that internally will chain the helpers so your syntax would look like n_optional($event, ['user'])->accountId.
Hope this helps you.
Also, bear in mind that in PHP 8+ you can use the null safe operator to have the same behavior with cleaner look:
// PHP 8+
$this->events()->create([
'category_a' => $event?->categoryA,
'category_b' => $event?->categoryB,
'user' => $event->user?->accountId,
]);
It is also chainable and can be used with methods:
// before PHP 8
$country = null;
if ($session !== null) {
$user = $session->user;
if ($user !== null) {
$address = $user->getAddress();
if ($address !== null) {
$country = $address->country;
}
}
}
// PHP 8+
$country = $session?->user?->getAddress()?->country;
Note by #pedro-x: Pay attention that optional() works for null|false values, while ?-> only works with null values. They are not the same! Pay attention to the details to avoid breaking your code.
Related
I am learning Laravel and have run into a problem handling missing arguments, which has also been explained here. However, the solution(s) focus primarily on named routes, but am using RESTful controllers.
TL;DR Is there some elegant solution to handle missing arguments on all of a given controller's methods without naming each one?
Route
Route::controller('accounts', 'AccountsController');
Controller
Here is a sample controller method...where the "example.com/accounts/profile/1" works, but "example.com/accounts/profile/" [with or without the trailing slash] will throw an exception. "ErrorException: Missing argument 1 for AccountsController::getProfile()"
public function getProfile($id)
{
$account = DB::table('accounts')->where('id', $id)->first();
$this->layout->content = View::make('account.profile', array('account' => $account));
}
One Attempted Solution (Doesn't work)
I've also tried this ↓ , which was a promising-looking suggestion from another question. However, it also does not work.
public function getProfile($id = NULL)
{
if ($id = NULL)
{
return Redirect::to('accounts');
} else {
$account = DB::table('accounts')->where('id', $id)->first();
$this->layout->content = View::make('account.profile', array('account' => $account));
}
}
Other Solution (Works, but is tedious)
I know that using named routes, such as the one below , to cover all of the holes will work, but this seems like such an un-elegant solution! (especially considering the suuuuper tedious process of naming all of the routes for all of the controllers that we are planning on using)
Route::get('/accounts/profile', 'AccountsController#missingMethod');
So....
What do you all think? Is there some elegant solution to handle missing arguments on all of a given controller's methods without naming each one?
The default parameter option should work aside from a minor and very commonly uncaught error in php.. You're using the assignment operator in your conditional. Use
if($id == NULL)
instead of
if($id = NULL)
or if you like
if(is_null($id) )
One of the tragedies of a dynamically typed language. Gets me more frequently than I care to admit.
This is probably very easy to do, but I can't seem to get my head around it right now. Let's say in a component in a cakephp application, I have a variable my_model, which contains the model of the corresponding controller that is currently using the component like:
function TestComponent extend Object
{
var $my_model; // can be either User, or Person
function test()
{
$myModelTemp = $this->my_model;
$model = $myModelTemp != 'User' ? $myModelTemp.'->User' : 'User';
$this->$model->find('all');
}
}
As you can see above in my function test() what I'm trying to do is call the correct model based on the value of my_model. So based on the condition, my query will be either:
$this->Person->User->find('all');
Or
$this->User->find('all');
When I do it like I did above, I get an error saying Fatal error: Call to a member function find() on a non-object. In order words, that error means Person->User is not an object (so, it is considered as a string).
What you're saying could be true, however, it can refer to any part of the call.
So either Person or User could be invalid, or together they causes the error. Hard to say.
Try dumping the individual objects using var_dump();
So try:
<?php
echo "<pre>";
var_dump(is_object($this->Person));
var_dump(is_object($this->User));
echo "</pre>";
?>
to determine where you're code goes wrong.
To be clear, that return value needs to be true for it to be an object.
The one that returns false is the likely culprit.
Should your question refer to the correct way to reference an object, an object is basically an array. For example:
<?php
$obj = (object) array("this", "my_function");
?>
The above example casts the array as an object. However, using multiple layers might prove to be more difficult than you'd expect.
Generally, it looks like you might be going about this all wrong. Obviously you want the models to be dynamic, but then you're hard-coding things which defeats the whole point of it being dynamic in the first place.
It also seems like you might be violating the principals of CakePHP and MVC by doing all this in a component. I'm not sure this component should really be manipulating models or assuming which models are currently in use.
However, if you want to evaluate a string as an actual object, you can wrap it in { ... } (this is valid standard PHP syntax, not Cake-specific code).
Try this:
$modelName = $this->my_model;
$model = ($modelName != 'User') ? $this->{$modelName}->User : $this->User;
$model->find('all');
Now, if this doesn't work or you get an error saying it can't find the model(s) you need to ensure the models are actually loaded and initialised in the current scope.
My question is a follow up to the following question: What does it mean to start a php function with an ampersand?
The example code used in the question is this:
class FacebookRestClient {
...
public function &users_hasAppPermission($ext_perm, $uid=null) {
return $this->call_method('facebook.users.hasAppPermission',
array('ext_perm' => $ext_perm, 'uid' => $uid));
}
...
}
Why would a reference be necessary when we already have a reference ($this)?
The chosen answer quotes the following from the PHP manual on Returning References
Returning by reference is useful when you want to use a function to find to which variable a reference should be bound. Do not use return-by-reference to increase performance. The engine will automatically optimize this on its own. Only return references when you have a valid technical reason to do so.
The second answer gives a reason why this technique was needed in PHP4. But I don't find the answer for why it is needed in PHP5 very convincing.
Does anybody know of any valid reason(s) for using this technique in PHP5?
Yeah, here's an example from my codebase.
I have a "factory" class for each model, such as UserFactory. When I call UserFactory::findOne() I return a reference to the model, which is stored in an array in UserFactory.
I do this so that if I get the user model and modify something in it, and then get it again later in my code, it is updated with the new information even though I never went back to the database.
For example:
<?php
$user = UserModel::findOne([ '_id' => 151 ]);
$user->status = 'disabled';
// Much later
$user = UserModel::findOne([ '_id' => 151 ]);
if ( $user->status != 'disabled' ) {
// Do stuff
}
Returning by reference is a good way of accomplishing this without making two calls to my database.
$winnerBid = Bids::model()->find($criteria);
Model has next relations:
public function relations() {
return array(
'item' => array(self::BELONGS_TO, 'Goods', 'item_id'),
'room' => array(self::BELONGS_TO, 'Rooms', 'room_id'),
'seller' => array(self::BELONGS_TO, 'RoomPlayers', 'seller_id'),
'buyer' => array(self::BELONGS_TO, 'RoomPlayers', 'buyer_id'),
);
}
When I am trying to save:
$this->seller->current_item++;
$this->seller->wins++;
$this->seller->save();
I am getting error:
Indirect modification of overloaded
property Bids::$seller has no effect
(/var/www/auction/www/protected/models/Bids.php:16)
But it was everything fine at another server?
How to fix it? Or override php directives? Any ideas? TNX
The problem here is that $seller is not a "real" property (Yii implements properties on its Models by using the magic __get method), so in effect you are trying to modify the return value of a function (which has no effect). It is as if you tried to do:
function foo() {
return 42;
}
// INVALID CODE FOR ILLUSTRATION
(foo())++;
I 'm not sure about the status of this behavior on different PHP versions, but there is an easy workaround you can use:
$seller = $this->seller;
$seller->current_item++;
$seller->wins++;
$seller->save();
I was also having the error message "Yii Indirect modification of overloaded property" when trying to massively manipulate attributes using the CActiveRecord attributes property.
Then, I discovered another method to overcome this issue, in a case where the magic method is related to an object variable which contains an array take a look: you create an AUXILIARY ARRAY in which you put the original and the new values (sometimes one wants to REPLACE a value related to one of the keys, and these methods are not satisfactory). And AFTERWARDS use an assignation, which works like the reference. For example:
$auxiliary_array = array();
foreach(Object->array_built_with_magic as $key=>$value) {
if(….) {
$auxiliary_array[$key] = Object->array_built_with_magic[$key];
} else if (…) {
$auxiliary_array[$key] = $NEW_VALUE
}
}
//So now we have the array $auxiliary_array with the
// desired MIX (that is, some originals, some modifications)
//So we will do now:
Object->array_built_with_magic =$auxiliary_array;
I had this error on yii when upgrade to php8.1,
it was in createCommand() method.
and it didn't complain in former version of php, that we access a property on model which hasn't been initialized.
the workaround was to change the bindParam() method to the bindValue().
because the former wanted to use corresponding database field which has not been initialized yet.
but the later (bindParam) just insert the value in the sql statement.
I have a object built through a factory containing my parameters read from the url.
From this object, I can get the language parameter
$language =
$my_parameters->getLanguage();
$language is NULL if it isn't been set.
$language may also be invalid ( $language->isValid() returns false ).
So, to build my page, I need some parameters.
The page is also built trough a factory. Then I know which parameters I need to build it. If it miss parameters, I build them with a valid default value according to the page asked.
At this point, into the page factory, if a have an invalid parameter, I throw an exception.
My page object contains a body object that needs a language parameter. I know that my parameters are valid when I build my body object.
Into my body object, I retrieve the language
$language =
$my_parameters->getLanguage();
At this point, $language ** MUST ** be valid.
So I verify again
$language = $my_parameters->getLanguage();
if( is_null( $language ) or !$language->isValid() ) {
throw new Exception( 'Language must be valid.' );
}
If I need 4 parameters, I have 4 ifs that verify if the object is not NULL and not invalid.
I do it because the method is public where $language is used in the body object .
And the body object may be built outside the factory. Who knows...
Is it correct to verify in that case ?
What are the best-practices about that ?
Here is the case for not checking for null in a recent blog post from the Google Testing blog.
The argument is that it gets in the way of writing clear, easy unit tests, because you can't actually fake the pieces that don't matter, because your exceptions/assertions will be thrown.
The author (Miško Hevery) does qualify the commentary by saying that if it's an external API, it might still be worth checking for an error condition.
i know very little about your domain, but in the general case, i like to assert(not null) all over the place, because typically if i end up with a null object somewhere, its a bug.
it's also good practice to prefer reference types which typically can't even be null.
I come from a very old school of C programming; so my thing is that variables that are not being used or that have been free()'d should always be NULL. That's just my opinion though.
Edit: Building on that, you should always check to see if a variable is NULL before using it as well. If the variable is NULL and shouldn't be then you should log an error. Crashing should not be a feature.
You can make life simpler for yourself by splitting the getLanguage() into two methods:
function getLanguageIfValid() {
// this method return a Language object, but only if it can be created
// correctly and the isValid() method returns TRUE. If the Language object
// can't be created correctly, then it will return null instead.
}
function getLanguageMustBeValid() {
// this method will always return an instance of Language, or else
// an exception will be thrown
if($return = $this->getLanguageIfValid())
return $return;
throw new Exception("Couldn't get Language object");
}
Once you've done that, in places where it is reasonable to say that the language item might not be created properly, you use the first method:
// we may or may not be able to get our Language object here
if($language = $my_parameters->getLanguageIfValid())
do_stuff($language);
If you're certain the language object should be created, then use the 2nd method which will throw the exception for you.
// we know the Language object is created at this point
$language = $my_parameters->getLanguageMustBeValid();
do_stuff($language);
So the answer to your question is No - you don't have to verify an object is not null as long as you can get it from a function that us guaranteed not to return null.
Throw your exception from ->getLanguage().
To me, exceptions should be thrown automatically. What you are doing seems like a mixture of error-code checkup and exception throwing.