Filter via condition on one relation and eager load another relation records - arrays

I have this code I want to get list users with a role of teacher where each teacher will have one object for the personal detail one object for the school detail
public function index(){
$teachers = User::whereHas('roles' , function($q){$q->where('name','isTeacher');})->get();
foreach($teachers as $teacher){
$teacher_id = $teacher->id;
$teacherinSchool = School_Teachers::where('user_id', $teacher_id)->first();
$teacherinSchool = $teacherinSchool->school_id;
$School = School::where('id', $teacherinSchool)->first();
return response(['teacher'=>$teacher, 'school'=>$School]);
}
}
this is what i got but i am expecting to have more that one teacher but it takes the first teacher in the list and display the objects
output in the postman
i have 5 models involved here User model, Role model, User_Role model, school model and school_teacher model

Few things to point out
You are doing queries within a loop (foreach) not good for performance.
Having return response() within foreach loop hence only 1 Teacher record is available
You are getting just the first record for School_Teachers & School
For what you are trying to do can be done more efficiently as under
public function index()
{
$teachers = User::whereHas('roles', fn($query) => $query->where('name', 'isTeacher'))->get();
$schoolIds = School_Teachers::whereIn('user_id', $teachers->pluck('id')->toArray())->pluck('id')->toArray();
$schools = School::whereIn('id', $schoolIds)->get();
return response()->json(['teachers' => $teachers, 'schools' => $schools]);
}
However that is also not optimal, still can be better
Define a relation on User model to link it to School model via School_Teachers (many-to-many)
Then in a single query you can get User(s) which have role of isTeacher with their School
For eg: Say you have a table school_teachers which has columns for user_id, school_id (kind of pivot table) where each record can be uniquely identified by ['user_id', school_id'] composite key - unique index on database table.
Then you can define direct (many-to-many) relation between User and School
//User model
public function schools()
{
return $this->belongsToMany(School::class, 'school_teachers', 'user_id', 'school_id');
}
//School model
public function teachers()
{
return $this->belongsToMany(User::class, 'school_teachers', 'school_id', 'user_id');
}
In controller you can do
public function index()
{
$teachers = User::with('schools')
->whereHas(
'roles',
fn($query) => $query->where('name', 'isTeacher')
)
->get();
return response()->json(['teachers' => $teachers]);
}
Laravel Docs - Eloquent Relationships - Many-to-Many

Related

Dotnet EF Core Linq string contains in a list string split by comma

I have a model like this in the database:
Post (PostId int, UserIds varchar(MAX)), example Post (12, "1,2,3,7,9,20")
I want to query it by UserId, for now, I use this:
DBContext.Posts.Where(_ => _.UserIds.Contains(targetId)).ToList();
But problem is that if target is 1, it also return Post with UserIds = "15,16"
I try to use Regex like Regex.IsMatch(_.UserIds, $"\\b{targetId}\\b") but SQL can't translate it.
Is any way to solve this case?
So your database has a table filled with Posts. Every Post seems to be posted by zero or more (maybe one or more) Users. It seems to me, that you also have a table of Users. Every User has posted zero or more Posts.
It seems to me that there is a many-to-many relation between Users and Posts: Every User has posted zero or more Posts; every Post has been posted by zero (one?) or more Users.
Normally in a database you would implement a many-to-many relation with a special table: the junction table.
You don't use the junction table. Your database is not normalized.
Maybe your current problem can be solved without changing the database, but I see so many problems you will have to solve, maybe not now, but in the near future: what immense work would you need to do if you want to delete a user? How do you get all "Posts that user [10] has posted" And what if User [10] doesn't want to be mentioned anymore in the publication list of Post [23]? How to prevent that User [10] is mentioned twice in Post[23]:
UserIds = 10, 3, 5, 10, 7, 10
Normalize the database
Consider to update the database with a junction table and get rid of the string column Post.UserIds. This would solve all these problems at once.
class User
{
public int Id {get; set;}
public string Name {get; set;}
...
// every user has posted zero or more Posts:
public virtual ICollection<Post> Posts {get; set;}
}
class Post
{
public int Id {get; set;}
public string Title {get; set;}
public Datetime PublicationDate {get; set;}
...
// every Post has been posted by zero or more Users:
public virtual ICollection<User> Users {get; set;}
}
And the junction table:
public UsersPost
{
public int UserId {get; set;}
public int PostId {get; set;}
}
Note: [UserId, PostId] is unique. Use this a the primary key
In entity framework the columns of tables are represented by non-virtual properties. The virtual properties reflect the relations between the tables (one-to-many, many-to-many)
Note: a foreign key is a real column in a table, hence a foreign key is non-virtual.
To configure many-to-many, you can use Fluent API:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// User - Post: many-to-many
modelBuilder.Entity<User>()
.HasMany<Post>(user => user.Posts)
.WithMany(post => post.Users)
.Map(userpost =>
{
userpost.MapLeftKey(nameof(UserPost.UserId));
userpost.MapRightKey(nameof(UserPost.PostId));
userpost.ToTable(nameof(UserPost));
});
// primary key of UserPost is a composite key:
modelBuilder.Entity<UserPost>()
.HasKey(userpost => new {userpost.UserId, userpost.PostId});
}
Back to your problem
Once you've implemented the junction table your data request will be easy:
int userId = ...
// get this User with all his Posts:
var userWithPosts= dbContext.Users
.Where(user => user.Id == userId)
.Select(user => new
{
// Select only the user properties that you plan to use
Name = user.Name,
...
Posts = user.Posts.Select(post => new
{
// Select only the Post properties that you plan to use
Id = post.Id
PublicationDate = post.PublicationDate,
...
})
.ToList(),
});
Or, if you don't want any user data, start with the Posts:
var postsOfUser = dbContext.Posts
.Where(post => post.Users.Any(user => user.Id == userId))
.Select(post => new {...});
Some people don't like to use the virtual ICollections, or they use a version of entity framework that doesn't support this. In that case, you'll have to do the Join yourself:
int userId = ...
var postsOfThisUser = dbContext.UserPosts
// keep only the UserPosts of this user:
.Where(userPost => post.UserId == userId)
// join the remaining UserPosts with Posts
.Join(dbContext.Posts,
userpost => userpost.PostId, // from every UserPost get the foreign key to Post
post => post.Id, // from every Post, get the primary key
// parameter resultSelector: from every UserPost with matching Post make one new
(userPost, post) => new
{
Title = post.Title,
PublicationDate = post.PublicationDate,
...
}
}
Solution without normalized database
If you really can't convince your project leader that a proper database will prevent a lot of problems in the future, consider to create a SQL text that get the proper posts for you.
Your DbContext represents the current implementation of your database. It described the tables and the relations between the tables. Adding a method to fetch the Posts of a user seems to me a legit method for the DbContext.
My SQL is a bit rusty, you'll know way better than I how to do this in SQL. I guess you'll get the gist:
public IEnumerable<Post> GetPostsOfUser(int userId)
{
const string sqlText = "Select Id, ... from Posts where ..."
object[] parameters = new object[] {userId};
return this.Database.SqlQuery(sqlText, parameters);
}
Here's a possible solution if you can't normalize it:
var sql = "select PostId,UserIds from Post";
sql += $" outer apply string_split(UserIds,',') where value={targetId}";
DBContext.Posts.FromSqlRaw(sql).ToList();

access model class inside entity class of other model

I am using CakePHP 3.4+
I have written an application with multi level membership.
The Pro members will have benefit to view short url for external links which when shared will record the visit count to that url.
The original url is stored in PostVideos table for all user.
I have created a table to store uniuqe keys for short urls inside short_video_post_urls with columns
+----+---------------+------------+-------------+
| id | post_video_id | unique_key | visit_count |
+----+---------------+------------+-------------+
Since, count of Pro members will be low than normal users, I don't want to generate unique_key entry in short_video_post_urls because It will flood database with useless records.
So, what I want is to generate them dynamically and store them for PRO members only
Now, in template file I'm using $postVideo->video_url to display original video url from post_videos table.
Question
What I want is to tweak video_url entity call which will check for
Membership level of logged in user
If member is pro
check if unique key exists in ShortVideoPostUrls model for the url requested
If no record exists, then create a unique_key in ShortVideoPostUrls
return the new url with unique_key
But for that I need to access logged_in user data in the entity class.
What I tried?
class PostVideoLog extends Entity
{
/*
* ----
* ----
*/
protected function _getVideoUrl()
{
$user = $this->Users->get($this->Auth->user('id'), [
'contain' => [
'MembershipLevels'
]
]);
if ($user) {
if (strtolower($user->membership_level->title) === 'pro') {
/**
* check if unique_key exists for this request
*/
$checkShortUrl = $this->ShortVideoPostUrls->find()
->where(['post_video_log_id' => $this->_properties['id']])
->first();
if ($checkShortUrl) {
return $this->_generateUrl($checkShortUrl->unique_key);
}
/**
* create new record
*/
$unique_key_generator = new Hashids(UNIQUE_SHORT_URL_SALT, 4);
$unique_key = $unique_key_generator->encode($this->_properties['id']);
$newShortUrl = $this->ShortVideoPostUrls->newEntity();
$newShortUrl = $this->ShortVideoPostUrls->patchEntity($newShortUrl, [
'unique_key' => $unique_key,
'post_video_log_id' => $this->_properties['id']
]);
if ($this->ShortVideoPostUrls->save($newShortUrl)) {
return $this->_generateUrl($unique_key);
}
}
}
return $this->_properties['video_url'];
}
private function _generateUrl($unique_key)
{
$base_url = Router::url('/', true);
return $base_url . '/u/' . $unique_key;
}
}
I'm not sure, whether my approach is right or wrong.
To load Users model and other models I'm using in above function requires to use
$this->loadModel('Users');
But, loadModel seems not to be working here.
What is other approach to do this? Or how to load external model and Auth component in Entity class?
Edit 2
Is there any better alternative to do what I want without entity? or simply some way to call function from template on each entity?
Ex.
$postVideo->video_url()
To load other model inside entity You can use TableRegistry class
use Cake\ORM\TableRegistry;
class MyModel extends Entity {
protected function _getVideoUrl() {
$table = TableRegistry::get('Users');
$users = $table->find('all')->toArray();
}
}
I think its not the best idea to use Auth component in model - I dont know how to do this "properly", but all session data (including auth data) is still available in $_SESSION array

Eloquent relation with where condition on model attribute

I have two models Student and StudentRevision with Student model having hasMany relation with StudentRevision model. I have defined a hasMany relation in Student as
public function revisions()
{
return $this->hasMany(
'StudentRevision',
'sid'
);
}
I have a field in students table (Student model) which references current revision of student from student_revisions table.
The table structure is something like this.
students sid srid name ....
student_revisions srid sid batch ....
Now i want to define hasOne relation with StudentRevision model which references current revision linked with Student. Currently I have defined this relation as:
public function current()
{
return $this->hasOne(
'StudentRevision',
'sid'
)
->where('srid', $this->srid);
}
But the problem with this relation is, that $this->srid is not available during query building process and can be only there after the actual model is available.
Please help how to overcome this.
I don't think you can define it as relation. But what you can do is this:
public function current(){
return $this->revisions()->where('srid', $this->srid)->get();
}
This way you can access it by $student->current(). You can even go a bit further and make it more relationship like:
public function current(){
return $this->revisions()->where('srid', $this->srid);
}
public function getCurrent(){
return $this->current()->get();
}
protected $appends = array('current');
Here we define an accessor for our attribute. Laravel Docs (scroll down to the bottom)
We can then use it like this:
$student->current; // retrieves the model
$student->current(); // retrieves an instance of the query builder

cakePHP - model relations

I'm developing a system for a University here in Brazil. In this sytem I've got the following models:
- Country;
- State;
- City;
- University;
- Unit;
- Class;
Ok. and the relations are:
- State belongs to Country;
- City belongs to State;
- University belongs to Country (because, in my database, not every country has states registered to it and not every state has citys registered to it, so I could not demand from the user to register a University to a City);
- Unit belongsTo university;
- Class belongs to University (because not every University has Units registered to it);
Ok. I guess now I've set a good background for you guys to understand my situation. Now onto the question itself:
In the Views of my Model Class I wanna display the Country and (IF there are State City). And I may have the need to display such content in other Model views such as Unit and University.
But when I try to do that in my Class model I can only display the country_id. The foreign key in my university database table. And why is that, that is because my Class model belongs to University, so it's pretty easy to access my University's properties. However I do not wish to access the id of the Country, I want it's Name. And maybe in the future I might want other properties, who knows?
HOW DO I DO THAT? How do I access the properties of the Model Country when my Model Class has NO direct relation to it?
many thx hugs and kisses. Hope someone can help me with this one.
p.S:
I've managed a kinda loser solution: In my Class Controller I've done the following (so I can access variables of the Models Country, State and City):
In the View function I've loaded the Models for State and City (the country Model I can access by the relation Class->University->Country);
Then I used a find method to find the respective country, state and city for the Class in question I wanna display in view.ctp. The code follows:
public function view($id = null) {
$this->Class->id = $id;
$this->set('class', $this->Class->read());
$this->loadModel('State');
$this->loadModel('City');
$country = $this->Class->Universsity->Country->find('first', array('conditions' => array('Country.id' => $this->Class->data['University']['country_id']),));
$state = $this->State->find('first', array('conditions' => array('State.id' => $this->Class->data['University']['state_id']),));
$city = $this->City->find('first', array('conditions' => array('City.id' => $this->Class->data['University']['city_id']),));
$this->set('country',$country['Country']);$this->set('city',$city);
$this->set('state',$state);
}
And it kinda works...In my Wiew.ctp I get an array for Country data, and array for State data and an array for City data. And in the view I check to see if the state and city arrays are not length = 0 to display them and all...but I think there HAS to be a better way to do This.
P.P.S:
The other question is...what about the Index.ctp? How will I do that if I do not know which Class I'm working with? Will I have to have logic and find methods in the View? Isin't that just messing up the code?
Without posting your code, it's difficult to point you in the right direction. However, working on the assumption that you have your models configured correctly you should be able to do the following:
ClassController.php
public class ClassController extends AppController{
public $uses = array('Class');
public function view($id){
$class = $this->Class->find('first', array('conditions' => array('Class.id' => $id));
$this->set('class', $class);
}
}
view.ctp
<?php echo $class['Country']['name']?>
You may find however you will have to configure the recursive parameter of your class model so that CakePHP retrieves all the associated data. This can be done one of two ways:
Configure the recursive parameter within your Class model (see the documentation on how to do this). This sets the default value for recursive whenever you retrieve records from the database and you don't specify the attribute in your options.
Set the recurisve parameter within your find(...) method calls. I favor this one because it helps avoid placing a heavy load on the database when you don't need the data it retrieves.
For example, in your controller you could do the following:
public function view($id){
$class = $this->Class->find('first', array('conditions' => array('Class.id' => $id), 'recursive' => 3));
$this->set('class', $class);
}
Just a warning, don't set recursive to a value higher than you need, otherwise you may retrieve stuff that you don't particularly require. Use the debug() function to inspect what you're model is currently retrieving so you can make more of an informed decision:
debug($class);
I hope this is of some help!

CakePHP: To Create A New Controller

I'm using CakePHP 2.0.5 (but this isn't necessarily a cakephp specific question). I have a Coupon and a User model. Each time a user prints a coupon (proccessed by: Coupon Controller):
class CouponsController extends AppController {
public function printcoupon($id = null) {
// code
}
}
I want to save the information to a "coupons_printed" table (id/coupon_id/user_id/created). Should I create a new model for this, or should I just create a function inside of the Coupon model similar to (and call it in the controller each time that page is viewed)?:
class Coupon extends AppModel {
function insertIntoPrinted($id) {
$this->query("UPDATE coupons_printed SET .....");
}
}
Whatever you do, a raw SQL query is not the best way to go. Always use CakePHP methods if at all possible (and almost always it is possible).
You should put the insertIntoPrinted() function in the CouponsPrinted model (although, as a side note, PrintedCoupon would be a more natural way to name the model...) You can then add a HasMany relationship to the Coupon model ($hasMany = array( 'CouponsPrinted' )) and call the function in the CouponsController:
public function printcoupon($id = null) {
$this->Coupon->CouponsPrinted->insertIntoPrinted( $id );
}
CakePHP's model has a thing call association.
In your case, Coupon has a hasMany association with coupons_printed.
You can create a new model, or query using the association in the Coupon model, the generated queries will be the same, I believe.
Your CouponsController already depend on Coupon Model, so not creating another model is a better solution.

Resources