Adding attributes to a model - laravel

I have four columns in my users database and a config variable. In my view I need to show the sum of those columns divided by the variable - simple enough to calculate but ideally I'd like the result of that to be stored as an attribute on the model.
Basically to avoid doing this:
$user = Auth::user();
$user->setPercent();
dd($user->percent);
What I'd like to do is have that percent attribute set automatically but I'm not sure where to do that. I've tried overriding the model constructor but that the data for the user hasn't been retrieved at that point.
Thanks

To create you just have to add a method like this to your user model:
class User extends BaseModel implements UserInterface, RemindableInterface {
public function getPercentAttribute($value)
{
/// do your magic
return $yourMagicValue
}
}
To use it the way you said you need:
$user->percent

And if you want to have this attribute when the object is created do:
public function __construct($attributes = array())
{
if (!isset($this->percent))
$this->percent = $this->setPercent();
parent::__construct($attributes);
}
This works for me

Related

Laravel Mutator to add predefined values into database

I'm new into Laravel and I'm trying to store the user's company id on a column of the products table each time a user creates a new product. The company's id it's retrieved from the user's session. I'm trying it with Laravel's Mutator:
public function setFirstNameAttribute($value) {
$this->attributes['company_id'] = session()->get('company.id');
}
But each time I create a new Product the company id stored it's null. Seems like the function it's never executing. Is there any other resource to perform actions like this?
You must use model events - this will be executed on model creation before saving. Or you can use another events depends on you logic - see docs.
class YourModel extends Model
{
/**
* The "booted" method of the model.
*
* #return void
*/
protected static function booted()
{
static::creating(function (YourModel $model) {
$model->company_id = session()->get('company.id');
});
}
}
Mutators only works when you change mutating field directly:
$model->first_name = 'new_name'
And with your code - you will lost "new_name".
I noticed that the function name is incorrect, since the accessors use "studly" cased name of the column you wish to access, it may be as simple as to change
public function setFirstNameAttribute($value)
to
public function setCompanyIdAttribute($value)

Laravel 4.1 eloquent model set appends dynamically

I am using Laravel 4.2.
I have two models: User and Video, both of these models are having one-to-many relationship i.e. User -> HasMany-> Video.
Recently, I got a requirement to display the list of users along with sum of file-size of total videos uploaded by each user and allow users to be order by the sum of file size ascending or descending.
I've made following changes in User model:
class User extends Eloquent {
protected $hidden = array('videosSum');
protected $appends = array('videos_size');
public function videosSum() {
return $this->hasOne('Video')
->selectRaw('sum(file_size) as sum, user_id')
->groupBy('user_id');
}
public function getVideosSizeAttribute()
{
// if relation is not loaded already, let's do it first
if ( ! array_key_exists('videos_size', $this->relations)){
$this->load('videosSum');
}
$related = $this->getRelation('videosSum');
return $this->attributes['videos_size'] = isset($related->sum) ? (int) $related->sum : 0;
}
}
And using like:
User::where('id', '!=', Auth::user()->id);
I am getting the desired result.
But the problem is, I don't want the videos_size attribute everywhere, where the User model gets called. I want to set it dynamically.
I tried User::$appends = ['videos_size'] but it gives protected property cannot be set outsize of class error.
I also tried to make a method in User model which set the $appends if called, but it is also not working.
Can anybody help me how to enable the appends property dynamically?
Laravel doesn't support this off the bat.
my friend and I wrote this extention:
Dynamically hide certain columns when returning an Eloquent object as JSON?
basically you have to override your models.php toArray() method as appended attributes get calculated when you ask for the model in json or array form.
you can add to the trait that's in that link and use it or just put these methods in your respective model class.
public static function getStaticAppends() {
return self::$_appends;
}
public static function setStaticAppends(array $value) {
self::$_appends = $value;
return self::$_appends;
}
public static function getDefaultAppends() {
return with(new static)->getAppends();
}
public function getAppends(){
return $this->appends;
}
public function toArray() {
if (self::getStaticAppends()) {
$this->appends = self::getStaticAppends();
}
return parent::toArray();
}

Limit Eloquent Model to specific columns

I'm pulling from a rather large database and for security reasons, my database user can only select a limited number of columns from the student table: name, graduation_date, and gender. But there are dozens of other columns returned in a select * statement.
In regular SQL, if I run something like:
SELECT * FROM students
will return an error on that table. Same if I run the eloquent model
Students::all();
will return an error as well.
I know in Eloquent, you can limit your selects when defining a relationship similar to:
class Students extends Eloquent {
protected $table = 'student_info';
public function classes() {
return $this->hasMany('classes')->select(array('room', 'time'));
}
}
So, My question is, can the select limits be done on the main model, similar to limiting it on the classes table. So, when I run Student::all(); it only selects the columns I need.
The main problem is every time I run a student Query, I'm having to do a specific select command each time instead of just saying "Student::all()". Same thing for Student::find(1); will also return an error, because it still runs a SELECT * FROM student_info WHERE id = 1.
I tried setting $visible variable, but it still renders sql equivalent to SELECT * FROM ...
Anyone have a solution?
UPDATE:
Please note that I'm looking or a solution on the model level, not the controller level. I can select from the controller side, but that defeats the purpose of a Model concept and have to declare the columns to select at every query.
Thanks!
Troy
You can create an intermediate class and overload the all() function. Let's call this class Elegant
Elegant.php
abstract class Elegant extends Model
{
public static $returnable = [];
public function all()
{
return $this->get(static::$returnable)->all();
}
}
Then you extend this class, and define your returnable columns to it.
Student.php
<?php
class Student extends Elegant
{
public static $returnable = ['room', 'time'];
}
Now use it as you wanted: Student::all() in your controller. If you leave returnable as an empty array, then you will get everything.
Expanding on Jarek's suggestion of using Global Scope you could do it like this, I'm not 100% sure the remove part is right though, will need testing.
SelectLimitTrait.php
trait SelectLimitTrait {
public static function bootSelectLimitTrait()
{
static::addGlobalScope(new SelectLimitScope);
}
public function getQueryable()
{
if(! $this->queryable ) return array('*');
return $this->queryable;
}
}
SelectLimitScope.php
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\ScopeInterface;
class SelectLimitScope implements ScopeInterface {
public function apply(Builder $builder)
{
$query = $builder->getQuery();
$queryable = $builder->getModel()->getQueryable();
$query->columns = $queryable;
}
public function remove(Builder $builder)
{
$query = $builder->getQuery();
$query->columns = null;
}
}
And then in your Eloquent model put this:
Students.php
class Students extends \Eloquent {
use SelectLimitTrait;
protected $queryable = array('name','graduation_date', 'gender');
}
Now Students::all() and Students::find(1) etc. are limited to querying name, graduation_date and gender
You may use something like this:
public function newQuery()
{
return parent::newQuery()->select('room', 'time');
}
Put the newQuery method in your Students model and use the Student model normally you would use. It's a hacky way but easiest one. Just override the parent::query(). In this way you'll always get the selected fields.

phpunit not finding model method (Laravel / Mockery)

I'm getting started with unit testing in Laravel 4, and I'm stuck testing a custom method in a model I've added to the standard User model.
use Illuminate\Auth\UserInterface;
use Illuminate\Auth\Reminders\RemindableInterface;
class User extends BaseModel implements UserInterface, RemindableInterface {
/**
* Logs to the database and streams an update
* Uses logIt and streamIt on Base model
* #param String $action The action being performed
* #return void
*/
private function logAndStream($action)
{
$this->logIt('info', $action.'d user '.$this->username);
$this->streamIt($action.'d user '.$this->username);
}
This class extends the BaseModel which in turn extends Eloquent, and has the defines the logIt and StreamIt methods like so:
class BaseModel extends Eloquent {
/**
* Log an action to log file
* #return void
*/
protected function logIt($level, $msg) {
...
}
/**
* Log an action to activity stream
* #return void
*/
protected function streamIt($msg, $client = null, $project = null) {
...
}
All of this code works fine when I'm manually testing things. But now I want to create a unit test to automate it.
class UserTest extends TestCase {
public function testLogAndStream()
{
$base = Mockery::mock('BaseModel')->shouldAllowMockingProtectedMethods();
$base->shouldReceive('logIt')
->with('info', 'Created user Tester')
->once();
$user = new User;
$user->username = 'Tester';
$user->logAndStream('Create');
}
When I try running this, I get a failure complaining about not finding logAndStream.
1) UserTest::testLogAndStream
BadMethodCallException: Call to undefined method Illuminate\Database\Query\Builder::logAndStream()
What am I missing?
You have two problems here:
First, your User model contains a logAndStream method, but it's private. This means that doing this:
$user->logAndStream('Create');
is not possible because only public methods are accessible in this way.
Second, in your test, you are mocking an instance of BaseModel. This has nothing to do with the instance of User that you instantiate a couple of lines later. User extends BaseModel - you can still have instances of User and BaseModel that are not related to one another. Here's an analogy:
class Database {
}
class DatabaseWithExtraFeatures extends Database {
}
The first one (Database) is just a plain old database access class and works just fine for basic stuff. Then someone comes along and realizes that Database doesn't provide some extra feature, so they build upon it by extending it. A developer can use either, or even both, in the same application.
$db = new Database;
$dbExtra = new DatabaseWithExtraFeatures;
// do something with $db
$result1 = $db->query();
// do something with $dbExtra
$result2 = $dbExtra->extraSpecialQuery();
What you've done in your test is analogous to this - you've mocked one instance of BaseModel and then instantiated a User class (which just happens to extend BaseModel).
EDIT Here's a little more detail on how to mock the user model:
$user = Mockery::mock('User')->shouldAllowMockingProtectedMethods();
$user->shouldReceive('logIt')
->with('some arguments')
->once();
$user->shouldReceive('streamIt')
->with('some arguments')
->once();
// when you set a property on an Eloquent model, it actually calls the
// setAttribute method. So do this instead of $user->username = 'Tester'
//
$user->shouldReceive('setAttribute')
->with('username', 'Tester')
->once()
$user->logAndStream('Create');
// now assert something...
Because User inherits from BaseModel, the methods from BaseModel are actually methods on User and can be treated as if they were defined as part of User.
Disclaimer: This was extracted from the question.
Firstly, I should have been checking that logIt and streamIt where observed on the same, mocked User model and not the parent BaseModel.
Populating the mock user model with $user->username was not correct either. In the end, Kryten helped me realise that Eloquent internally calls getAttribute('username') for this anyway, so I can return the value directly as part of an assertion, which would then be fed into logIt and StreamIt. This works but feels a little clunky - if anybody can suggest a better way, I'd love to learn.
Here's the working test case which works irrespective of whether logAndStream is declared as either a public or protected:
public function testLogAndStream()
{
$user = Mockery::mock('User');
$user->shouldReceive('getAttribute')
->with('username')
->atLeast()->once()
->andReturn('Tester');
$user->shouldReceive('logIt')
->once()
->with('info','Created user Tester');
$user->shouldReceive('streamIt')
->once()
->with('Created user Tester');
$user->logAndStream('Create');
}

How to return database table name in Laravel

Is there a way that I can get the current database table in use by the model that I'm in? I see that there is a table() function in Laravel/Database/Eloquent/model.php but I've been unsuccessful calling it calling it from the model that I'm in.
There is a public getTable() method defined in Eloquent\Model so you should be able to use $model->getTable().
Taylor has an answer to your question:
Within the model class you can do something like this:
return with(new static)->getTable();
If you want all your models to have the ability to return table name statically, then so something like this:
class BaseModel extends Eloquent {
public static function getTableName()
{
return with(new static)->getTable();
}
}
class User extends BaseModel {
}
User::getTableName();
Edit April 2019: This answer is now out of date. See the new correct answer by Flyn San
Yes - Eloquent has a $table variable. There are two ways you can access this:
class yourModel extends Eloquent {
public static $table = "differentTable";
function someFunction()
{
return yourModel::$table;
}
}
or
class yourModel extends Eloquent {
public function someFunction()
{
return $this->table();
}
}
then in your code
Route::get('/', function () {
$model = new yourModel();
dd($model->someFunction());
});
In my case, i'm using laravel 5.4
return (new static)->getTable();
Since table is a protected property in the Model class (Laravel >= 5) you will need an instance of your Model.
Here is a case example:
DB::table( (new YourModelClassname)->getTable() )
->update(['field' => false]);
You can get name of a model's table by following code:
If we have a Model as ModelName:
ModelName::query()->getQuery()->from
This method also works fine in case of custom table name that are defined by protected $table = 'custom_table_name' in the Model.
It will return the table name from the model. perfectly worked on laravel 8
app(Modelname::class)->getTable()
you have to replace Modelname with your model class
Based on Lucky Soni answer, there is another easy trick if you want to directly call it from Vontroller or View.
Tested in Laravel 6, and I keep using it, if you are "One Line Programmer" who hates extra line instance declaration. No need for extra lines in Model file too.
$string_table_name = with(new \App\Model\TableModelName)->getTable();
or better you may also be able to just call this
$string_table_name = (new \App\Model\TableModelName)->getTable();
It will return plain string of the tabel name even if you rename $table variable inside model class.
EDIT :
Minus Rep ?? Maybe you should try this first in your controller instead making new function in model class just to get table name and no need to declare the object when calling.
with() itself is Laravel helper function that returns an object of the class. and inside class that extends Model, already has function getTable(). So, you don't have to put another new redundant function inside model class.
It seems the latest version, you can just call (new Class) without with() function.
The difference between this answer and Lucky's answer, mine doesn't make any new function inside Model class to get the table name, even you can just call the function inside the Controller and View without declaring the object of model class. It's for beautify the code.
While Lucky's answer create new function that inside Model class, and you need to call the function from the object.
Simple way to get table name from Laravel Model by this:
$tableName = app(\App\User::class)->getTable();
Don't forget to replace:
\App\User
With Model path.
Here's an other approach so that you can get a model's table name statically.
Define a Trait: app/Traits/CanGetTableNameStatically.php
<?php namespace App\Traits;
trait CanGetTableNameStatically
{
public static function tableName()
{
return (new static)->getTable();
}
}
Extend your required Model or BaseModel with the use statement.
app/Models/BaseModel.php
<?php namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Traits\CanGetTableNameStatically;
class BaseModel extends Model
{
use CanGetTableNameStatically;
// ...
}
On your models, if you set the custom table name on Laravel's reserved attribute: protected $table then it will still work & return correct table name.
app/Models/Customer.php
<?php namespace App\Models\Master;
use App\Models\BaseModel;
class Customer extends BaseModel
{
protected $table = 'my_customers';
// ...
}
Usage: just call YourModel::tableName() anywhere.
In Views:
{{ \App\Models\Customer::tableName() }}
When doing Joins:
DB::table( Product::tableName() . ' AS p' )
->leftJoin( ProductCategory::tableName() . ' AS pc', 'pc.id', '=', 'p.category_id')
// ... etc
Note:
I use this approach where needed but full disclosure, I found another answer here that have the exact same approach, so I copy pasted here for reference of course with citation thanks to #topher
Based on tailor Otwell's answer you could use something like this:
with(new Model)->getTable();
Note: tested on versions 5.x, 6.x, 7.x, 8.x and it works well.
another solution is to use the resolve helper like so:
resolve('\\App\\Models\\User')->getTable()
None of the answers so far will get you the table name with the prefix, if you are using a table name prefix. At this time it seems like we need to concatenate the prefix with the table name ourselves if we want the real name of database table.
Here's how to get the table name including the table prefix:
echo \App\MyModel::query()->getQuery()->getGrammar()->getTablePrefix() . app(\App\MyModel::class)->getTable();
in laravel 7.x (i'm used)
you can get table name with (new Target())->getTable();
$query->where('parent_id', function ($query) use ($request) {
$query->select('id')->from((new Target())->getTable())->where('unit_id', $request->unit_id);
});
hope it's helps
To people who want to get table name from a Builder object instead of other object, here you are:
$conn = DB::connection("my_private_mysql_conn");
$my_builder_object = $conn->table("my_table_name");
//This will print out the table name
print $my_builder_object->from;
It will work 100%. You will get table name.
$object = new OrderStockProduct();
// Use below line only when you have dynamic connection in laravel project
// $object->setConnection('mysql');
$object = $object->getTable();
dd($object);
I just wanted to add the following for people coming from search engines:
In case you do not even want to instantiate the Model at all (faster?) :
$model = 'App\User';
$modelTable = str_replace('\\', '', Str::snake(Str::plural(class_basename($model))));
dd($modelTable); // will return "users"
That might look ugly but that's exactly how the getTable() method resolves it under the hood, so...
You will need to use Illuminate\Support\Str; on top of your file.
Addendum: implying you follow the framework's standards (i.e: Post model has posts table, User model has users table, etc)
In Laravel 4 use static method
$table_name = Model::getTable();
or "self" inside Eloquent Model
$table_name = self::getTable();

Resources