I am working on a Laravel 5.5 project that contains multiple "applications": Articles, Notes, Photos, etc.
Each application should have its own directory/namespace, containing its models:
app/
Blog/
Article.php
Category.php
Notes/
Note.php
Category.php
...
When I run this command php artisan make:model --migration Blog/Category, it successfully creates a app/Blog/Category.php model and App\Blog namespace, but the associated migration creates a table named category, instead of blog_category. Which is problematic since I also need to create app/Notes/Category.php.
Is there a trick to prefix category tables ? Would Laravel resolves those tables if I manually change their names, or must I add a $table attribute in each model?
Isn't it surprising that Model namespace and table name are not related by prefix, following Laravel logic?
There is two choice to solve it:
Make the model with BlogArticle which will create blog_articles table
OR add $table="your table name" to every model
this trait can help you
use Illuminate\Support\Str;
trait TableNameResolver
{
public $base_namespace=__NAMESPACE__;
public function getTable()
{
if (! isset($this->table)) {
$this->setTable(str_replace(
'\\', '', Str::snake(Str::plural(trim(str_after(get_class($this),trim($this->base_namespace,'\\')),'\\')))
));
}
return $this->table;
}
}
Following the answer for the_hasanov:
I have done some changes and works great.
namespace App\Traits;
use Illuminate\Support\Str;
trait ModelTrait
{
/**
* Scopped Variables
*/
protected $table_prefix = "prefix_";
/**
* Appends prefix to table name
*
* #return $table
*/
public function getTable() {
$model = explode("\\", get_class($this));
$model = Str::lower(array_pop($model));
if (!isset($this->table)) $this->setTable(Str::plural($this->table_prefix . $model));
return $this->table;
}
}
Add a trait like this and in model use it like this:
class Model {
use ModelTrait;
}
Related
I want to change the default database naming conventions in my Laravel app. By default, Laravel uses snake case for database table and column names. But I want to use Pascal Case for table names and i want to use camel Case for fields.
So a table name of Users instead of users, and field names createdAt, updatedAt, and deletedAt instead of created_at, updated_at, and deleted_at.
I know I can change these on a per-model basis using the $table property but I'd like to change the default without having to modify each model.
Are there any settings like Symfony's NamingStrategy in Laravel?
If you look at the code for Illuminate\Database\Eloquent\Model::getTable() it's pretty straightforward:
public function getTable()
{
return $this->table ?? Str::snake(Str::pluralStudly(class_basename($this)));
}
Same for Illuminate\Database\Eloquent\Concerns\HasTimestamps::getCreatedAtColumn():
public function getCreatedAtColumn()
{
return static::CREATED_AT;
}
So create your own class that extends Model and override that behaviour:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model as BaseModel;
use Illuminate\Support\Str;
class Model extends BaseModel
{
const CREATED_AT = 'createdAt';
const UPDATED_AT = 'updatedAt';
const DELETED_AT = 'deletedAt';
public function getTable()
{
return $this->table ?? Str::pluralStudly(class_basename($this));
}
}
Now, just have your models extend this class.
You can use stubs for that. Also if you named your table in Pascal Case just use $table property in model which need to be sync with that table.
You can create a new model that you extend your models with.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
class ModelWithPascalCase extends Model
{
const DELETED_AT = 'deletedAt';
const CREATED_AT = 'createdAt';
const UPDATED_AT = 'updatedAt';
public function getTable()
{
return $this->table ?? Str::pluralStudly(class_basename($this));
}
}
If you want to make Laravel generate your models extending this, you can do it by editing stubs.
Run
artisan stub:publish
then edit stubs/model.stub by replacing Model with your ModelWithPascalCase.
After that, when you run
artisan make:model User
you get your User model extended by ModelWithPascalCase.
I have a model called Tree that is supposed to be associated to 1..n Things. Things can be associated to 0..n things. In other words this is a many-to-many relationship, and a Thing must be chosen when a Tree is being created. My thing_tree migration looks like this (there's also a thing_thing pivot table but that's irrelevant):
public function up()
{
Schema::create('thing_tree', function (Blueprint $table) {
$table->id();
$table->timestamps();
$table->unsignedBigInteger('tree_id')->nullable();
$table->unsignedBigInteger('thing_id')->nullable();
$table->unique(['tree_id', 'thing_id']);
$table->foreign('tree_id')->references('id')->on('trees')->onDelete('cascade');
$table->foreign('thing_id')->references('id')->on('things')->onDelete('cascade');
});
}
My Tree model looks like this:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Tree extends Model
{
use HasFactory;
protected $guarded = [];
public function path(){
$path = '/trees/' . $this->id;
return $path;
}
public function associatedThings () {
return $this->belongsToMany(Thing::class);
}
}
The Thing model looks like this:
public function trees()
{
return $this->belongsToMany(Tree::class);
}
public function parentOf (){
return $this->belongsToMany(Thing::class, 'thing_thing', 'parent_id', 'child_id');
}
public function childOf(){
return $this->belongsToMany(Thing::class, 'thing_thing', 'child_id', 'parent_id');
}
Finally, the Tree Nova resource has these fields:
public function fields(Request $request)
{
return [
ID::make(__('ID'), 'id')->sortable(),
Text::make('name'),
ID::make('user_id')->hideWhenUpdating()->hideWhenCreating(),
Boolean::make('public'),
BelongsToMany::make('Things', 'associatedThings')
];
}
It should not be possible to create a Tree without an attached Thing, but the creation screen looks like this:
How do I require this in Nova?
This is not possible through nova's default features. Here is how I would go about it with the least effort (you Might want to create a custom field for that yourself) - or at least how I solved a similar issue in the past:
1. Add the nova checkboxes field to your project
2. Add the field to your nova ressource :
// create an array( id => name) of things
$options = Things::all()->groupBy('id')->map(fn($e) => $e->name)->toArray();
// ...
// add checkboxes to your $fields
Checkboxes::make('Things', 'things_checkboxes')->options($options)
3. Add a validator that requires the things_checkboxes to be not empty
4. Add an observer php artisan make:observer CheckboxObserver that will sync the model's relations with the given id-array through the checkboxes and then remove the checkboxes field from the object (as it will throw a column not found otherwise), so something like this:
public function saving($tree)
{
// Note: In my case I would use the checkbox_relations method of the HasCheckboxes trait and loop over all checkbox relations to perform the following and get the respective array keys and relation names
$available_ids = array_unique($tree['things_checkboxes']);
// Attach new ones, remove old ones (Relation name in my case comes from the HasCheckboxes Trait)
$tree->things->sync($available_ids);
// Unset Checkboxes as the Key doesn't exist as column in the Table
unset($tree['things_checkboxes']);
return true;
}
5. Add the same thing in reverse for the retreived method in your observer if you want to keep using the checkboxes to handle relations. Otherwise, add ->hideWhenUpdating() to your checkbox field
I added a trait for that to easily attach the relations through checkboxes to a model:
trait HasCheckboxRelations
{
/**
* Boot the trait
*
* #return void
*/
public static function bootHasCheckboxRelations()
{
static::observe(CheckboxObserver::class);
}
/**
* Defines which relations should be display as checkboxes instead of
* #return CheckboxRelation[]
*/
public static function checkbox_relations()
{
return [];
}
}
And checkbox_relations holds an array of instances of class CheckboxRelation which again holds informations about the key name, the relation name and so on.
public function __construct(string $relationName, string $relatedClass, string $fieldName, bool $hasOverrides = false, string $relationType = null, array $_fields = [])
Also, I added a method attachCheckboxRelationFields to the default nova resource which will be called on the $fields when the model uses the trait.
Now, I only have to add HasCheckboxRelations to a model, add the array of checkbox_relations and thats it - I have a belongsToMany relation on the nova resource through checkboxes. Of course you don't have the option to manage pivot fields anymore if you go for it this way - which might be why it was not done by the nova devs - but for simple belongsToMany relations I really like to work with the checkbox solution instead of the default attach-table. And for data with pivot fields you can still use the default way.
Also note that parts of the code where written on the fly so it might not work out of the box, but the overall idea should be delivered.
Hope it helped!
alternative
https://github.com/Benjacho/belongs-to-many-field-nova
BelongsToManyField::make('Role Label', 'roles', 'App\Nova\Role'),
What I need to do is extend all of the functionality of the Spatie permissions package Role model, but use a different table for the derived model.
Right now I have a model SubscriptionPackage that I want to emulate the behavior of a Role such that it can be assigned permissions and in turn this model can be assigned to users. But I wanna keep the Role model intact too.
I have tried extending Yes, but when I create a new SubscriptionPackage, the new record is created in the roles tables instead of subscription_packages table despite specifying the table in my derived Model. As shown below
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Models\Permission; // This extends from Spatie\Permission\Models\Permission
use Spatie\Permission\Models\Role as SpatieRole;
class SubscriptionPackage extends SpatieRole
{
//
protected $guarded = ['id'];
protected $table = 'subscription_packages';
/**
* The permissions that belong to the package.
*/
public function packagePermissions()
{
return $this->belongsToMany(Permission::class);
}
}
With the code above I expect when I create a new SubscriptionPackage, the record should be inserted into the subscription_packages table but in this case it goes to the roles table.
Any pointers on how to go about this will be highly appreciated.
If you have a look at the Role source code you will this inside the __construct method:
public function __construct(array $attributes = [])
{
$attributes['guard_name'] = $attributes['guard_name'] ?? config('auth.defaults.guard');
parent::__construct($attributes);
$this->setTable(config('permission.table_names.roles')); // <-- HERE IS THE PROBLEM!
}
So, if you want that your SubscriptionPackage to write its records in the right table you have to override this behaviour like this:
public function __construct(array $attributes = [])
{
parent::__construct($attributes)
$this->setTable('your_table_name'); // <-- HERE THE SOLUTION!
}
I don't think you can. Spatie already have 5 tables and fetched data from those only. But still if you want to make the change you have make the changes with table and column name in the model
I am new to Laravel and also asked the question on Laracast without any success so far.
Here is my problem: I have a database layout something like this:
Table: categoryA_products
Table: categoryB_products
Table: categoryC_products
and per default the Laravel user table:
Table: user
I have create a two Laravel Eloquent models:
Product:
namespace App;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
// protected $table = '';
public function users()
{
return $this->belongsTo( User::class );
}
}
User:
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
public function products()
{
return $this->hasMany( Product::class );
}
}
As each product has a different table name I would normally create 1 model for each table but as they are all similar I would like to define the model table name at runtime.
I know I can do this with "$product->setTable()" but as I use the "newRelatedInstance" class from Laravel (hasMany and belongsTo) I cannot initiate the product class and set the table.
Is there a workaround for this?
Yes, I am aware that I could create a category table and link the products to each category but this is a fictional database model. There is a reason for this approach and I can explain it more in detail if needed. That said it make sense for this sample but I cannot use it for the live database.
I have a working solution with a model for each "category" but this is very messy.
Any help would be appreciated.
Since you're unable to load the relations, you could try referencing and re-initializing them like:
$relations = $product->getEagerLoads();
$attributes = $product->getOriginal();
table_name = 'categoryA_products'; // or categoryB_products or categoryC_products
$product->newQuery()
->newModelInstance($attributes)
->setTable($table_name)
->setEagerLoads($relations)
->...
in my model i have
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Page extends Model
{
//
}
in my controller i can say Page:all() and get all the rows from pages table
but i dont see any connection between Page model and pages table in the database
does it just guess table name based on the model name (lower case with extra s at the end ) or it's mentioned somewhere else ?
As you can see in the docs, this is the magic of Laravel :-)
https://laravel.com/docs/5.2/eloquent#defining-models (see Table Names)
If you want, you can set another name manually by user the following
protected $table = 'my_table_name';
And to go a bit further, this is how Laravel gets the table name in the base Model you can found at /vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php
/**
* Get the table associated with the model.
*
* #return string
*/
public function getTable()
{
if (isset($this->table)) {
return $this->table;
}
return str_replace('\\', '', Str::snake(Str::plural(class_basename($this))));
}
You can specify the table by putting the below code into your model
protected $table = 'your table name';
Otherwise, it takes a table name as a plural form of the model.
For example, if your model name Product then by default it connects the table named 'products'.
So if your table name is not different then no need to configure it. It's will connect automatically
By default, it takes the "snake case" of the class name used in the model. Also, add a final "s" to make the plural.
You can also define a custom name by adding the variable
protected $table = 'my_table_name';
in the model class.
For instance:
namespace App;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
/**
* The table associated with the model.
*
* #var string
*/
protected $table = 'my_flights';
}