Laravel Model conditional formatting - laravel

I have a database and model called Vote_actions that looks like this:
id
group_id
user_id
action_type
anonymous (boolean)
User can ask to be anonymous (that would make the boolean value to be true).If that is the case, I want to change the group_id and user_id from the returned model to -1.
Is there a way in laravel that I can do it ?

I know this question is old. I was looking for a way to hide some fields on certain conditions, external conditions like Auth Roles, and internal conditions like Model attributes, and I found a very flexible way to hide them.
And since I saw the other OP's duplicated post Laravel Hidden Fields On Condition asking for hiding field instead, So I'm gonna share it with you.
I know a mutator can change the value of its field, but to Hide it, you need :
the $hidden array attribute
the constructor __Construct() (optional)
to override method newFromBuilder method of Laravel Model
Here are the processes in the Model app\Vote_actions.php:
Hidden. Let's say you normally want to hide the fields created_at and updated_at of Laravel, you use:
protected $hidden = ['created_at', 'updated_at'];
External Conditions. Now let's say if the Authenticated User is Staff you want to unhide them:
public function __Construct()
{
parent::__construct();
if(\Auth::check() && \Auth::user()->isStaff()) {
// remove all fields so Staff can access everything for example
$this->hidden = [];
} else {
// let's hide action_type for Guest for example
$this->hidden = array_merge($this->hidden, ['action_type'];
}
}
Internal Conditions Let's say now you want to hide anonymous field is its value is true:
/**
* Create a new model instance that is existing.
*
* #param array $attributes
* #param array $connection
* #return \Illuminate\Database\Eloquent\Model|static
*/
public function newFromBuilder($attributes = array(), $connection = null)
{
$instance = parent::newFromBuilder($attributes, $connection);
if((bool)$instance->anonymous === true) {
// hide it if array was already empty
// $instance->hidden = ['anonymous'];
// OR BETTER hide single field with makeHidden method
$instance->makeHidden('anonymous');
// the opposite is makeVisible method
}
return $instance;
}
You can't play with hidden attributes and method inside mutators, that's their weakness when we need to hide instead of changing values.
But in any case, understand that calling modification on high load of hundredths of rows can be costly in time.

You are leaning towards an edge case, with special conditions.
Make use of accessors:
class VoteActions extends \Eloquent {
public $casts = [
'anonymous' => 'boolean'
];
...
/**
* Accessors: Group ID
* #return int
*/
public function getGroupIdAttribute()
{
if((bool)$this->anonymous === true) {
return -1;
} else {
return $this->group_id;
}
}
/**
* Accessors: User ID
* #return int
*/
public function getUserIdAttribute()
{
if((bool)$this->anonymous === true) {
return -1;
} else {
return $this->user_id;
}
}
}
Official Documentation: https://laravel.com/docs/5.1/eloquent-mutators#accessors-and-mutators
However, i would recommend that you set the value in the database directly to -1 where necessary so as to preserve the integrity of your application.

Of course you can easily do that. Read about accessors (getters):
https://laravel.com/docs/5.1/eloquent-mutators
Example:
function getUserIdAttribute()
{
return $this->anonymous ? -1 : $this->user_id;
}
function getGroupIdAttribute()
{
return $this->anonymous ? -1 : $this->group_id;
}

Related

How to make it easier to access options in Laravel

There is a database structure like this:
users
id
name
user_options
id
user_id
option_name
option_value
There can be a lot of options, so I didn’t make a separate field for each of them.
I would like to make it easier to access an option by its name.
Currently implemented like this:
In the User model:
public function options() : HasMany
{
return $this->hasMany(UserOptions::class);
}
For the test, I write the code directly in web.php in routes:
$user = User::with('options')->first();
$theme = $user
->options
->firstWhere('option_name', '=', 'theme')
->option_value;
There are many options and I would not like to make such a voluminous appeal to each of them.
Please help me to simplify access to options
If you absoltely must access like an object you could add an attribute accessor on your user model like so:
protected function options(): Attribute
{
return Attribute::make(
get: function($_){
if($this->userOptions !== null){ // have the user options not already been set?
$this->setUserOptions() // set them if not
}
return $this->userOptions // return them
}
)
}
private function setUserOptions(): void
{
$this->userOptions = new stdClass()
foreach(UserOptions::where('user_id', $this->id)->get() as $option){
$optionName = $option['option_name']
$optionValue = $option['option_value']
$this->userOptions->$optionName = $optionValue
}
}
Call like
$user->options->theme
But be way of nonexistant options
A much less complex way would be adding a helper function on your user Model though like so:
/**
* #return Collection<UserOption>
*/
public function options(): HasMany
{
return $this->hasMany(UserOption::class, 'user_id', 'id');
}
/**
* #param string $optionName
* #return mixed
*/
public function getOption(string $optionName): mixed
{
/** #var UserOption $option */
foreach($this->options as $option){
if($option->option_name === $optionName){
return $option['option_value'];
}
}
return null;
}
And simply call like $user->getOption('color'); // eg: "red" | null

Laravel/Livewire: Use withTrashed() on model route binding on to show deleted records

In the list I display the latest topic, including those that is deleted.
function latest()
{
return Topic::withTrashed()->latest();
}
For displaying a single topic I have a Livewire component with that topic passed into it.
class ShowTopic extends Component
{
public $topic;
public function mount(Topic $topic)
{
$this->topic = $topic;
}
public function render()
{
return view('livewire.show-topic', [
'topic' => $this->topic,
]);
}
}
But when I go to a single topic that is deleted, it doesn't show. How can I use withTrashed() on model route bindings to show deleted records with my Livewire component?
You can overwrite the resolveRouteBinding() method on your Eloquent model, and conditionally remove the SoftDeletingScope global scope.
Here I'm using a policy for that model to check if I can delete the model - and if the user can delete it, they can also see it. You could implement any logic you want, or remove the global scope for all requests if that is more suitable for your application.
use Illuminate\Database\Eloquent\SoftDeletingScope;
class Topic extends Model {
// ...
/**
* Retrieve the model for a bound value.
*
* #param mixed $value
* #param string|null $field
* #return \Illuminate\Database\Eloquent\Model|null
*/
public function resolveRouteBinding($value, $field = null)
{
// If no field was given, use the primary key
if ($field === null) {
$field = $this->getKey();
}
// Apply where clause
$query = $this->where($field, $value);
// Conditionally remove the softdelete scope to allow seeing soft-deleted records
if (Auth::check() && Auth::user()->can('delete', $this)) {
$query->withoutGlobalScope(SoftDeletingScope::class);
}
// Find the first record, or abort
return $query->firstOrFail();
}
}

Laravel - formatting all dates simultaneously

I'm currently working on a POC to showcase that it's going to be fairly painless to create an API with Laravel, the catch being that the database is already set in stone.
One problem I've run into is that they've used custom created at and updated at column names, e.g. for a car table, the created_at column would be car_time and the updated date would be cardata_time, and these are all saved as unix timestamps.
I know you can set the CREATED_AT and UPDATED_AT columns for each model. I want to go another step and return all dates in ISO 8601 format.
I've inserted a class between my models and Model called MasterModel and I want to do something like
protected function getCreatedAtAttribute($value)
{
$format = "Y-m-d\TH:i:s\Z";
$datetime = new DateTime($value);
return $datetime->format($format);
}
to make all created at dates be in that format. The problem is that I the custom created at and updated columns mean that this never gets called.
Is there a way for me to identify the created at and updated at columns in such a way that I can use a single method to updated all created at dates at the same time?
UPDATE: I realize my original question was not clear enough - I need to identify all fields that are dates, not just created_at and updated_at, and have them formatted a certain way. They will always be unix timestamps. Not sure how I'd go about this.
Here an answer that will expand on #caddy dz answer who happen to be sitting with me.
 All the things that need to be known
Deactivation of auto management of timestamps
public $timestamps = false; // <-- deactivate the automatic handling
Change table attributes names
const CREATED_AT = 'creation_date'; // <--- change the names
const UPDATED_AT = 'last_update';
source doc:
https://laravel.com/docs/5.8/eloquent#eloquent-model-conventions
By default, Eloquent expects created_at and updated_at columns to
exist on your tables. If you do not wish to have these columns
automatically managed by Eloquent, set the $timestamps property on
your model to false:
 Creating the accessors
class User extends Model
{
/**
* Get the user's first name.
*
* #param string $value
* #return string
*/
public function getFirstNameAttribute($value)
{
// do whatever you want here (change and mutate the value)
return ucfirst($value);
}
}
First thing to know, is that the accessors are a global concept for
eloquent and can be writing for all attributes and not just
getCreatedAtAttribute or getUpdatedAtAttribute.
Second thing to know is that whatever the name of the column, that is
in camle case (firstName) or with _ (first_name) eloquent know to
match to it. The format of the accessor should be
get[NameOfATtribute]Attribute in pascal case (camle case but first
letter too in uppercase).
Three the method argument hold the value of the column in
question. Bellow a snippet that show how it's used
$user = App\User::find(1);
$firstName = $user->first_name; //|=> first_name => getFirstNameAttribute(columnVal)
The resolution is clear.
first_name (column name) => getFirstNameAttribute(columnValue)
All the snippets are from the doc: https://laravel.com/docs/5.8/eloquent-mutators#accessors-and-mutators
 Let's apply all of that
First we need to not use $table->timestamps() in the migration so we make the changment to the bellow.
Schema::create('cars', function (Blueprint $table) {
$table->bigIncrements('id');
$table->timestamp('cardata_time', 0)->nullable();
$table->timestamp('car_time', 0)->nullable();
});
Then we apply the modification on our model:
- we deactivate the auto handling of timestamps.
- Override the timestamps columns names.
- And create the accessors.
Here depend on what we want. If we want to only do the above here a snippet that show that:
// deactivate auto timestamps management
public $timestamps = false;
// change the columns names
const CREATED_AT = 'car_time';
const UPDATED_AT = 'cardata_time';
// creating the accessors (respect the naming)
protected function getCarTimeAttribute($value) //car_time => CarTime
{
// <-- do whatever you want here (example bellow)
$format = "Y-m-d\TH:i:s\Z";
$datetime = new DateTime($value);
return $datetime->format($format);
}
protected function getCardataTimeAttribute($value) //cardata_time => CardataTime
{
// <-- do whatever you want here
$format = "Y-m-d\TH:i:s\Z";
$datetime = new DateTime($value);
return $datetime->format($format);
}
 Doing it with renaming the attributes completely
If what you want is to use another accessing name. Then what my friend #caddy dz did is the way to go. Which happen to be sitting with me. And dared me to expand upon the answer. (hhhh)
You will need to know
$appends and $hidden
Part of the serialization API.
https://laravel.com/docs/master/eloquent-serialization#appending-values-to-json
https://laravel.com/docs/master/eloquent-serialization#hiding-attributes-from-json
$appends allow us to add attributes to the model. That don't exists on the table. We need also to create an accessors for them.
class User extends Model
{
/**
* The accessors to append to the model's array form.
*
* #var array
*/
protected $appends = ['is_admin'];
// ........
/**
* Get the administrator flag for the user.
*
* #return bool
*/
public function getIsAdminAttribute()
{
return $this->attributes['admin'] == 'yes';
}
}
and
$hidden allow us to remove and limit the attribute from the models. Like with the password field.
Doc examples:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = ['password'];
}
And from that what we need to do is to hide the attributes holding the time, that want to be changed to somehting else.
// remove the old attributes names
protected $hidden = ['car_time', 'cardata_time']; // renaming those
// append the new one \/ \/ <- to those
protected $appends = ['car_crated_at', 'cardata_created_at']; // names just for illustration
protected function getCarCreatedAtAttribute($value) // car_created_at => CarCreatedAt
{
$format = "Y-m-d\TH:i:s\Z";
$datetime = new DateTime($value);
return $datetime->format($format);
}
protected function getCardataCreatedAtAttribute($value) // cardata_created_at => CardataCreatedAt
{
$format = "Y-m-d\TH:i:s\Z";
$datetime = new DateTime($value);
return $datetime->format($format);
}
 Applying it for different models
The basic idea is to create a base model then extend it when you create your model.
Formatting all time attributes of the model without exception
If what you want is to apply the formatting for all the time attributes within the model.
Then override serializeDate() method. In practice write a trait, and then you can apply it. Otherwise a base model.
The answer bellow cover it well:
https://stackoverflow.com/a/41569026/7668448
And historically This thread is interesting :
https://github.com/laravel/framework/issues/21703
Serializing in Carbon level
In the documentation laravel 5.7 and up (what i checked [doc only]) :
https://laravel.com/docs/master/eloquent-serialization#date-serialization
We can change the formatting at the level of carbon serialization. But it happen that there was a bug in the past. Normally fixed but i didn't try it. Bug was in 5.7 and fixed in 5.7 if i'm not wrong. The git link above discuss it.
Snippet:
class AppServiceProvider extends ServiceProvider
{
/**
* Perform post-registration booting of services.
*
* #return void
*/
public function boot()
{
Carbon::serializeUsing(function ($carbon) {
return $carbon->format('U');
});
}
___THE_END ^ ^
Not sure what you're asking but if you have cardata_time and car_time in your table defined like this
Schema::create('cars', function (Blueprint $table) {
$table->bigIncrements('id');
$table->timestamp('cardata_time', 0)->nullable();
$table->timestamp('car_time', 0)->nullable();
});
And a MasterModel like so
/**
* Indicates if the model should be timestamped.
*
* #var bool
*/
public $timestamps = false;
const CREATED_AT = 'created_at';
const UPDATED_AT = 'updated_at';
/**
* The accessors to append to the model's array form.
*
* #var array
*/
protected $appends = ['created_at', 'updated_at'];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = ['car_time', 'cardata_time'];
protected function getCreatedAtAttribute($value)
{
$format = "Y-m-d\TH:i:s\Z";
$datetime = new DateTime($value);
return $datetime->format($format);
}
protected function getUpdatedAtAttribute($value)
{
$format = "Y-m-d\TH:i:s\Z";
$datetime = new DateTime($value);
return $datetime->format($format);
}
Results:
{
"id": 1,
"created_at": "2019-09-02T20:31:38Z",
"updated_at": "2019-09-02T20:31:38Z"
}
As in the documentation. The first approach. This requires the dates, to be defined in the $dates property. This will only be triggered if the Model is serialized.
public class YourModel extends Model
{
protected $dateFormat = "Y-m-d\TH:i:s\Z";
}
You can also define it in a provider in the boot method. Which will trigger when a Carbon date is serialized.
public function boot()
{
Carbon::serializeUsing(function ($carbon) {
return $carbon->format("Y-m-d\TH:i:s\Z");
});
}

How do i switch a custom implementation of a interface on a condition in http controller in Laravel

I have a method in my controller which basically is just storing a form to the database.
My form is very big and it has more than 30-40 fields.
So i need to store this information in different 3 tables on a condition.
For example :
foreach($request->all() as $answer):
if($answer->employeeType === 1){
//store data to type_one_table
} else if($answer->employeeType === 2){
//store data to type_two_table
} else if($answer->employeeType === 3){
//store data to type_two_table
} else {
//store data to some_other_table
}
endforeach;
So i was thinking to use some custom StoreInterface which will have some store method.
and extract above logic to its own implementation.
And then in my Controllers constructor receive that interface instance.
But how i can call which implementation do i need for particular condition.
Can i do this a controller ?
Or Should i use different strategy in this case.
please guide me.
Thanks.
You can have a general interface like this:
interface EmployeeStoreContract
{
public function saveAnswer();
}
Then implement this for all of the different ways you can save the answer, e.g.:
class FirstTypeEmployee implements EmployeeStoreContract
{
public function saveAnswer()
{
// do something
}
}
And in your controller to figure out how to save the request data:
public function store()
{
$employeeTypesMap = [
1 => 'FirstTypeEmployee',
2 => 'SecondTypeEmployee',
3 => 'ThirdTypeEmployee',
];
foreach ($request->all() as $answer) {
$employeeType = $answer->employeeType;
if (!array_key_exists($employeeType, $employeeTypesMap)) {
throw new \InvalidArgumentException('Answer type is not available.');
}
$employeeStrategyClass = "App\\Service\\Employee\\{$employeeTypesMap[$employeeType]}";
$employeeStrategyObject = new $employeeStrategyClass;
$employeeStrategyObject->saveAnswer($answer);
}
}
Please note the namespace where you have these classes App\Service\Employee, could be anything you want. Also feel free to make an abstract class or a trait to reuse some of the functionality of each employee class.
There can be many approaches.
I will suggest 2 ways that i would personally use:
1) Create dedicated eloquent models one for each case.
For example:
if($answer->employeeType === 1){
EmployeeOne::create($answer);
}
And your eloquent model can look like:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use DB;
class EmployeeOne extends Model
{
protected $table = 'type_one_table';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
// your db field names
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
];
public $timestamps = false;
}
Make sure that the array keys of the array you pass to the create function must match your db fields. I just gave you an example of the first if so you can take it from here.
2)
Another approach would be to create a model that accepts as a parameter the table name. So based on your if you are going to pass the table name you want to insert and the data to be inserted.
if($answer->employeeType === 1){
$this->myModel->insertData($answer,'type_one_table');
}
And in your model you are going to have something like:
public function insertData($answer,$table){
$query = DB::table($table)->insertGetId($answer); // returns the id of the new record
return $query;
}
So in every if-else statement you just change the table name passing to your model function and that's it.

Dynamically hide certain columns when returning an Eloquent object as JSON?

How do dynamically hide certain columns when returning an Eloquent object as JSON? E.g. to hide the 'password' column:
$users = User::all();
return Response::json($users);
I'm aware I can set protected properties in the model ($hidden or $visible), but how do I set these dynamically? I might want to hide or show different columns in different contexts.
$model->getHidden();
$model->setHidden(array $columns);
$model->setVisible(array $columns);
From Lavarel 5.3 Documentation :
Temporarily Modifying Attribute Visibility
If you would like to make some typically hidden attributes visible on a given model instance, you may use the makeVisible method. The makeVisible method returns the model instance for convenient method chaining:
return $user->makeVisible('attribute')->toArray();
Likewise, if you would like to make some typically visible attributes hidden on a given model instance, you may use the makeHidden method.
return $user->makeHidden('attribute')->toArray();
I've found a complete solution around the problem with using $model->setHidden(array $columns);
Lets say, for example, that you would like to decide in the controller exactly which fields to return. Updating only the model's hidden forces you to go over each model before you return an array of models for example. The problem becomes even worse when those models have relationships that you would also like to change. You have to loop over each model, set the hidden attribute, and then for each also set the relationships hidden. What a mess.
My solution involves creating a static member for each model that when present, updates the visible/hidden attribute just before the call to "toArray":
<?php
trait DynamicHiddenVisible {
public static $_hidden = null;
public static $_visible = null;
public static function setStaticHidden(array $value) {
self::$_hidden = $value;
return self::$_hidden;
}
public static function getStaticHidden() {
return self::$_hidden;
}
public static function setStaticVisible(array $value) {
self::$_visible = $value;
return self::$_visible;
}
public static function getStaticVisible() {
return self::$_visible;
}
public static function getDefaultHidden() {
return with(new static)->getHidden();
}
public static function geDefaultVisible() {
return with(new static)->getVisible();
}
public function toArray() {
if (self::getStaticVisible())
$this->visible = self::getStaticVisible();
else if (self::getStaticHidden())
$this->hidden = self::getStaticHidden();
return parent::toArray();
}
}
As an added bonus, I expose a way to the model's default hidden/visible that you may have set in your model's class.
Don't to forget to add the trait
class Client extends Eloquent {
use DynamicHiddenVisible;
}
Finally, in the controller, before returning your model, decide on visible/hidden attributes:
public function getIndex($clientId) {
// in this specific call, I would like to hide the "special_type" field of my Client model
$hiddenFields = Client::getDefaultHidden();
array_push($hiddenFields, "special_type");
Client::setStaticHidden($hiddenFields);
return Client::find($clientId)->toJson();
}
I don't believe it is the job of the ORM to worry about presentation logic, and that is what JSON is. You'll aways need to cast data to various types as well as hide things and sometimes create a buffer zone to rename things safely.
You can do all of that with Fractal which I built for exactly this reason.
<?php namespace App\Transformer;
use Acme\Model\Book;
use League\Fractal\TransformerAbstract;
class BookTransformer extends TransformerAbstract
{
/**
* List of resources possible to include
*
* #var array
*/
protected $availableIncludes = [
'author'
];
/**
* Turn this item object into a generic array
*
* #return array
*/
public function transform(Book $book)
{
return [
'id' => (int) $book->id,
'title' => $book->title,
'year' => (int) $book->yr,
'links' => [
[
'rel' => 'self',
'uri' => '/books/'.$book->id,
]
],
];
}
/**
* Include Author
*
* #return League\Fractal\ItemResource
*/
public function includeAuthor(Book $book)
{
$author = $book->author;
return $this->item($author, new AuthorTransformer);
}
}
Embedding (including) stuff might be a bit more than you need right now, but it can be very handy too.
In 5.4 you can hide and show attributes dinamically:
$model->makeVisible('attribute');
$model->makeHidden('attribute');
Laravel docs
In addition to #deczo's answer - I feel the $hidden variable is not really designed to be used dynamically. It is more to protect specific data from ever been incorrectly displayed (such as 'password').
If you want specific columns - you should probably just be using a select statement and just get the specific columns you want.
For Laravel 5.3 or greater version,
If you want to make multiple attributes temporary hidden or visible using single statement, you may use model->makeVisible() and model->makeHidden() methods with passing array of attributes.
For example, to hide multiple attributes,
$user->makeHidden(["attribute1", "attribute2", "attribute3"]);
And to make visible multiple attributes,
$user->makeVisible(["otherAttribute1", "otherAttribute2", "otherAttribute3"]);
In the Model:
protected $hidden = [
'your_field_1',
'your_field_2',
];
You can override the getHidden method in order to hide certain columns dynamically:
class FooModel extends Model
{
public function getHidden()
{
// do here your validations and return
// the columns names with the specific criteria
// you need
return ['columnName1', 'columnName2'];
}
}
Made a package for this that uses Model Policies.
https://github.com/salomoni/authorized-attributes
Use the Salomoni\AuthorizedAttributes trait
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Salomoni\AuthorizedAttributes;
class Post extends Model
{
use AuthorizedAttributes;
/**
* The attributes that should be hidden for serialization.
*
* #var array
*/
protected $hidden = ['author_comments'];
}
Create and register a model policy. Add methods for the hidden attributes in camel-case prefixed with see.
namespace App\Policies;
use App\User;
class PostPolicy
{
/**
* Determine if a post author_comments-atrribute can be seen by the user.
*
* #param \App\User $user
* #return bool
*/
public function seeAuthorComments(User $user)
{
return $user->isAuthor();
}
}

Resources