I have a pair of objects in laravel, pages and contents.
I have setup a relationship function, and include, which includes contents based on page_id.
I want to apply other conditions such as where deleted - 0, and where dates are within certain bounds, I know how to apply where conditions and set these field up.
What I can't fathom is how extra conditions can be applied as well as matching relationship fields.
Could anybody help me with this?
Defining the relationship in the model is all you need to do to the model itself. However you can add a static function to the model to get the data with the information you need.
Page model methods examples:
class Page extends Eloquent {
public function contents()
{
return $this->has_many('Content');
}
// Return all content that has not been marked as
// deleted in the database
public static function with_contents($id)
{
return Page::find($id)->contents()
->where_null('deleted')
->get();
}
}
This example assumes that the deleted field in the contents table is null unless it is deleted, in which case it will have any value.
To use you would simply use
$page = Page::with_contents('1');
You can create as many of these static helper methods as you like or add as many conditions to the one method as you like, then use your new static methods to retrieve your data.
I think this might be what you're looking for
http://doginthehat.com.au/2012/06/adding-conditions-to-relationships-in-laravel/
class User extends Eloquent {
public function meta()
{
return $this->has_many('Meta','target_id')
->where('target_type','=',$this->table());
}
}
Related
I've inherited a Laravel 5 project at work and would like to know how I should check for the existence of a related model to avoid null exceptions.
BlockDate model
class BlockDate extends Model {
public function claims()
{
return $this->belongsToMany(User::class);
}
}
User model
class User extends Model {
public function blocks()
{
return $this->belongsToMany(BlockDate::class);
}
}
Pivot table
$table->unsignedInteger('user_id');
$table->foreign('user_id')->references('id')->on('users');
$table->unsignedInteger(block_date_id');
$table->foreign('block_date_id')->references('id')->on(block_dates);
Users can claim a range of dates for vacation requests. However, users may have no claims or dates may not have been claimed. I am currently using
if ($user->blocks->count() > 0) {
$dates = $user->blocks->sortByDesc('created_at');
// more logic here....
}
I do not like using count everywhere, is there a way to incorporate the check like:
// I don't know what hasClaimedDates might be
$dates = $user->hasClaimedDates()->blocks->sortByDesc('created_at');
You can use the actual relationship method instead of the magic accessor:
$sortedDates = $user->blocks()->latest()->get();
This will give you an empty collection if no relations are established, but it will not fail on the sorting.
Note: latest() is an equivalent for orderBy('created_at', 'desc') in this case.
By the way, if you use $user->blocks->count(), it will first load all related models into memory and then count on the relation. If you are going to use the related models afterwards, that is fine. But if you don't and you only count them, this is a waste of resources. In this case $user->blocks()->count() is way more performant as it executes a database query that only returns a single number. Take this into consideration especially where you have a lot of related models.
Laravel offers an optional helper method to guard against nulls:
// will return either a collection or null
$dates = optional($user->blocks)->sortByDesc('created_at');
I have a model "User", and each user has "Posts". Now there are different types of posts, like "TextPost", "VideoPost", etc.
I want to get all posts of a user like this:
$user->posts()->get()
This should return an array of objects with the correct class, so e.g. an array like this:
[App\VideoPost{}, App\TextPost{}]
I tried to use polymorphic relationships as described in the docs, but it doesn't work. Any ideas?
There isn't really a relationship type in Laravel for your specific use case. The easiest thing you can do in this case is create a separate hasMany relationship for each of your post types, and then write a function that returns a combination of all the results.
So you'd end up with something like this in your User class:
public function videoPosts()
{
return $this->hasMany(App\VideoPost::class);
}
public function textPosts()
{
return $this->hasMany(App\TextPost::class);
}
public function getPosts()
{
return $this->textPosts->concat($this->videoPosts);
}
You can then just keep chaining concat calls for every other post type you may want to add.
My Association model looks like this (irrelevant code redacted):
class Association extends Model
{
public function members() {
return $this->hasMany('App\Member');
}
}
My Member model looks like this:
class Member extends Model
{
public function scopeActive($query) {
return $query->where('membership_ended_at', Null);
}
public function scopeInactive($query) {
return $query->whereNotNull('membership_ended_at');
}
}
This is what I want to be able to do:
$association = Association::find(49);
$association->members->active()->count();
Now, I'm aware there's a difference between a Query and a Collection. But what I'm basically asking is if there's some kind of similar scope for collections. Of course, the optimal solution would be to not have to write TWO active methods, but use one for both purposes.
(question already answered in the comments, but might as well write a proper answer)
It is not possible to use a query scope in a Colletion, since query scope is a concept used in Eloquent to add constraints to a database query while Collections are just a collection of things (data, objects, etc).
In your case, what you need to do is to change this line:
$association->members->active()->count();
to:
$association->members()->active()->count();
This works because when we call members as a method, we are getting a QueryBuilder instance, and with that we can start chaining scopes to the query before calling the count method.
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
},
......
]
I have 3 models in my Laravel 4 project: Employee, EmployeeClass, Employer:
class Employee extends Eloquent {
protected $table = 'users';
public function employee_class () {
return $this->belongsTo('EmployeeClass', 'employee_class_id');
}
}
class EmployeeClass extends Eloquent {
protected $table = 'employee_classes';
public function employees () {
return $this->hasMany('Employee');
}
public function employer () {
return $this->belongsTo('Employer');
}
}
class Employer extends Eloquent {
protected $table = 'employers';
public function employee_classes () {
return $this->hasMany('EmployeeClass');
}
}
The EmployeeClass relationships work as expected. I can execute EmployeeClass::find(1)->employees; or EmployeeClass::find(1)->employer; and it returns the object.
Trying to make the same calls on the other two (to retrieve the relationship to EmployeeClass) doesn't work. Both of these lines return empty sets:
Employee::find(1)->employee_class;
Employer::find(1)->employee_classes;
However, the weird thing is, both of these lines work correctly:
Employee::find(1)->employee_class()->first();
Employer::find(1)->employee_classes()->first();
The first example returns NULL (I believe it should be returning a Collection). The second example returns an EmployeeClass object (the expected instance).
I want to point out that there is one entry in each table with an id of 1, and each one is set up with the FK = 1 as well, so they should join properly. In fact, I think the fact that EmployeeClass works correctly, and the fact that getting the query and executing it (in the second, successful, set of code) does as well, sort of proves that.
I'm sure I'm just doing something stupid; maybe another set of eyes will help!
I can use the workaround (the second set of code) since it seems to be working but I'd like to get it clean and correct if at all possible...
For multi-word relationships, the function should be in camelCase (in fact, all class methods should). When accessing a model's attributes, it is still allowed to access the relationship name in snake case (in your example, 'employee_class', but note that this bypasses all eager loading and you should access the relationship in exactly the same case as the relationship method's name.
In your example, if you rename the employee_class(es) functions to employeeClass(es), everything should work.
// gets all employees and their class(es). the argument(s) for with()
// MUST match the names of the methods exactly.
Employee:with('employeeClass')->get();
// you MUST access eager loaded data in the same case as in with().
// if you access via snake case, eager loading is bypassed.
$employee->employeeClass;
// this also works but should generally be avoided.
Employee::find(1)->employeeClass()->first();