Do something before saving model to database in Laravel 5.1 - laravel

How can I do something such as modify some data fields or more validate before writing data to database in Laravel 5.1 model ?
It's document about that problem is hard to use in real application: http://laravel.com/docs/5.1/eloquent#events
My code is
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use App\Helpers\Tools as Tools;
class Atoken extends Model
{
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'atoken';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'token',
'user_id',
'role',
];
/**
* The attributes excluded from the model's JSON form.
*
* #var array
*/
protected $hidden = [
];
public static function newToken($userId, $role){
# Remove all token assoiciate with input user;
Atoken::where('user_id', $userId)->delete();
$params = [
'user_id' => $userId,
'role' => $role,
];
Atoken::insert($params);
$item = Atoken::where('user_id', $userId)->first();
return $item->token;
}
protected static function boot(){
static::creating(function ($model) {
$model->token = 'sometoken';
});
}
}
In this case, I always got error:
SQLSTATE[23502]: Not null violation: 7 ERROR: null value in column \"token\" violates not-null constraint (SQL: insert into \"atoken\" (\"user_id\", \"role\") values (2, USER))
How can I fix it?

class Lunch extends Eloquent
{
protected static function boot()
{
static::creating(function ($model) {
$model->topping = 'Butter';
return $model->validate();
});
}
protected function validate()
{
// Obviously do real validation here :)
return rand(0, 1) ? true : false;
}
public static function newToken($userId, $role)
{
static::where('user_id', $userId)->delete();
return static::create([
'user_id' => $userId,
'role' => $role,
])->token;
}
}

I would recommend to go into EventServiceProvider, and register event listeners
public function boot(DispatcherContract $events)
{
parent::boot($events);
// Register Event Listeners
\App\Product::updating(function ($product) {
$product->onUpdating();
});
...
then create function onUpdating within the model. You also can choose from saving, saved, creating, created, updating, updated..
This documentation has more:
https://laravel.com/docs/5.1/eloquent#events

Related

Laravel: How do i fix this trait (with attributes) to also work when creating a model?

I'm using a trait to dynamically add e-mail attribute(s) to a model. It gives me the possibility to reuse code amongst many models. However, this code fails when i try to create a new model (but succeeds when i update an existing model).
The issue is the assumption that $this->id is available in Traits/Contact/HasEmails > setEmailTypeAttribute. Id is not yet available, because saving is not finished.
My question: How can i fix this trait to also work when creating a model?
Google, no results
Thinking about something of model events (static::creating($model))
\app\Traits\Contact\HasEmails.php
/*
* EmailGeneric getter. Called when $model->EmailGeneric is requested.
*/
public function getEmailGenericAttribute() :?string
{
return $this->getEmailTypeAttribute(EmailType::GENERIC);
}
/*
* EmailGeneric setter. Called when $model->EmailGeneric is set.
*/
public function setEmailGenericAttribute($email)
{
return $this->setEmailTypeAttribute(EmailType::GENERIC, $email);
}
/*
* Get single e-mail model for model owner
*/
private function getEmailTypeAttribute($emailType) :?string
{
$emailModel = $this->emailModelForType($emailType);
return $emailModel ? $emailModel->email : null;
}
/*
* Update or create single e-mail model for model owner
*
* #return void
*/
private function setEmailTypeAttribute($emailType, $email) :void
{
$this->emails()->updateOrCreate([
'owned_by_type' => static::class,
'owned_by_id' => $this->id,
'type' => $emailType
],['email' => $email]);
}
\app\Models\Email.php
namespace App\Models;
class Email extends Model
{
public $timestamps = false;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'email'
];
/*
* Get polymorphic owner
*/
public function ownedBy(): \Illuminate\Database\Eloquent\Relations\MorphTo
{
return $this->morphTo();
}
/*
* Default attributes are prefilled
*/
protected function addDefaultAttributes(): void
{
$attributes = [];
$attributes['type'] = \App\Enums\EmailType::GENERIC;
$this->attributes = array_merge($this->attributes, $attributes);
}
}
\migrations\2019_10_16_101845_create_emails_table.php
Schema::create('emails', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('owned_by_id');
$table->string('owned_by_type');
$table->string('type'); //f.e. assumes EmailType
$table->string('email');
$table->unique(['owned_by_id', 'owned_by_type', 'type'], 'owner_type_unique');
});
I expect a related model to be created/updated, but it fails on creating.
Trick was using a saved model event and also not forgetting to set the fillable attribute on the email model:
/*
* Update or create single e-mail model for model owner
*
* #return void
*/
private function setEmailTypeAttribute($emailType, $email) :void
{
static::saved(static function($model) use($emailType, $email) {
$model->emails()
->updateOrCreate(
[
'owned_by_type' => get_class($model),
'owned_by_id' => $model->id,
'type' => $emailType
],
[
'email' => $email
]);
});
}

Lazy eager loaded relationship missing in toArray()

Laravel 5.8
I am lazy loading an user with the related customer which has a one-to-one-relation with a crmaccount-object
The models are working so that when i retrieve the eager-loaded entity it shows all of the nested relationships.
One row later i use the "toArray()" method on that object and the output is missing the third-level-relations.
The only thing which maybe some kind of special regarding the "crmaccount"-model is that it holds a column which is json an has to be casted.
Any idea what is going on here?
All of these happens in a middleware. No difference if i use with or load.
public function handle($request, Closure $next)
{
$UserData = \Auth::user();
if($UserData){
$User = \App\Login::with(['role','customer','customer.crmaccount'])->find($UserData->id);
dump($User);
dd($User->toArray());
$UserData['isAdmin'] = false;
if($UserData['role']['name'] === 'Admin'){
$UserData['isAdmin'] = true;
}
$request->request->add(['UserData' => $UserData]);
}
return $next($request);
}
Login
<?php
namespace App;
use Illuminate\Notifications\Notifiable;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
class Login extends Authenticatable{
use Notifiable;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'name', 'email', 'password','customer_id','role_id'
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'password', 'remember_token',
];
/**
* The attributes that should be cast to native types.
*
* #var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
/* */
public function Role(){
return $this->belongsTo('App\Role');
}
public function Customer(){
return $this->belongsTo('App\Customer');
}
/**
* [hasOpportunities Ruft alle Opportunities des Users ab. Da diese lediglich zwei Entitäten weiter sind, kann anstatt von dot-notated Lazy-Load auch die hasManyThrough-ORM-Methode genutzt werden]
* #return [hasManyThrough-Relation] [Die hasManyThrough-ORM-Beziehung]
*/
public function hasOpportunities(){
return $this->hasManyThrough(
'App\Opportunity',
'App\Customer',
'id',
'customer_id',
'customer_id'
);
}
/**
* [hasSalesreps Ruft alle SalesReps des Users ab. Da diese lediglich zwei Entitäten weiter sind, kann anstatt von dot-notated Lazy-Load auch die hasManyThrough-ORM-Methode genutzt werden]
* #return [hasManyThrough-Relation] [Die hasManyThrough-ORM-Beziehung]
*/
public function hasSalesreps(){
return $this->hasManyThrough(
'App\Salesrep',
'App\Customer',
'id',
'customer_id',
'customer_id'
);
}
}
Customer
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Customer extends Model{
public $timestamps = false;
protected $visible = ['id','name'];
protected $fillable = ['name'];
public function crmaccount(){
return $this->hasOne('App\Crmaccount');
}
public function Salesreps()
{
return $this->hasMany('App\Salesrep');
}
public function Prospects()
{
return $this->hasMany('App\Prospect');
}
public function Trees()
{
return $this->hasMany('App\Salesreptrees');
}
public function Opportunities()
{
return $this->hasMany('App\Opportunity');
}
public function User()
{
return $this->hasMany('App\Login');
}
}
Crmaccount
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Crmaccount extends Model{
public $timestamps = false;
protected $visible = ['id','name','crm_system','customer_id','crm_api_config'];
protected $fillable = [
'name','crm_system','customer_id','crm_api_config'
];
protected $casts = [
'crm_api_config' => 'array'
];
public function customer(){
return $this->belongsTo('App\Customer');
}
}
On every model, there is a protected $visible = []; and protected $hidden = [] attribute. These control the attributes that are available when the model is converted to an object, array or json. This includes relationships, as Laravel internally converts them to attributes, so omitting them from visible, or including them in hidden will cause them to not be available.
In Customer.php:
protected $visible = ['id','name'];
Since crmaccount is not in that array, only id and name will be available. Simply add crmaccount to the array to handle:
protected $visible = ['id','name', 'crmaccount'];
Alternatively, use hidden to explicitly set the attributes you don't want to show, and relationship, if loaded via ->with() will show by default.

Laravel - How to dynamically resolve an instance of a model that has different Namespace?

tldr:
How do you dynamically get an instance of a model just by its DB table name?
What you get from the request:
ID of the model
table name of the model (it varies all the time!)
What you don't know:
Namespace of the model
Longer explanation:
I have a reporting system, that users can use to report something. For each reporting, the ID and the table name is sent.
Until now, every model was under the Namespace App\*. However, since my project is too big, I needed to split some code into Modules\*
Here is an example, how the report is saved in the database:
Example:
Request contains rules:
public function rules()
{
return [
'id' => 'required|string',
'type' => 'required|in:users,comments,offer_reviews, ......',
'reason' => 'required|string',
'meta' => 'nullable|array',
'meta.*' => 'string|max:300'
];
}
In the database, we save the data into :
id reportable_type ...
1 App\User ...
4 Modules\Review\OfferReview ...
How would you create an instance of a model dynamically, when you just know the database table name for example offer_reviews?
There is one solution that jumps to my mind, however, I'm not sure if it adds more security issues. What is if the user sends the full namespace + class name? With that, I know directly where to resolve an instance.
Have a look what I'm doing right now
(before I changed to modules)
//In my controller
class ReportController extends Controller
{
/**
* Stores the report in DB.
*/
public function store(StoreReportRequest $request)
{
$model = $request->getModel();
$model->report([
'reason' => $request->reason,
'meta' => $request->meta
], auth()->user());
return response()->json(['status' => 'Submitted'], 201);
}
}
//in StoreReportRequest
/**
* Gets the Model dynamically.
* If not found we throw an error
* #return \App\Model
*/
public function getModel()
{
return get_model($this->type)
->findOrFail(\Hashids::decode($this->id)[0]);
}
//in Helpers
/**
* Gets me the model of a table name.
* #param String $table Has to be the name of a table of Database
* #return Eloquent The model itself
*/
function get_model($table)
{
if (Schema::hasTable(strtolower($table))) {
return resolve('App\\' . Str::studly(Str::singular($table)));
}
throw new TableNotFound;
}
I don't know if there is a better solution, but here you go. My code is looking for a method with namespace when it's not found we are using App\ as the namespace.
Maybe this code helps someone :)
class StoreReportRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'id' => 'required|string',
'type' => 'required|in:mc_messages,profile_tweets,food,users,comments,offer_reviews,user_reviews',
'reason' => 'required|string',
'meta' => 'nullable|array',
'meta.*' => 'string|max:300'
];
}
/**
* Gets the Model dynamically.
* If not found we throw an error
* #return \App\Model
*/
public function getModel()
{
$namespace = $this->getNamespace();
return $this->resolveModel($namespace);
}
protected function getNamespace(): string
{
$method = $this->typeToMethod();
if (method_exists($this, $method)) {
return $this->$method();
}
return 'App\\';
}
protected function typeToMethod(): string
{
return 'get' . \Str::studly(\Str::singular($this->type)) . 'Namespace';
}
protected function resolveModel(string $namespace)
{
return get_model($this->type, $namespace)
->findOrFail(\Hashids::decode($this->id)[0]);
}
protected function getOfferReviewNamespace(): string
{
return 'Modules\Review\Entities\\';
}
protected function getUserReviewNamespace(): string
{
return 'Modules\Review\Entities\\';
}
}

pivot table in laravel 4 insertion

hey guys im new in laravel and i was trying to insert into my pivot table. i have this structure in my database
the departments table belongs to many categories and same as category so i have this models
use Illuminate\Auth\UserInterface;
use Illuminate\Auth\Reminders\RemindableInterface;
class Departments extends Eloquent {
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'departments';
protected $fillable = ['department_name'];
public $timestamps = false;
/**
* The attributes excluded from the model's JSON form.
*
* #var array
*/
public function categories()
{
return $this->belongsToMany('Categories');
}
}
use Illuminate\Auth\UserInterface;
use Illuminate\Auth\Reminders\RemindableInterface;
class Categories extends Eloquent {
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'categories';
protected $fillable = ['name'];
public $timestamps = false;
/**
* The attributes excluded from the model's JSON form.
*
* #var array
*/
public function department()
{
return $this->belongsToMany('Departments');
}
}
then i have a query in my controller like this
$messages = array(
'required' => 'Please Fill the required field',
'unique' => 'Name Already exist'
);
$catName = Input::get('categoryName');
$deptId = Input::get('deptId');
$validation = Validator::make(Input::all(),[
'categoryName' => 'required|unique:categories,name' ], $messages);
if($validation->fails()){
return array('error' =>$validation->messages()->all() );
}else{
$findDepartment = Departments::find($deptId);
$saveCat = $findDepartment->categories()->insert(array('name' => $catName));
}
but then when i checked the tables it adds up on the categories table but nothing is added in the category_department. do i miss any codes? and also i had an error last time I was trying to migrate my pivot table the error was this.
can you help me guys on what i am missing? tnx for the help in advanced.
First, you should name your model classes as singular: Category, Department.
Then try to declare your relationships with the pivot table name:
public function categories()
{
return $this->belongsToMany('Category', 'category_department');
}
and
public function departments()
{
return $this->belongsToMany('Departments', 'category_department');
}
now, to insert new data, try attach:
$findDepartment = Department::find($deptId);
$category = Category::where('name', '=', $catName)->first();
$saveCat = $findDepartment->categories()->attach($category->id);

Laravel Trying to get property of non-object

I am struggling to understand how laravel works and I have a very difficult time with it
Model - User.php the User model
<?php
use Illuminate\Auth\UserInterface;
use Illuminate\Auth\Reminders\RemindableInterface;
class User extends Eloquent implements UserInterface, RemindableInterface {
protected $fillable = array('email' , 'username' , 'password', 'code');
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'users';
/**
* The attributes excluded from the model's JSON form.
*
* #var array
*/
protected $hidden = array('password');
public function Characters()
{
return $this->hasMany('Character');
}
/**
* Get the unique identifier for the user.
*
* #return mixed
*/
public function getAuthIdentifier()
{
return $this->getKey();
}
/**
* Get the password for the user.
*
* #return string
*/
public function getAuthPassword()
{
return $this->password;
}
/**
* Get the e-mail address where password reminders are sent.
*
* #return string
*/
public function getReminderEmail()
{
return $this->email;
}
}
Model - Character.php the character model
<?php
class Character extends Eloquent {
protected $table = 'characters';
protected $fillable = array('lord_id','char_name', 'char_dynasty', 'picture');
public function user()
{
return $this->belongsTo('User');
}
public function Titles()
{
return $this->hasMany('Title');
}
}
?>
routes.php
Route::group(array('prefix' => 'user'), function()
{
Route::get("/{user}", array(
'as' => 'user-profile',
'uses' => 'ProfileController#user'));
});
ProfileController.php
<?php
class ProfileController extends BaseController{
public function user($user) {
$user = User::where('username', '=', Session::get('theuser') );
$char = DB::table('characters')
->join('users', function($join)
{
$join->on('users.id', '=', 'characters.user_id')
->where('characters.id', '=', 'characters.lord_id');
})
->get();
if($user->count()) {
$user = $user->first();
return View::make('layout.profile')
->with('user', $user)
->with('char', $char);
}
return App::abort(404);
}
}
In my code I will redirect to this route with the following:
return Redirect::route('user-profile', Session::get('theuser'));
In the view I just want to do:
Welcome back, {{ $user->username }}, your main character is {{ $char->char_name }}
My problem is that I will receive this error: Trying to get property of non-object in my view. I am sure it is referring to $char->char_name. What's going wrong? I have a very difficult time understanding Laravel. I don't know why. Thanks in advance!
You should be using the Auth class to get the session information for the logged in user.
$user = Auth::user();
$welcome_message = "Welcome back, $user->username, your main character is $user->Character->char_name";
You don't need to pass anything to that route either. Simply check if the user is logged in then retrieve the data. You have access to this data from anywhere in your application.
if (Auth::check())
{
//the user is logged in
$user = Auth::user();
To answer your question in the comments, reading the documentation would solve all of these problems, however:
public function user()
{
if (Auth::check())
{
$user = Auth::user();
return View::make('rtfm', compact('user'));
}
else
{
return "The documentation explains all of this very clearly.";
}
}

Resources