Laravel - how to makeVisible an attribute in a Laravel relation? - laravel

I use in my model code to get a relation
class User extends Authenticatable
{
// ...
public function extensions()
{
return $this->belongsToMany(Extension::class, 'v_extension_users', 'user_uuid', 'extension_uuid');
}
// ...
}
The Extension has field password hidden.
class Extension extends Model
{
// ...
protected $hidden = [
'password',
];
// ...
}
Under some circumstances I want to makeVisible the password field.
How can I achieve this?

->makeVisible([...]) should work:
$model = \Model::first();
$model->makeVisible(['password']);
$models = \Model::get();
$models = $models->each(function ($i, $k) {
$i->makeVisible(['password']);
});
// belongs to many / has many
$related = $parent->relation->each(function ($i, $k) {
$i->makeVisible(['password']);
});
// belongs to many / has many - with loading
$related = $parent->relation()->get()->each(function ($i, $k) {
$i->makeVisible(['password']);
});

Well, I got the idea from https://stackoverflow.com/a/38297876/518704
Since my relation model Extension::class is called by name in my code return $this->belongsToMany(Extension::class,... I cannot even pass parameter to it's constructor.
So to pass something to the constructor I may use static class variables.
So in my Extension model I add static variables and run makeVisible method.
Later I destruct the variables to be sure next calls and instances use default model settings.
I moved this to a trait, but here I show at my model example.
class Extension extends Model
{
public static $staticMakeVisible;
public function __construct($attributes = array())
{
parent::__construct($attributes);
if (isset(self::$staticMakeVisible)){
$this->makeVisible(self::$staticMakeVisible);
}
}
.....
public function __destruct()
{
self::$staticMakeVisible = null;
}
}
And in my relation I use something like this
class User extends Authenticatable
{
...
public function extensions()
{
$class = Extension::class;
$class::$staticMakeVisible = ['password'];
return $this->belongsToMany(Extension::class, 'v_extension_users', 'user_uuid', 'extension_uuid');
}
...
}

The highest voted answer didn't seem to work for me (the relations attribute seems to be a protected array now so can't be used as a collection in #DevK's answer), I instead used:
$parent->setRelation('child', $parent->child->first()->setVisible(['id']));

Related

lararvel uuid as primary key

I'm trying to set an uuid as primary key in a Laravel Model. I've done it setting a boot method in my model as stablished here so I don't have to manually create it everytime I want to create and save the model. I have a controller that just creates the model and saves it in database.
It is saved correctly in database but when controller returns the value of the id is always returned with 0. How can I make it to actually return the value that it is creating in database?
Model
class UserPersona extends Model
{
protected $guarded = [];
protected $casts = [
'id' => 'string'
];
/**
* Setup model event hooks
*/
public static function boot()
{
parent::boot();
self::creating(function ($model) {
$uuid = Uuid::uuid4();
$model->id = $uuid->toString();
});
}
}
Controller
class UserPersonaController extends Controller
{
public function new(Request $request)
{
return UserPersona::create();
}
}
You need to change the keyType to string and incrementing to false. Since it's not incrementing.
public $incrementing = false;
protected $keyType = 'string';
Additionally I have an trait which I simply add to those models which have UUID keys. Which is pretty flexible. This comes originally from https://garrettstjohn.com/articles/using-uuid-laravel-eloquent-orm/ and I added some small adjustments to it for issues which I have discovered while using it intensively.
use Illuminate\Database\Eloquent\Model;
use Ramsey\Uuid\Uuid;
/**
* Class Uuid.
* Manages the usage of creating UUID values for primary keys. Drop into your models as
* per normal to use this functionality. Works right out of the box.
* Taken from: http://garrettstjohn.com/entry/using-uuids-laravel-eloquent-orm/
*/
trait UuidForKey
{
/**
* The "booting" method of the model.
*/
public static function bootUuidForKey()
{
static::retrieved(function (Model $model) {
$model->incrementing = false; // this is used after instance is loaded from DB
});
static::creating(function (Model $model) {
$model->incrementing = false; // this is used for new instances
if (empty($model->{$model->getKeyName()})) { // if it's not empty, then we want to use a specific id
$model->{$model->getKeyName()} = (string)Uuid::uuid4();
}
});
}
public function initializeUuidForKey()
{
$this->keyType = 'string';
}
}
Hope this helps.
Accepted answer not worked for me on Laravel 9, but this way worked perfect, you can try it:
1- Create new Trait Class in project path app/Traits/IdAsUuidTrait.php (if you not found Traits folder create it, this is full code of this Class:
<?php
namespace App\Traits;
use Illuminate\Support\Str;
trait IdAsUuidTrait
{
public function initializeIdAsUuidTrait(): void
{
$this->keyType = 'string';
$this->id = Str::orderedUuid()->toString();
}
}
2- In any model you want to make id as UUID just call trait like this:
use App\Traits\IdAsUuidTrait;
class YourModelName extends Model
{
use IdAsUuidTrait;
...
That is it, now try to create, select, update any row in database by this model...

Laravel: Creating a common function for all models

In my laravel(7.x) application, I have a common functionality to show the count of all the active and inactive records in all the modules. Therefore, I am obligated to repeat the same functionality on every module.
For example: Device, DeviceType, DeviceCompany, etc models have a same method called _getTotal and everywhere that _getTotal method is doing the same work.
Device.php
class Device extends Model
{
protected $table = 'devices';
...
public function _getTotal($status = \Common::STATUS_ACTIVE)
{
return self::where([
'status' => $status
])->count() ?? 0;
}
}
DeviceType.php
class DeviceType extends Model
{
protected $table = 'device_types';
...
public function _getTotal($status = \Common::STATUS_ACTIVE)
{
return self::where([
'status' => $status
])->count() ?? 0;
}
}
I tried to put this method in the Base Model but I think may not be a good practice. Am I right..?
Is there any way to make this method _getTotal a common method for all the modules..?
You could move this method to a trait and include the trait instead to all classes that need this method.
trait DeviceStatusTotal
{
public function _getTotal($status = \Common::STATUS_ACTIVE)
{
return self::where([
'status' => $status
])->count() ?? 0;
}
}
DeviceType.php
class DeviceType extends Model
{
use DeviceStatusTotal;
protected $table = 'device_types';
// ...
}
Or you can create a classe extending Model default class and your models extends from this custom class (that haves your custom function)
You can use laravel Global scopes:
https://laravel.com/docs/5.7/eloquent#global-scopes
Another why use traits and use in the models and make the method to local scope as:
public function scopePopular($query) {
return $query->where('votes', '>', 100);
}

Laravel - Delete if no relationship exists

Below is the one of the model. I would like to delete a Telco entry only if no other model is referencing it? What is the best method?
namespace App;
use Illuminate\Database\Eloquent\Model;
class Telco extends Model
{
public function operators()
{
return $this->hasMany('App\Operator');
}
public function packages()
{
return $this->hasMany('App\Package');
}
public function topups()
{
return $this->hasMany('App\Topup');
}
public function users()
{
return $this->morphMany('App\User', 'owner');
}
public function subscribers()
{
return $this->hasManyThrough('App\Subscriber', 'App\Operator');
}
}
You can use deleting model event and check if there any related records before deletion and prevent deletion if any exists.
In your Telco model
protected static function boot()
{
parent::boot();
static::deleting(function($telco) {
$relationMethods = ['operators', 'packages', 'topups', 'users'];
foreach ($relationMethods as $relationMethod) {
if ($telco->$relationMethod()->count() > 0) {
return false;
}
}
});
}
$relationships = array('operators', 'packages', 'topups', 'users', 'subscribers');
$telco = Telco::find($id);
$should_delete = true;
foreach($relationships as $r) {
if ($telco->$r->isNotEmpty()) {
$should_delete = false;
break;
}
}
if ($should_delete == true) {
$telco->delete();
}
Well, I know this is ugly, but I think it should work. If you prefer to un-ugly this, just call every relationship attributes and check whether it returns an empty collection (meaning there is no relationship)
If all relationships are empty, then delete!
After seeing the answers here, I don't feel copy pasting the static function boot to every models that need it. So I make a trait called SecureDelete. I put #chanafdo's foreach, inside a public function in SecureDelete.
This way, I can reuse it to models that need it.
SecureDelete.php
trait SecureDelete
{
/**
* Delete only when there is no reference to other models.
*
* #param array $relations
* #return response
*/
public function secureDelete(String ...$relations)
{
$hasRelation = false;
foreach ($relations as $relation) {
if ($this->$relation()->withTrashed()->count()) {
$hasRelation = true;
break;
}
}
if ($hasRelation) {
$this->delete();
} else {
$this->forceDelete();
}
}
}
Add use SecureDelete to the model that needs it.
use Illuminate\Database\Eloquent\Model;
use App\Traits\SecureDelete;
class Telco extends Model
{
use SecureDelete;
public function operators()
{
return $this->hasMany('App\Operator');
}
// other eloquent relationships (packages, topups, etc)
}
TelcoController.php
public function destroy(Telco $telco)
{
return $telco->secureDelete('operators', 'packages', 'topups');
}
In addition, instead of Trait, you can also make a custom model e.g BaseModel.php that extends Illuminate\Database\Eloquent\Model, put the function secureDelete there, and change your models to extends BaseModel.

Laravel: override eloquent function for get results?

I can override function before save :
public function save(array $options = [])
{
if(isset($this->datesConvert)){
foreach($this->datesConvert as $date){
$this->attributes[$date] = Carbon::createFromFormat('d/m/Y', $this->attributes[$date])->format('Y-m-d');
}
}
parent::save($options);
}
But which method I can use for get result ? and where is documentation for this. I am looking for something like :
public function get()
{
parent::get();
if(isset($this->datesConvert)){
foreach($this->datesConvert as $date){
$this->attributes[$date] = Carbon::createFromFormat('Y-m-d', $this->attributes[$date])->format('d/m/Y');
}
}
}
With that I can convert 10 date rows without need of 20 mutators..
It seems that Attribute casting fits your needs or use Date mutators
You may customize which fields are automatically mutated, and even completely disable this mutation, by overriding the $dates property of your model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* The attributes that should be mutated to dates.
*
* #var array
*/
protected $dates = [
'created_at',
'updated_at',
'deleted_at',
// more dates
];
}
EDIT
Another way, you can override getAttribute method in Model
<?php
namespace App;
use Carbon\Carbon;
trait DateFormatting
{
protected function dateFields()
{
return [];
}
public function getAttribute($key)
{
if ( array_key_exists( $key, $this->dateFields() ) ) {
return Carbon::createFromFormat('d/m/Y', $this->attributes[$key])->format('Y-m-d');
}
return parent::getAttribute($key);
}
}
then you can use this trait in any your model, just don't forget override dateFields in it
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use App\DateFormatting;
class User extends Model
{
use DateFormatting;
protected function dateFields()
{
return [
'finished_at',
// other field names that you need to format
];
}
after all you can access to this fields as usual(using magic __get())
$model->finished_at;
I find a solution, My solution is :
public function save(array $options = [])
{
if(isset($this->datesConvert)){
foreach($this->datesConvert as $date){
$this->attributes[$date] = \Carbon\Carbon::createFromFormat('d/m/Y', $this->attributes[$date])->format('Y-m-d');
}
}
parent::save($options);
}
public function getAttribute($key)
{
$value = parent::getAttribute($key);
if(isset($this->attributes[$key])){
if(isset($this->datesConvert) && in_array($key, $this->datesConvert)){
$value = \Carbon\Carbon::createFromFormat('Y-m-d', $value)->format('d/m/Y');
}
}
return $value;
}
Laravel comes with something very useful for this problem. I'm not sure what it's called, but you can modify attributes or even add new attributes like this:
class YourModel extends Model
{
...
public function getDateAttribute()
{
return Carbon::createFromFormat('Y-m-d', $this->attributes[$date])->format('d/m/Y');
}
...
}
You can retrieve this attribute like:
$yourModel->date;
Edit after comment #fico7489
You can't ignore the fact you always have to modify things. However there are still some solutions to make it easier.
For example you can change your date column to a string and just store your date with the desired date format.
Other solution should be modifying the date through sql. FORMAT(Now(),'YYYY-MM-DD').
Example in laravel would look like (not tested):
YourModel::select([
'*',
DB::raw('
FORMAT(yourDateColumn,'YYYY-MM-DD')
')
])->get();

Laravel4 - Saving a model with multiple relationships/foreign keys

I've tried to understand a process of saving a model with multiple relationships but I still can't figure out how to do it "kosher" way.
To begin with - I have an Event model that belongs to a category (Eventcat) and a Location:
// Event.php
class Event extends Eloquent {
protected $table = 'events';
public function location()
{
return $this->belongsTo('Location');
}
public function eventcat()
{
return $this->belongsTo('Eventcat');
}
public function users()
{
return $this->belongsToMany('User');
}
}
// Location.php
class Location extends Eloquent
{
protected $table = 'locations';
public function events()
{
return $this->hasMany('Event');
}
}
// Eventcat.php
class Eventcat extends Eloquent
{
protected $table = 'eventcats';
public function events()
{
return $this->hasMany('Event');
}
}
I've seeded the database with a few categories and locations and now I trying to get events saving work. I thought that the $event->eventcat()->associate( $eventcat ) would work but I got a Call to undefined method eventcat() error.
public function postCreateEvent() {
$event = new Event();
$eventcat = Eventcat::find( Input::get('event-create-eventcat[]') );
$location = Location::find( Input::get('event-create-location[]') );
$event->title = Input::get('event-create-title');
$event->description = Input::get('event-create-description');
$event->price = Input::get('event-create-price');
$event->start_date = Input::get('event-create-start_date');
$event->end_date = Input::get('event-create-end_date');
$event->eventcat()->associate( $eventcat );
$event->location()->associate( $location );
$event->save();
}
I've read the documentation, API and a few threads here but I still can't figure out the best way to deal with this.
Thanks for replies!
I would actually bet that you have a conflict in your class name. Laravel contains an Event class and I wonder if that isn't what's being called in your code. As a quick test, you could rename your class FooEvent and see if it works.
The best solution is probably namespacing your model (see http://chrishayes.ca/blog/code/laravel-4-methods-staying-organized for a quick intro) so that your model can still be called Event without conflicting with the builtin class.

Resources