Laravel store data thru model - laravel

I dont know how else to label the title. Anyone have ideas go ahead and make a suggested edit.
I have a series of Models for the database in my application.
I can currently add a global scope to the model and have the model automatically add a "where" clause on my queries to the database on a key:value pair. This is working great.
class Customers extends Model
{
protected $table = 'customers';
protected static function boot() {
parent::boot();
static::addGlobalScope('companyRecordID', function (Builder $builder) {
$builder->where('companyRecordID', Auth::guard('user')->user()->companyRecordID);
});
}
}
I am having troubles trying to identify if this can be done; Id like to be able to store the "companyRecordID" from the Auth::guard('user')->user()->companyRecordID automatically when a database record is created. Similar to created_at and updated_at are created automatically without requiring code from the controller to define.
Can someone direct me to what I should be looking for. Ive spent a few hours trying to google key word pairs to find an answer with no avail.

If you are using Models to create the records you can listen for the creating event for the Model and then add any additional fields you may need. You can create a listener for the creating event on Customers:
Customers::creating(function ($customer) {
if (auth('user')->user()) {
$customer->companyRecordID = auth('user')->user()->companyRecordID;
}
});
You can throw that in a Service Provider's boot method or your Model's boot method. If you throw it in the Model's boot method you may want to adjust to using static:: instead of Customers::.

I believe you got your answer but,
On your model please use protected $fillable
protected $fillable = ['','',''];
protected $table = 'customers';
also, you can use Relationships to optimize your codes.

Related

Extending Eloquent Models with dynamic global scopes

I have a MySQL table that receives many different Jotform reports in JSON payloads. This has helped tremendously in capturing queryable data quickly without adding to the front-end developer's workload.
I created an eloquent model for the table. I now would like to be able to create models that extend it for each Jotform we create. I feel like it will increase the readability of my code drastically.
My eloquent model is called RawDataReport. It has created_at, updated_at, data, and report name columns in the table. I want to create the model ShiftInspectionReport extending the RawDataReport.
I have two JotForm reports one is called Shift Inspection Report and one is called Need Inspection Report. Both are part of the ShiftInspectionReport model.
So I need to query the RawDataReports table for any reports matching those names. I frequently need to query the RawDataReports report_name column with either one or more report names.
To help with this I created a local scope to query the report name which accepts either a string report name or an array of string report names. Here is the local scope on the RawDataReports model.
protected function scopeReportName($query, $report_name): \Illuminate\Database\Eloquent\Builder
{
if (is_array($report_name)) {
return $query->orWhere(function ($query) USE ($report_name) {
ForEach($report_name as $report) {
if (is_string($report) === false) {
throw new \Exception('$report_name must be an array of strings or a string');
}
$query->where('report_name', $report);
}
});
} else {
if (is_string($report_name) === false) {
throw new \Exception('$report_name must be an array of strings or a string');
}
return $query->where('report_name', $report_name);
}
}
EDIT - after comments I simplified the reportName scope
protected function scopeReportName($query,array $report_name): \Illuminate\Database\Eloquent\Builder
{
return $query->whereIn('report_name',$report_name);
}
Now in my ShiftInspectionReport model, I'd like to add a global scope that can use that local scope and pass in the $report_name. But according to this article, Laravel 5 Global Scope with Dynamic Parameter, it doesn't look like Laravel global scopes allow you to use dynamic variables.
I could just create a local scope in ShiftInspectionReport but the readability would look like
$reports = ShiftInspectionReport::shiftInspectionReport()->startDate('2021-05-15')->get()
when I'd really like to be able to just call
ShiftInspectionReport::startDate('2021-05-15')->get()
Any suggestions or comments would be appreciated.
Thank you
Thanks to IGP I figured out that I can just call the local scope right from my boot function.
My extended class looks like this now and it works.
class ShiftInspection extends RawDataReport
{
use HasFactory;
protected static function booted()
{
static::addGlobalScope('shift_inspection_report', function(\Illuminate\Database\Eloquent\Builder $builder) {
$builder->reportName(['Shift Safety Inspection','Need Safety Inspection']);
});
}
}

Overriding Laravel get and first methods

I need to override above mentioned methods to skip some database records. Using where is not an option since I would have to use it every single time as there are records in database that I do not need most of the time and I am not allowed to delete them from DB. Here is my attempt of doing this:
class SomeTable extends BaseModel {
public static function first() {
$query = static::query();
$data = $query->first();
if($data && $data->type == 'migration_type') return null;
return $data;
}
public static function get() {
$query = static::query();
$data = $query->get();
foreach($data as $key => $item) {
if($item->type == 'migration_type') unset($data[$key]);
}
return $data;
}
}
The problem with this code is that it works only when direct called on model. If I am using some other functions, like where, before get or first methods, it just skips my overridden method.
What would be the right way to do this and should I put this code within model?
My question is not duplicate as in the answer from mentioned question it is said:
all queries made from Models extending your CustomModel will get this new methods
And I need to override those two functions only for specific model, not for each one in application as not all tables have type column. That's the reason why I have written them within model class.
I need to override above mentioned methods to skip some database records.
Consider a global query scope on the model.
https://laravel.com/docs/5.8/eloquent#global-scopes
Global scopes allow you to add constraints to all queries for a given model. Laravel's own soft delete functionality utilizes global scopes to only pull "non-deleted" models from the database. Writing your own global scopes can provide a convenient, easy way to make sure every query for a given model receives certain constraints.
The issue here is that the where() method on the model returns a QueryBuilder instance where get() will return a Collection instance.
You should be able to override collection's default methods by adding a macro in it's place and can be done like so...
Collection::macro('toUpper', function () {
return $this->map(function ($value) {
return Str::upper($value);
});
});
Extending the query builder instance is not so easy but a good tutorial exists here and involves overriding the application's default connection class, which is not great when it comes to future upgrades.
Because after calling where you're dealing with the database builder and theses methods inside your model aren't being called .. about the issue you might overcome it by using select instead of first directly so will deal with the builder ..
example:
SomeTable::select('col1','col2')->take(1)->get();
another thing overriding these kind of methods is not a good idea if you're working with other developer on the same project.
good luck

Load more attributes into existing eloquent model instance

Is it possible to load additional attributes into model instance without multiple queries or hackery? Let me explain:
// I got a tiny model with only id loaded
$model = Model::first(['id']);
// Then some code runs
// Then I decide I'd need `name` and `status` attributes
$model->loadMoreAttributes(['name', 'status']);
// And now I can joyously use name and status without additional queries
$model->name;
$model->status;
Does Eloquent have something similar to my fictional loadMoreAttributes function?
Notice kindly that I'm not a novice and am very well aware of Model::find($model->id) and such. They're just too wordy.
Thanks for your attention in advance.
You may extend the Eloquent model to have this loadMoreAttributes method like so:
use Illuminate\Database\Eloquent\Model;
class YourModel extends Model
{
public function loadMoreAttributes(array $columns)
{
// LIMITATION: can only load other attributes if id field is set.
if (is_null($this->id)) {
return $this;
}
$newAttributes = self::where('id', $this->id)->first($columns);
if (! is_null($newAttributes)) {
$this->forceFill($newAttributes->toArray());
}
return $this;
}
}
This way you can do this on your model:
$model = YourModel::first(['id']);
$model->loadMoreAttributes(['name', 'status']);
LIMITATION
However there's a limitation to this hack. You may only call loadMoreAttributes() method if the unique id of your model instance is already fetched.
Hope this help!

Laravel - Extending Model

I've created a BaseModel class, which extends from Model. It seemed like everything was working fine, but now I've run into a problem when saving. I'm overriding the save() method in this BaseModel. I'd just like to add some attributes to the model before saving. So I do that, then call return parent::save($options);. The method signature is still the same: public function save(array $options = []).
It appears to be grabbing the name of the BaseModel class for the table name when performing the insert (it's using base_models as the table name), rather than the actual model that is being saved. Has anyone run into this before? What is the proper way of extending from the model class?
I originally created some traits to handle some extra functionality, but thought it would be a better idea to just create a base model and have my models extend from that instead.
In your model (the child one that extends the base model) add the table name explictly for example:
class SomeChildModel extends BaseModel {
// Manually set the table name
protected $table = 'table_name';
}
I realized that I previously had a static method that was creating an instance of itself using new self() and would set a few attributes, back when I was using the methods from a trait. It was fine before, but now since I moved the methods into the base model, that method was actually being called on the base model itself rather than the class that had the trait.
I was basically using the static method to instantiate the class, as I've read it's one way to avoid cluttering the constructor. But I just opted to do it in the constructor this time around since it made sense, so that was my solution.
Laravel will use snake case of the class name by default (the class where save method is called), if no $table instance variable is set. In your case it will use snake case of the BaseModel as a table name. You have two solutions:
Solution 1:
In classes which extends BaseModel add the $table instance variable as follow:
class User extends BaseModel {
protected $table = 'table_name'; // Your table name in the database;
}
Solution 2:
You can use Laravel Eloquent's Events, which allows you to hook into various points in the model's lifecycle.
You can hook into the save method as follow and make your changes. You can use these methods in your BaseClass, in traits, etc. For example in your BaseModel:
class BaseModel extends Model
{
/**
* Listen for save event
*/
protected static function boot()
{
parent::boot();
static::saving(function($model)
{
if ( ! $model->isValid()) {
return false;
}
});
}
}
The above will always call isValid before a model is saved into the storage. In this case it will return false and will not save the object.
For more info see the official docs here. Let me know if it isn't clear.

Laravel 4: How to add more data to Auth::user() without extra queries?

I'm rather new to Laravel 4 and can't seem to find the right answer, maybe you can help:
A User in our application can have many Accounts and all data is related to an Account, not a User. The account the User is currently logged into is defined by a subdomain, i.e. accountname.mydomain.com.
We added a method account() to our User model:
/**
* Get the account the user is currently logged in to
*/
public function account()
{
$server = explode('.', Request::server('HTTP_HOST'));
$subdomain = $server[0];
return Account::where('subdomain', $subdomain)->first();
}
The problem is that there is always an extra query when we now use something like this in our view or controller:
Auth::user()->account()->accountname
When we want to get "Products" related to the account, we could use:
$products = Product::where('account_id', Auth::user()->account()->id)->get();
And yet again an extra query...
Somehow we need to extend the Auth::user() object, so that the account data is always in there... or perhaps we could create a new Auth::account() object, and get the data there..
What's the best solution for this?
Thanks in advance
Just set it to a session variable. This way, you can check that session variable before you make the database call to see if you already have it available.
Or instead of using ->get(), you can use ->remember($minutes) where $minutes is the amount of time you wish to keep the results of the query cached.
You should take a look at Eloquent relationships : http://laravel.com/docs/eloquent#relationships
It provides simple ways to get the account of a user and his products. You said that a user can have many accounts but you used a first() in your function I used a hasOne here.
Using Eloquent relationships you can write in your User model:
<?php
public function account()
{
// I assume here 'username' is the local key for your User model
return $this->hasOne('Account', 'subdomain', 'username');
}
public function products()
{
// You should really have a user_id in your User Model
// so that you will not have to use information from the
// user's account
return $this->hasMany('Product', 'account_id', 'user_id');
}
You should define the belongsTo in your Account model and Product model.
With Eager Loading you will not run a lot of SQL queries : http://laravel.com/docs/eloquent#eager-loading
You will be able to use something like
$users = User::with('account', 'products')->get();
To get all users with their account and products.
I think this is a good example for the purpose of Repositories.
You shouldn't query the (involved) models directly but wrap them up into a ProductRepository (or Repositories in general) that handles all the queries.
For instance:
<?php
class ProductRepository
{
protected $accountId;
public function __construct($accountId)
{
$this->accountId = $accountId;
}
public function all()
{
return Product::where('account_id', $this->accountId)->get();
}
}
//now bind it to the app container to make it globaly available
App::bind('ProductRepository', function() {
return new ProductRepository(Auth::user()->account()->id);
});
// and whenever you need it:
$productRepository = App::make('ProductRepository');
$userProducts = $productRepository->all();
You could group the relevant routes and apply a filter on them in order to bind it on each request so the account-id would be queried only once per repository instance and not on every single query.
Scopes could also be interesting in this scenario:
// app/models/Product.php
public function scopeCurrentAccount($query)
{
return $query->where('account_id', Auth::user()->account()->id);
}
Now you could simply call
$products = Product::currentAccount()->get();

Resources