Model fields not recognized from the Intellisense - laravel

So, in the model I've listed the fillable and hidden fields, and then when I access fields on the object of that model, they get highlighted as Field 'some_field' not found in class .... If I add phpDoc to it as follows:
/** #var Trip $trip */
$trip->driver = ...
the field is being highlighted. If I write
/** #var object $trip */
$trip->driver = ...
it's not highlighted, but that's just not right. Everything works fine, but it just looks bad in the IDE, and the highlighting is annoying.
Then I decided to simply declare variables in the model class, for every field, so that they're recognized, but then the fields always hold NULL, when I access them on the object.
Anyone has a solution for that?

If you use PhpStorm you can write it above your model class as a comment.
/**
* App\User
*
* #property string $username
*/
You can also use a composer package called laravel-ide-helper here you have the link
https://packagist.org/packages/barryvdh/laravel-ide-helper
You can use some simple commands to generate helper files and it will provide you with code completion.

Related

Laravel Model Define Parameters Won't Insert

I am currently moving over from symfony to laravel, it's quite a bit different when it comes to the database. So i have a basic model, i'm just going to use an example:
class Test extends Model
{
use HasFactory;
}
All good, i have a migration and the table created. However, i don't like this:
$test = new Test();
$test->my_field = 'hello';
$test->save();
I don't like it because it's having to use a magic __set() to create the parameter, if i define the parameter in my model like this:
class Test extends Model
{
use HasFactory;
public ?string $my_field;
}
I get database errors when it tries to insert when i define the params like this. Why is that? It's doing the same thing as __set() but i'm actually physically defining them, which in my opinion is a better way to code it as my IDE can typehint and it's just nicer to follow the program knowing what params are there.
What's the reason for it inserting when i don't define them, and not when i do? From my actual table which is bookings , has a field booking_ref:
General error: 1364 Field 'booking_ref' doesn't have a default value (SQL: insert into booking_reviews (updated_at, created_at) values (2021-12-13 14:13:08, 2021-12-13 14:13:08))
This happens when i define the $booking_ref param on the model, but if i take it out and rely on the __set() method it works fine. Doesn't make any sense to me right now.
I think this is a reasonable enough misunderstanding to be useful to future visitors, so I want to try to explain what's going on with some pseudo-code and some references to the current source code.
You are correct that when setting a property on a Laravel model, that is a column in the DB, internally Laravel is using the PHP magic method __set.
What this does is allow you to 1) set properties directly instead of calling some kind of setter function, and 2) interact with your table columns without needing the boilerplate of column definitions in your model.
Where the assumptions go wrong is with what __set is doing. __set does not have to simply set an actual property with the same name. __set is just a method you may implement to do whatever you want. What you assumption implies is that it's doing something like this:
public function __set($key, $value)
{
$this->{$key} = $value;
}
However, you can do whatever you want with the $key and $value passed to the magic method.
What Laravel does is call another method defined in the HasAttributes trait - setAttribute.
public function __set($key, $value)
{
$this->setAttribute($key, $value);
}
setAttribute does a few extra things, but most importantly it adds the key/value pair to Model property $this->attributes[].
To hopefully help this difference make sense, here is what the two __set methods would yield with a basic example:
$model->my_column = 'value';
// 1st example
/**
* {
* public $my_column = 'value';
* }
*/
// Laravel way
/**
* {
* protected $attributes= ['my_column => 'value'];
* }
*/
I won't go through both saving and updating since they're very similar, but to show how this is used, we can look at the save method, which calls performInsert and after a few more calls makes it's way back to the attributes property to determine what to actually insert into the query.
Summary
Laravel does not use custom model properties when deciding what column/values to add to queries.
This is why when you create custom mutators, you interact with the attributes property just like Laravel does internally.
Anytime you introduce "magic" into code, you have some tradeoffs. In this case, that tradeoff is slightly less clarity with what database columns are actually available. However, like I mentioned in comments, there are other solutions to make models more IDE friendly like Laravel IDE helper.

Laravel 5 how to modify default Date accessors?

I have a code like this:
public function getUpdatedAtAttribute($value) {
setlocale(LC_TIME, config('app.locale'));
return Carbon::parse($value)->formatLocalized(__('DateFormat'));
}
I want to run this accessor for each field specified in $dates array instead of manually specifying it for each date field in each model, just like default Carbon instance convertion works. How could I do this? And is there better ways of specifying default locale-dependant date format for Carbon?
I think you can use $dateFormat variable of a model to apply a common date format on all the model fields:
class Flight extends Model
{
/**
* The storage format of the model's date columns.
*
* #var string
*/
protected $dateFormat = 'U';
}
More info: https://laravel.com/docs/5.4/eloquent-mutators#date-mutators
Found an elegant and simple solution: LocalizedCarbon package. It works as simply as this:
use \Laravelrus\LocalizedCarbon\Traits\LocalizedEloquentTrait;
UPD: It seems that this package actually translating only DateDiff's, but anyway I can see how it works and use that logic in my models.
UPD2: I've dig deeper and found out what there is overloaded formatLocalized method, which allows useage of non-standard "%f" parameter, which represents month name in current application locale. So I ended up with one-liner date formatting into my View instead of Model, which is more correct.

Laravel : Polymorphic Relations + Accessor

I have a Gallery table that uses Polymorphic Relations so I can add Images and Videos to my gallery list.
Within the Gallery table I have a galleryable_type column that is populated with either App\Video or App\Image.
Is there a way for me to use an accessor (docs here) to change the value of galleryable_type to either video or image so I can use that column in JS to decide what gallery item type I'm dealing with?
I tried the following:
/**
* Get and convert the makeable type.
*
* #param string $value
* #return string
*/
public function getMakeableTypeAttribute($value)
{
return str_replace('app\\', '', strtolower($value));
}
But i end up with the following error:
FatalErrorException in Model.php line 838:
Class '' not found
I'm assuming that has to do with the accessor is being processed before the the polymorphic relationship but I'm not sure.
I can simply use the following in my controller:
foreach (Gallery::with('galleryable')->get() as &$gallery) {
$gallery->galleryable_type = str_replace('app\\', '', strtolower($gallery->galleryable_type ));
}
But that seems like a dodgy way of doing things. Could a Laravel guru shed some light on the best way to tackle this problem?
Thanks!
Well I've found an interesting way to solve this issue.
In your models (App\Video and App\Image) you have to add:
protected $morphClass = 'video'; // 'image' for image class
then in your register method in service provider class add:
$aliasLoader = \Illuminate\Foundation\AliasLoader::getInstance();
$aliasLoader->alias('video', \App\Video::class);
$aliasLoader->alias('image', \App\Image::class);
This will cause that you will write image, and video in galleryable_type in the database instead of class names.
So now you can easily get to this values with:
echo $model->galleryable_type;

How to create a clone of an entity collection without preserving relationship in doctrine?

I've been trying to figure out how to get this to work for along time but without any luck. Due to a complex logic in an app I'm working on, I need to create an isolated clone of a entity collection without preserving what so ever relation to the database. Whatever changes I do on the cloned collection should not be tracked by Doctrine at all and should be treated as if it doesn't exist at all.
Here's an example code:
/*
* #ORM\Entity()
*/
class Person
{
/**
* #var integer
*
* #ORM\Id
* #ORM\Column(name="person_id", type="integer",nullable=false)
* #ORM\GeneratedValue(strategy="AUTO")
*/
public $id;
/**
* #var ArrayCollection
*
* #ORM\OneToMany(targetEntity="Car", mappedBy="person", cascade={"persist"})
*/
public $cars;
}
/**
* #ORM\Entity()
* #ORM\HasLifecycleCallbacks()
*/
class Car
{
/**
* #var integer
*
* #ORM\Id
* #ORM\Column(name="car_id", type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/*
* #ORM\JoinColumn(name="person_id", referencedColumnName="person_id", nullable=true)
* #ORM\ManyToOne(targetEntity="Person", inversedBy="cars", cascade={"persist"})
*/
private $person;
}
I've already tried the following code in my controller to store the collection into the session but it still somehow stores the relationships:
$tmp = clone $person;
$this->get('session')->set('carCollection', $tmp->getCars());
$tmpCars = clone $person->getCars();
$tmpCollection = new ArrayCollection();
foreach($tmpCars as $car) {
$tmpCollection->add(clone $car);
}
$this->get('session')->set('carCollection', $tmpCollection);
$tmpCars = clone $person->getCars();
$tmpCollection = new ArrayCollection();
foreach($tmpCars as $car) {
$clone = clone $car;
$entityManager->detach($car);
$tmpCollection->add(clone $clone);
}
$this->get('session')->set('carCollection', $tmpCollection);
Apparently I'm doing something wrong here because I end up having more results in the Car collection when flushing the entity even though the collection itself has the correct number of records. I have a suspicion that somewhere in the chain Doctrine doesn't compute correctly what needs to be done.
Any ideas or directions on how to solve or debug this?
Follow-up question: When retrieving back the cloned collection from the session will it still be an isolated clone or Doctrine will try merge it back?
I'm writing this answer to give directions to anybody who might have similar issues. I couldn't find many topics or documentation in this manner which is why I decided to share my experience. I am no deep expert on Doctrine an how it internally works, so I won't go into big details of "how it works". I will rather focus on the end result.
Storing entities which have relations to other entities into a session is quite problematic. When you retrieve it from the session, Doctrine loses track of the relationships (OneToMany, ManyToOne, etc). This leads to some undesired effects:
Doctrine wrongly decides to insert a new record of an existing entity.
Doctrine might throw exceptions such as A new entity was found through the relationship 'Acme\MyBundle\Entity\Person#cars' that was not configured to cascade persist operations for entity: Opel. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configure cascade persist this association in the mapping for example #ManyToOne(..,cascade={"persist"}). and at least 2 other types of exceptions which might seem totally irrelevant at first.
Apparently when fetching a result from the database and it "as-is" in your session things get really messy, specially if the entity has relations to other entities (which was my case). Pay big attention if you have entity relationships - they might need to be "refreshed" if you start getting strange exceptions.
There are a couple of ways to overcome this issue. One of which is to use the data sent via the form (as #flec suggested) by using $myForm->getData(). This approach might work well for you, but unfortunately it was not the case with me (too complex to explain).
What I ended up doing was implementing the \Serializable in the entity. I also created a method called __toArray() which converted my entity into an array. What data you return in the __toArray() method is totally up to you and your business logic. The array data is stored into the session and you use it to re-create a fresh object with all necessary relations.
Hope this helps somebody.
I think hydrators/extractors would be the way to go for you.
They can extract the data from an entity and you can pass them to a newly created instance of that entity via the hydrator.
The only thing you'll need to do in between is the unsetting of the relation properties.
They should be fetchable via a metadata class via doctrine somehow.

How to get position value from a model (catalog/product_image) in Magento?

I only have this Magento model (catalog/product_image). Is there any way for me to get position value or label value of that image. When I var dump this model, I can only get width, height, quality, _keepAspectRatio, etc. The most valuable data I can get from it is image file name like this: /s/e/productABC.png
Im not quite sure if you will get anything more useful from the catalog/product_image model. From your question it sounds like you are wanting to fetch the image label. If you have access to a product object you can use:
$_product->getMediaGalleryImages()->getItemByColumnValue('label', 'LABEL_NAME')->getUrl();
This will return you the label of the image.
If you need any more help please share what objects you have access to and what you are trying to achieve.
in that class there is just one property and one set and one get function for file
Property : protected $_baseFile;
Function
/**
* Set filenames for base file and new file
*
* #param string $file
* #return Mage_Catalog_Model_Product_Image
*/
public function setBaseFile($file)
and
public function getBaseFile()
{
return $this->_baseFile;
}
This implies: You will not get position using this object.
Just check getBaseFile that will return a string.
I always suggest that for any given object if extended from varien_object use $object->debug(); as this provides lot of information on what all it stores.

Resources