I have a "belongsTo" relation.
public function relation()
{
return $this->belongsTo('App\Relation', 'relation_id');
}
When I create an instance everything works as expected. It is returned the new instance including the relation.
public function store(Request $request)
{
$instance = $this->model->create($request->all());
return $instance;
}
When I update the "relation_id" It returns the old relation. It is not returned immediatly.
public function findOrFail($id)
{
$link = $this->model
->with('relation')
->findOrFail($id);
return $link;
}
public function update(Request $request, $id)
{
$instance = $this->findOrFail($id);
$instance->update($request->all());
//$instance = $instance ->fresh();
return $instance ;
}
Seems that using $instance->fresh() or removing ->with('relation') the new relation is returned immediatly in the current instance.
I am wondering why it does not return the new relation immediatly as it does for create.
create() function returns instance see API DOC on create()
update() returns bool see API DOC on update()
After update you need to reload relation using load() or fresh() as you stated.
You only update current model / table not any relationships bound to it.
Most likely you are missing (or have incorrect) $fillable array in your model. Which protects you against "mass-assignment" vulnerability.
Read more on topic in documentation here.
A mass-assignment vulnerability occurs when a user passes an unexpected HTTP parameter through a request, and that parameter changes a column in your database you did not expect. For example, a malicious user might send an is_admin parameter through an HTTP request, which is then passed into your model's create method, allowing the user to escalate themselves to an administrator.
Tip:
Do not use $request->all(), instead use $request->only(['name', 'age']) now its explicitly using only name and age parameters. I know its not DRY, because you have to keep $fillable in sync with ->only array, but anyone new to the code will know right from controller what is going on.
Related
I have a group of Repository classes I'd like to test. All Eloquent methods are called in these classes, however I am unsure on how to proceed. The only thing that needs to be tested, is that the methods return the right data type. I want to be sure that getByEmail($email) for example actually returns a User object, but I don't care about the implementation details.
Every example I see tells you to inject the model in to the Repository class and mock it, then you can proceed by having that Mock expect very specific method calls. Is this the correct approach? Because it seems to me that would be forcing the developer to use very specific methods when building the Eloquent query.
Here's an example:
class UserRepository {
public function __construct(User user) { ... }
public function getByEmail(string $email) : ?User {
$this->user->where('email', $email)->first();
}
}
For the test with Mock to work I have to do:
class myTest {
public function testMethodReturnsUserObject() {
$user = Mockery::mock(User::class);
$repo = new UserRepository($user);
// now, if a developer changes the eloquent method calls, this will fail.
$user->shouldReceive('where->first')->andReturn($user);
$this->assertEquals($user, $repo->getByEmail('joe.bloggs#acme.com'));
}
}
Any ideas what I should do to test that my method does indeed return a User object, without forcing the developer to use exactly one where() method, and one first() method in this example. What if the developer needs to instead make it ->select()->where()->where()->firstOrFail() . Now the unit test will break, even though it really shouldn't care about this, and only that the method returns a User.
Thanks!
Skip unit testing this, and focus on integration tests with the database. Since that's your primary functionality you want to assert: "Does this repository return the record(s) I expect?"
public function testMethodReturnsUserObject(): void
{
// Note: old factory() syntax; Laravel 8 is different
$expectedUser = factory(User::class)->create([
'id' => 1,
'email' => 'user#example.com',
]);
$repo = new UserRepository(new User());
$actualUser = $repo->getByEmail('user#example.com');
self::assertNotEmpty($actualUser);
self::assertSame(1, $actualUser->id);
self::assertNull($repo->getByEmail('notfound#example.com'));
}
Lots of great docs to assist in database testing.
I am trying to validate a model before saving it. Obviously if the model isn't valid, it shouldn't be saved to the database. When validation fails, it throws an exception and doesn't continue to save. This below works:
$question = new Question([...]);
$question->validate();
$question->save();
I have a problem with the answers() hasMany relationship. According to this answer I should be able to call add() on the relation object:
$question = new Question([...]);
$question->answers()->add(new Answer([...]));
$question->validate();
$question->save();
The above fails:
Call to undefined method Illuminate\Database\Query\Builder::add()
I thought the answers() function would return a HasMany relationship object, but it looks like I'm getting a builder instead. Why?
answers() does return a HasMany object. However, because there is no add method on a HasMany object, Laravel resorts to PHP's __call magic method.
public function __call($method, $parameters)
{
$result = call_user_func_array([$this->query, $method], $parameters);
if ($result === $this->query) {
return $this;
}
return $result;
}
The __call method gets the query instance and tries to call the add method on it. There is no add method on the query builder though, which is why you are getting that message.
Finally, the add method is part of Laravel's Eloquent Collection, not a part of HasMany. In order to get the Collection class, you need to drop the parenthesis (as shown in the answer provided in your link) and do this instead:
$question->answers->add(new Answer([...]));
There's no add method. Use the save method:
$question->answers()->save(new Answer([]));
I have worked on my own factory class that instantiates dependencies and passes through constructs when a dependency is set/requested. If the dependency is cached ( Has already been instantiated by another class ) i pass that class instance instead of instantiating all over again ( mainly used for db connection ). The issue i am having at the moment is as follows.
** To avoid a large question & save reading time i am attempting to illustrate the issue as simply as possible, if the actual code is needed i can paste in.
Class View {
// Construct Requests User Model
}
Class Controller {
// Construct Requests User Model & Class View
$this->user->set($newuserid);
$this->view->display('file');
}
So Controller is instantiated, since View is set as a dependencies it is instantiated and passed to Controller via __construct. Everything is fine, but for things like a profile page. Where i set a new user ( illustrated above ) setting the new userid also alters the userid that is contained within the View User Model. I am not using static vars so i am confused as to why changed made in controller affect the view user model. This causes an issue for me because the logged in User's ID is set via entry point of site ( bootstrap ) & errors are caused when the profile page overwrites the logged in users id. I have added a newInstance option within my factory, to instead instantiate a new user model for the user profile. Things work fine, but i am still curious as to why i had/have this issue.
In PHP5, variables that hold objects do not contain the actual object itself, but an identifier to reference the actual object.
So when you pass objects around, you actually pass a copy of the identifier to the object which of course points to the same object.
PHP Docs on Objects and references
So when you use the same user object in both, your Controller and your View and manipulate it, the actual User Object will be changed. Since both object variables still hold the same identifier to the object, the objects state will be the same in your Controller as well as in your View.
<?php
class User {
protected $name;
public function __construct($name)
{
$this->setName($name);
}
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
}
}
class View {
protected $user;
public function __construct( User $user )
{
$this->user = $user;
}
public function render()
{
echo $this->user->getName();
$this->user->setName('Tony');
}
}
class Controller {
protected $user;
protected $view;
public function __construct( User $user, View $view )
{
$this->user = $user;
$this->view = $view;
}
public function someAction()
{
$this->user->setName('Thomas');
$this->view->render();
echo $this->user->getName();
}
}
$user = new User('Jeffrey');
$view = new View($user);
$controller = new Controller($user, $view);
$controller->someAction(); // Output: ThomasTony
The cruicial thing to understand is that both, the View and the Controller are referencing the same object, thus manipulating the object in one class will result in also manipulating it in the other class (technically that's wrong because both just point to the same object).
Now let's use the clone keyword in the View:
public function __construct( User $user )
{
$this->user = clone $user;
}
Now the $user property of the view will hold a "pointer" to a copy of the user object. Changes made on that object will not affect the initial object that was passed.
Thus the output will be: JeffreyThomas
I want to lose a few words of caution:
Instead of cloning the object you should rather make sure to have clean way of flow of your objects. Your View should not manipulate the state of the User once it got passed.
Using clone can lead to undesired behavior. If someone passes an object to one of your classes and expects class to alter the objects state (we're not talking about "Views" here) while it doesn't it can give them a very hard time of debugging.
I'm trying to provide an extra static 'find' method on my eloquent model, shown here:
public static function findBySku($sku)
{
// Using new self; provides the same empty collection results
$instance = new static;
// Using $instance->sku()->newQuery()->get() also returns the same empty collection
$results = $instance->sku()->get();
/*
* This returns an empty collection, however there are records inside the
* relationship database table?
*/
dd($results);
}
So I can use: Inventory::findBySku($sku);
Here's the relationship:
public function sku()
{
return $this->hasOne('Stevebauman\Maintenance\Models\InventorySku', 'inventory_id', 'id');
}
I know the relationship itself isn't the issue because this returns the results from the database table fine:
Inventory::find(1)->sku()->get();
Anyone have any ideas why this doesn't work?
I know it could be because I'm calling a non-static method from a static instance, but why would it return a resulting collection without throwing an error?
Thanks!
Hang on, figured it out, apologies!
Eloquent relationships have a method getRelated() to access the related model instance. I can then call the methods I need off of it, for example:
public static function findBySku($sku)
{
$instance = new static;
// Using the getRelated() method allows me to run queries on the related model
$results = $instance->sku()->getRelated()->get();
dd($results);
}
Just sort of an odd workaround as you'd think accessing the relationship itself would give you the proper query.
I hope this helps out someone in the future!
I am using the Laravel Framework and this question is directly related to using Eloquent within Laravel.
I am trying to make an Eloquent model that can be used across the multiple different tables. The reason for this is that I have multiple tables that are essentially identical but vary from year to year, but I do not want to duplicate code to access these different tables.
gamedata_2015_nations
gamedata_2015_leagues
gamedata_2015_teams
gamedata_2015_players
I could of course have one big table with a year column, but with over 350,000 rows each year and many years to deal with I decided it would be better to split them into multiple tables, rather than 4 huge tables with an extra 'where' on each request.
So what I want to do is have one class for each and do something like this within a Repository class:
public static function getTeam($year, $team_id)
{
$team = new Team;
$team->setYear($year);
return $team->find($team_id);
}
I have used this discussion on the Laravel forums to get me started: http://laravel.io/forum/08-01-2014-defining-models-in-runtime
So far I have this:
class Team extends \Illuminate\Database\Eloquent\Model {
protected static $year;
public function setYear($year)
{
static::$year= $year;
}
public function getTable()
{
if(static::$year)
{
//Taken from https://github.com/laravel/framework/blob/4.2/src/Illuminate/Database/Eloquent/Model.php#L1875
$tableName = str_replace('\\', '', snake_case(str_plural(class_basename($this))));
return 'gamedata_'.static::$year.'_'.$tableName;
}
return Parent::getTable();
}
}
This seems to work, however i'm worried it's not working in the right way.
Because i'm using the static keyword the property $year is retained within the class rather than each individual object, so whenever I create a new object it still holds the $year property based on the last time it was set in a different object. I would rather $year was associated with a single object and needed to be set each time I created an object.
Now I am trying to track the way that Laravel creates Eloquent models but really struggling to find the right place to do this.
For instance if I change it to this:
class Team extends \Illuminate\Database\Eloquent\Model {
public $year;
public function setYear($year)
{
$this->year = $year;
}
public function getTable()
{
if($this->year)
{
//Taken from https://github.com/laravel/framework/blob/4.2/src/Illuminate/Database/Eloquent/Model.php#L1875
$tableName = str_replace('\\', '', snake_case(str_plural(class_basename($this))));
return 'gamedata_'.$this->year.'_'.$tableName;
}
return Parent::getTable();
}
}
This works just fine when trying to get a single Team. However with relationships it doesn't work. This is what i've tried with relationships:
public function players()
{
$playerModel = DataRepository::getPlayerModel(static::$year);
return $this->hasMany($playerModel);
}
//This is in the DataRepository class
public static function getPlayerModel($year)
{
$model = new Player;
$model->setYear($year);
return $model;
}
Again this works absolutely fine if i'm using static::$year, but if I try and change it to use $this->year then this stops working.
The actual error stems from the fact that $this->year is not set within getTable() so that the parent getTable() method is called and the wrong table name returned.
My next step was to try and figure out why it was working with the static property but not with the nonstatic property (not sure on the right term for that). I assumed that it was simply using the static::$year from the Team class when trying to build the Player relationship. However this is not the case. If I try and force an error with something like this:
public function players()
{
//Note the hard coded 1800
//If it was simply using the old static::$year property then I would expect this still to work
$playerModel = DataRepository::getPlayerModel(1800);
return $this->hasMany($playerModel);
}
Now what happens is that I get an error saying gamedata_1800_players isn't found. Not that surprising perhaps. But it rules out the possibility that Eloquent is simply using the static::$year property from the Team class since it is clearly setting the custom year that i'm sending to the getPlayerModel() method.
So now I know that when the $year is set within a relationship and is set statically then getTable() has access to it, but if it is set non-statically then it gets lost somewhere and the object doesn't know about this property by the time getTable() is called.
(note the significance of it working different when simply creating a new object and when using relationships)
I realise i've given alot of detail now, so to simplify and clarify my question:
1) Why does static::$year work but $this->year not work for relationships, when both work when simply creating a new object.
2) Is there a way that I can use a non static property and achieve what I am already achieving using a static property?
Justification for this: The static property will stay with the class even after I have finished with one object and am trying to create another object with that class, which doesn't seem right.
Example:
//Get a League from the 2015 database
$leagueQuery = new League;
$leagueQuery->setYear(2015);
$league = $leagueQuery->find(11);
//Get another league
//EEK! I still think i'm from 2015, even though nobodies told me that!
$league2 = League::find(12);
This may not be the worst thing in the world, and like I said, it is actually working using the static properties with no critical errors. However it is dangerous for the above code sample to work in that way, so I would like to do it properly and avoid such a danger.
I assume you know how to navigate the Laravel API / codebase since you will need it to fully understand this answer...
Disclaimer: Even though I tested some cases I can't guarantee It always works. If you run into a problem, let me know and I'll try my best to help you.
I see you have multiple cases where you need this kind of dynamic table name, so we will start off by creating a BaseModel so we don't have to repeat ourselves.
class BaseModel extends Eloquent {}
class Team extends BaseModel {}
Nothing exciting so far. Next, we take a look at one of the static functions in Illuminate\Database\Eloquent\Model and write our own static function, let's call it year.
(Put this in the BaseModel)
public static function year($year){
$instance = new static;
return $instance->newQuery();
}
This function now does nothing but create a new instance of the current model and then initialize the query builder on it. In a similar fashion to the way Laravel does it in the Model class.
The next step will be to create a function that actually sets the table on an instantiated model. Let's call this one setYear. And we'll also add an instance variable to store the year separately from the actual table name.
protected $year = null;
public function setYear($year){
$this->year = $year;
if($year != null){
$this->table = 'gamedata_'.$year.'_'.$this->getTable(); // you could use the logic from your example as well, but getTable looks nicer
}
}
Now we have to change the year to actually call setYear
public static function year($year){
$instance = new static;
$instance->setYear($year);
return $instance->newQuery();
}
And last but not least, we have to override newInstance(). This method is used my Laravel when using find() for example.
public function newInstance($attributes = array(), $exists = false)
{
$model = parent::newInstance($attributes, $exists);
$model->setYear($this->year);
return $model;
}
That's the basics. Here's how to use it:
$team = Team::year(2015)->find(1);
$newTeam = new Team();
$newTeam->setTable(2015);
$newTeam->property = 'value';
$newTeam->save();
The next step are relationships. And that's were it gets real tricky.
The methods for relations (like: hasMany('Player')) don't support passing in objects. They take a class and then create an instance from it. The simplest solution I could found, is by creating the relationship object manually. (in Team)
public function players(){
$instance = new Player();
$instance->setYear($this->year);
$foreignKey = $instance->getTable.'.'.$this->getForeignKey();
$localKey = $this->getKeyName();
return new HasMany($instance->newQuery(), $this, $foreignKey, $localKey);
}
Note: the foreign key will still be called team_id (without the year) I suppose that is what you want.
Unfortunately, you will have to do this for every relationship you define. For other relationship types look at the code in Illuminate\Database\Eloquent\Model. You can basically copy paste it and make a few changes. If you use a lot of relationships on your year-dependent models you could also override the relationship methods in your BaseModel.
View the full BaseModel on Pastebin
Maybe, a custom Constructor is the way to go.
Since all that varies is the year in the name of the corresponding db, your models could implement a constructor like the following:
class Team extends \Illuminate\Database\Eloquent\Model {
public function __construct($attributes = [], $year = null) {
parent::construct($attributes);
$year = $year ?: date('Y');
$this->setTable("gamedata_$year_teams");
}
// Your other stuff here...
}
Haven't tested this though...
Call it like that:
$myTeam = new Team([], 2015);
Well its not an answer but just my opinion. I guess, you are trying to scale your application just depending on php part. If you expect that your application will grow by time then it will wise to distribute responsibilities amount all other components. Data related part should handled by RDBMS.
As for example if you are using mysql, you can easily partitionize your data by YEAR. And there are lot's of other topic which will help you to manage your data effectively.
I have a very simple solution to this problem. I am being used in my projects.
you have to use Model Scope for define Table Name Dynamic.
write code in your Model File
public function scopeDefineTable($query)
{
$query->from("deviceLogs_".date('n')."_".date('Y'));
}
Now in your Controller Class
function getAttendanceFrom()
{
return DeviceLogs::defineTable()->get();
}
But If you want to manage Table Name form Controller then you can follow this code.
In Model Class
public function scopeDefineTable($query,$tableName)
{
$query->from($tableName);
}
In Controller Class
function getAttendanceFrom()
{
$table= "deviceLogs_".date('n')."_".date('Y');
return DeviceLogs::defineTable($table)->get();
}
Your Output
[
{
DeviceLogId: 51,
DownloadDate: "2019-09-05 12:44:20",
DeviceId: 2,
UserId: "1",
LogDate: "2019-09-05 18:14:17",
Direction: "",
AttDirection: null,
C1: "out",
C2: null
},
......
]