I have a old project on Illuminate Database 4.2 and now i trying to upgrade it to 6.0, but i found some weird stuff:
i have relation like:
public function addressTV()
{
return $this->hasMany('Billing\db\Models\AddrMapTv', 'StreetID');
}
in 4.2 when i use
$list = \Billing\db\Models\Street::with(['addressTV'])->get()->toArray();
it transforming to:
$list['address_tv']
Its ok.
But in 6.0 with same code i got:
$list['address_t_v'];
This creates a lot of problems for me. Is there any way to return the old name conversion?
It might get a bit difficult if you have many relations defined this way, however, the easiest way I see here is to define accessor
Your issue here is that address_tv key is absent, defining an accessor will add that key.
So in your Street model:
/**
* Get the street's address_tv.
*
* #return object
*/
public function getAddressTvAttribute()
{
return $this->addressTV();
}
did not test it but it should work in theory.
Related
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.
I have an app and I want to allow modules in it. I find nWidart /Laravel-modules is the best solution for this. I have used it in the past, but in my previous projects I was the sole developer, so when I created a model inside a module, to create the relationships between it and one of my base models, I just went and edit both files:
In App\Models\Disease I would add a new method:
public function symptoms(){
return $this->hasMany( Modules\Treatments\Entities\Symptom::class );
}
In Modules\Treatments\Entities\Symptom, the opposite:
public function disease(){
return $this->belongsTo( App\Models\Symptom::class );
}
Now, I would like to create those relationships, but without writing code in the App\Models files (of course I know there would be some modification required to make it work, I just mean without having to edit the files every time a module is created). Is there a way to do it? Is it possible to work that out?
Ok, in the module Entities I created a new model Modules\Treatments\Entities\Disease which extends from App\Models\Disease and I inserted the relationship method there.
use App\Models\Disease as BaseDisease;
class Disease extends BaseDisease{
/**
* Relationship between a Disease and it's symptoms
*
* #return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function symptoms(){
return $this->hasMany( Modules\Treatments\Entities\Symptom::class );
}
}
The down side of this approach is that the relationship is only present when I load the Disease from Modules\Treatments\Entities\Disease but I couldn't find another way to do it
This is probably pretty simple, but I can't find a way to do this.
Is there any way to get a list of class names of the entities that Doctrine manages? Something like:
$entities = $doctrine->em->getEntities();
where $entities is an array with something like array('User', 'Address', 'PhoneNumber') etc...
I know this question is old, but in case someone still needs to do it (tested in Doctrine 2.4.0):
$classes = array();
$metas = $entityManager->getMetadataFactory()->getAllMetadata();
foreach ($metas as $meta) {
$classes[] = $meta->getName();
}
var_dump($classes);
Source
Another way to get the class names of all entities (with namespace) is:
$entitiesClassNames = $entityManager->getConfiguration()->getMetadataDriverImpl()->getAllClassNames();
Unfortunately not, your classes should be organized in the file structure though. Example: a project i'm working on now has all its doctrine classes in an init/classes folder.
There is no built function. But you can use marker/tagger interface to tag entity classes that belong to your application. You can then use the functions "get_declared_classes" and "is_subclass_of" find the list of entity classes.
For ex:
/**
* Provides a marker interface to identify entity classes related to the application
*/
interface MyApplicationEntity {}
/**
* #Entity
*/
class User implements MyApplicationEntity {
// Your entity class definition goes here.
}
/**
* Finds the list of entity classes. Please note that only entity classes
* that are currently loaded will be detected by this method.
* For ex: require_once('User.php'); or use User; must have been called somewhere
* within the current execution.
* #return array of entity classes.
*/
function getApplicationEntities() {
$classes = array();
foreach(get_declared_classes() as $class) {
if (is_subclass_of($class, "MyApplicationEntity")) {
$classes[] = $class;
}
}
return $classes;
}
Please note that my code sample above does not use namespaces for the sake simplicity. You will have to adjust it accordingly in your application.
That said you did't explain why you need to find the list of entity classes. Perhaps, there is a better solution for what your are trying to solve.
I noticed that I write the database table names quite a lot, and in different files, when I use the Query Builder. If I were to change the database table names, I would have to search and change quite many rows in my project.
Is this an issue your Laravel guys noticed and come up with an solution to?
I like the Eloquent approach which uses class models, instead of database names; but for some queries I think the Query Builder is a better solution (though I am no expert in this matter).
If you already have a queryBuilder object you can obtain the table name like
$tableName = $query->getModel()->getTable();
Use this in your query :
(new YourModel())->getTable()
Example :
DB:raw('SELECT * FROM '.(new User())->getTable().' WHERE id=3');
How about using OOP concept. Laravel is a framework, so no one stops you from using basic PHP OOP concept. This is what I do:
Consider my query is like :
$result=DB::table('myTable')->select()->get();
What I do is make a class that holds all the tablenames :
class TableName
{
private $tableName= "myTable";
public function getTableName()
{
return $this->tableName;
}
public function setTableName($table_name)
{
$this->tableName = $table_name;
}
}
Now all i have to do is call a method using an object in the file I want to use the table like :
$name = new TableName() ;
$result=DB::table($name->getTableName())->select()->get();
Use wherever you want. I don't think its the best solution however it works for me. Hope it helps
Maybe you can extend the model class.
CModel extend Model {
protected static $tableName;
public static getTableName(){
if(static::$tableName)
return static::$tableName;
/* if you create a "reference break" you don't have to *
/* create "protected static $tableName" row in your all model */
$table = (new static())->getTable();
return static::$tableName = &$table;
}
}
YourModel extends CModel {...}
than you can use
YourModel::getTableName()
I'm not have better idea.
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;