Yii2 photo gallery Photo->Likes relation - php

So basically, I have a photo application and the relation between photos and likes is hasMany(). How can I make the relation to be ordered by count(number_of_likes) for each photo?
TABLE `Likes` (
`id_lk` int(11) NOT NULL AUTO_INCREMENT,
`idusr_lk` int(11) NOT NULL,
`idpht_lk` int(11) NOT NULL,
PRIMARY KEY (`id_lk`),
KEY `idusr_lk` (`idusr_lk`),
KEY `idpht_lk` (`idpht_lk`),
CONSTRAINT `Likes_ibfk_1` FOREIGN KEY (`idusr_lk`) REFERENCES `users_usr` (`id_usr`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `Likes_ibfk_2` FOREIGN KEY (`idpht_lk`) REFERENCES `photos_pht` (`id_pht`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=latin1
the php code of the relations:
Photo model
public function getLikes()
{
return $this->hasMany(Likes::className(), ['idpht_lk' => 'id_pht']);
}
Likes model
public function getPhoto()
{
return $this->hasOne(Photo::className(), ['id_pht' => 'idpht_lk']);
}
I know that you can add an orderBy clause after the relations, but I just simply don't know if i am allowed to write an SQL query there and if I am, how am I supposed to write it?

Well what you require is that you don't have to build the query manually every time and still able to call the result set which has
All photos along with the number of likes they have using the existing relation getLikes().
They should be ordered according to the number of likes they have.
Well what i suggest wont just use Photo::find()->all() to do the job but yes if you are ok with doing Photo::find()->byLikes()->all() then you can use the following approach
Create ActiveQuery Class PhotoQuery for the Photo model.
Override the find() method inside your Photo Model to use the newly generate/created PhotoQuery class.
You can use Gii to generate the default PhotoQuery class or you can create manually like the below one.
Adjust the namespaces accordingly.
<?php
namespace app\models;
/**
* This is the ActiveQuery class for [[Photo]].
*
* #see Photo
*/
class PhotoQuery extends \yii\db\ActiveQuery
{
/**
* {#inheritdoc}
* #return Photo[]|array
*/
public function all($db = null)
{
return parent::all($db);
}
/**
* {#inheritdoc}
* #return Photo|array|null
*/
public function one($db = null)
{
return parent::one($db);
}
}
Now what you need to do is to add a new method in the PhotoQuery with the name byLikes()
/**
* #return mixed
*/
public function byLikes()
{
return $this->alias('p')
->select(['p.*', new \yii\db\Expression('count(l.idpht_lk) as likeCount')])
->joinWith(['likes l'])
->groupBy('p.id_pht')
->orderBy('likeCount desc');
}
and then add the following method in your Photo model to use PhotoQuery class instance.
/**
* {#inheritdoc}
* #return PhotoQuery the active query used by this AR class.
*/
public static function find()
{
return new PhotoQuery(get_called_class());
}
Now you can call the query like Photo::find()->byLikes()->all() and it will return the results ordered by total number of likes along with the likes count, where at the same time you can still call Photo::find()->all() to get only the photo specific result set if you want to somewhere in the future.
Hope it helps.

In your table Likes you have to add a field where you can store the number of likes for each photo, for example: number_of_likes.
Then you can run a query like this to get the number of like for each photo on descending order:
SELECT idusr_lk, idpht_lk, number_of_likes
FROM photos_pht
INNER JOIN Likes
ON photos_pht.id_pht = Likes.idpht_lk
ORDER BY Likes.number_of_likes DESC

Related

Eloquent hasMany Not Returning Any Results

I have a noob question about how Eloquent generates SQL for the following.
DB:
Customer Table:
ID (Public Key)
... (Some general columns)
Group Table:
ID (Public Key)
FK_CUSTOMER_ID (Foreign Key)
... (Some general columns)
I have the following code in Customer Model:
public function groups()
{
return $this->hasMany(Group::class, 'fk_customer_id');
}
I am trying to get all groups (in the groups function above), that I can narrow down the groups later, to a particular customer.
The above code generates the following SQL (which results an empty result set, which is understandable by looking at the SQL). I have no idea, why the where clause (see the SQL below) gets generated, does not makes much sense.
select * from `group` where `group`.`fk_customer_id` is null and `group`.`fk_customer_id` is not null limit 1
I would like the following SQL to be generated :
select * from `group`
also, how to get the following SQL generated (If I need to select groups based on customer_id, I believe I'll need to add some where clause somehow)
select * from `group` where `group`.`fk_customer_id`= SOME_VALUE
Thanks!
--- Customer Model
<?php
namespace App;
use App\Role;
use Illuminate\Support\Facades\Log;
use Illuminate\Database\Eloquent\Model;
class Customer extends Model
{
/**
* Get ALL groups for the customer.
*/
public function groups()
{
return $this->hasMany(Group::class, 'fk_customer_id');
}
/**
* Get ONE group for the customer.
*/
public function group($groupId)
{
return $this->hasMany(Group::class, 'fk_customer_id')->where('id', $groupId);
}
/**
* The table associated with the model.
*
* #var string
*/
protected $table = 'customer';
}
Group Model
<?php
namespace App;
use App\Group;
use App\Customer;
use Illuminate\Database\Eloquent\Model;
class Group extends Model
{
/**
* Get ONE customer for the group.
*/
public function customer()
{
return $this->belongsTo(Customer::class, 'fk_customer_id');
}
/**
* Get ONE directory configuration for the group.
*/
public function directoryConfiguration()
{
return $this->belongsTo(DirectoryConfiguration::class, 'fk_directory_configuration_id');
}
/**
* Get ONE user for the group.
*/
public function user($userId)
{
return $this->hasMany(User::class, 'fk_role_id')->where('user_id', $userId);
}
/**
* Get ALL user for the group.
*/
public function users()
{
return $this->hasMany(User::class, 'fk_role_id');
}
/**
* The table associated with the model.
*
* #var string
*/
protected $table = 'group';
}

Nested comments with Laravel

I'm currently trying to replicate something similar to a forum, but I'm stumped on how I could create nested comments. I understand that for regular replies I could create a replies table and run a loop for each comment that matches the thread id. But I don't know how I would easily do this for nested replies.
Could someone please give me some advice or point me in the right direction? Thanks.
This is the structure for my posts table:
Screenshot of phpMyAdmin http://bitdrops.co/drops/J5v.png
You want to look into polymorphic relations to solve this. You want to be able to comment on Posts and Comments.
What I have done is set up a commentable trait and have the models I want to add comments to use it. This way if you ever want to comment on another model, you can just add the trait to that model.
Laracasts is a great resource for laravel and has a good lesson on traits.
There is a bit more to it than this, but hopefully it will get you started.
You set up your database structure like this.
User Table
`id` int(10),
`name` varchar(255),
`username` varchar(255)
Comments table
`id` int(10),
`user_id` int(10),
`body` text,
`commentable_id` int(10),
`commentable_type` varchar(255)
Posts Table
`id` int(10),
`user_id` int(10),
`body` text
Your models like this.
Comment Model
<?php namespace App;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model {
use CommentableTrait;
/**
* Get all of the owning commentable models.
*/
public function commentable()
{
return $this->morphTo();
}
public function user()
{
return $this->belongsTo('App\User');
}
}
Post Model
<?php namespace App;
use CommentableTrait;
use Illuminate\Database\Eloquent\Model;
class Post extends Model {
use CommentableTrait;
}
and of course you need the trait.
Trait
<?php namespace App;
use Comment;
trait CommentableTrait {
/**
* List of users who have favorited this item
* #return \Illuminate\Database\Eloquent\Relations\MorphToMany
*/
public function comments()
{
return $this->morphMany('App\Comments\Comment', 'commentable')->latest();
}
/**
* #return \Illuminate\Http\RedirectResponse
*/
public function addComment($body, $user_id)
{
$comment = new Comment();
$comment->body = $body;
$comment->user_id = $user_id;
$this->comments()->save($comment);
return $comment;
}
}

One-To-Many relationship deletes foreign key in Doctrine

This is driving me crazy.
A Client can have many Vehicles.
This is a one to many relationship. When trying to save the entities I get an error saying that the foreign key is null. When I remove the Doctrine relation and store the Vehicle separately everything is working fine.
This is how I created the relation:
class Vehicle {
...
/**
* #ORM\ManyToOne(targetEntity="Client", inversedBy="vehicles")
* #ORM\JoinColumn(name="client_id", referencedColumnName="id")
*/
public $client;
}
class Client {
...
public function __construct()
{
parent::__construct();
$this->vehicles = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* #ORM\OneToMany(targetEntity="Vehicle", mappedBy="client", cascade={"persist"})
*/
private $vehicles;
}
I try to save the entities like this:
$client = new Client();
$vehicle = new Vehicle();
$client->getVehicles()->add($vehicle);
$em->persist($client);
$em->flush();
Next I get a PDO exception saying that client_id can't be null on the Vehicle table.
It seems like Doctrine is not copying the foreign key correctly.
What am I doing wrong?
According to their docs:
It is not possible to use join columns pointing to non-primary keys.
Doctrine will think these are the primary keys and create lazy-loading proxies with the data, which can lead to unexpected results. Doctrine can for performance reasons not validate the correctness of this settings at runtime but only through the Validate Schema command.
Obviously you need a different approach.
A solution is given in their example:
CREATE TABLE product (
id INTEGER,
name VARCHAR,
PRIMARY KEY(id)
);
CREATE TABLE product_attributes (
product_id INTEGER,
attribute_name VARCHAR,
attribute_value VARCHAR,
PRIMARY KEY (product_id, attribute_name)
);
This schema should be mapped to a Product Entity as follows:
class Product
{
private $id;
private $name;
private $attributes = array();
}
Where the attribute_name column contains the key and attribute_value contains the value of each array element in $attributes.

Laravel: How should I update this relationship?

Suppose I have the following tables:
User:
-userID
-userName
...
Exercises:
-exerciseID
...
User model:
<?php
use Illuminate\Auth\UserInterface;
use Illuminate\Auth\Reminders\RemindableInterface;
class User extends Eloquent implements UserInterface, RemindableInterface {
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'users';
protected $primaryKey = 'userID';
...
public function hasPassedExercises() {
return $this->hasMany('Exercise', 'exerciseID');
}
}
I want to say that a User has many completedExercises, so when the user completes an exercise, I update the model like so:
Route::post('dbm/userPassedExercise', function () {
$user = User::with('hasPassedExercises')->find($_POST['userID']);
$exercise = Exercise::find($_POST['exerciseID']);
$user->hasPassedExercises->save($exercise);
});
However, this has no effect on any underlying table, as far as I have understood. I'm trying to make sense of the documentation and see how it applies to my problem. So my question is what is the right course of action to do here.
Should I create a table users_completed_exercises that has userID and exerciseID as foreign keys, and if so, how do I link them to my user when I do the update? Or is there a more elegant solution?
Indeed, you have to use a relationship table (called pivot table).
In the laravel documentation, you have to name your pivot table with your tables name ordered by their name (you have not to, but it's prefered).
We'll take your naming convention so : users_completed_exercises
So here we shoud have this :
users:
- userId // Unsigned Int PRIMARY KEY AUTO_INCREMENT
Exercises:
- exerciseId // Unsigned Int PRIMARY KEY AUTO_INCREMENT
users_completed_exercises:
- id // Unsigned Int PRIMARY KEY AUTO_INCREMENT
- exerciseId // Unsigned Int FOREIGN KEY REFERECES EXERCICES ON ID
- userId // Unsigned Int FOREIGN KEY REFERECES USERS ON ID
On the user model, you should have :
public function passedExercises()
{
// Alphabetical order of your id's are here, very important because laravel
// retreives the good ID with your table name.
return $this->belongsToMany('Exercise', 'users_completed_exercises', 'exerciseId', 'userId');
}
And the inverse on Excercise Model
public function usersWhoPassed()
{
// Alphabetical order of your id's are here, very important because laravel
// retreives the good ID with your table name.
return $this->belongsToMany('User', 'users_completed_exercises', 'exerciseId', 'userId');
}
Retreiving infos are now, so easy.
Route::post('dbm/userPassedExercise', function () {
// Don't use $_POST with laravel, they are exceptions indeed, but avoid as much as
// possible.
$user = User::find(Input::get('userId'));
$exercise = Exercise::find(Input::get('exerciseId'));
// Very important, use () on relationships only if you want to continue the query
// Without () you will get an Exercises Collection. Use ->get() or ->first() to end
// the query and get the result(s)
$exercise->usersWhoPassed()->save($user);
});
You can easly check if user has passed an exercise too
Route::get('/exercises/{id}/passed_users', function($id)
{
$exercise = Exercise::find($id);
if ($exercise->usersWhoPassed()
->where('userId', '=', Input::get('userId'))->count()) {
return 'User has passed';
}
return 'User has failed';
});

Doctrine nullable one-to-one relationship still wants to create unique index

I have a Person entity which has two relations (hometown and current) to Location table. Both of these fields can be null, otherwise they must exist in the Location table:
class Person {
.....
/**
* #var Location
* #ORM\OneToOne(targetEntity="Location")
* #ORM\JoinColumn(name="hometown_id", referencedColumnName="id",nullable=true)
**/
protected $hometown;
/**
* #var Location
* #ORM\OneToOne(targetEntity="Location")
* #ORM\JoinColumn(name="current_id", referencedColumnName="id", nullable=true)
**/
protected $current;
....
}
Now, I want to update my db schema, based on doctrine:schema:update --dump-sql output, but it creates problems:
CREATE UNIQUE INDEX UNIQ_8D93D6494341EE7D ON person (hometown_id);
CREATE UNIQUE INDEX UNIQ_8D93D649B8998A57 ON person (current_id);
I cannot define these indexes as there are more than one null row in the table.
Would you please help me?
A OneToOne relationship is unique as it would mean that only one person could be assigned to one location and one location to one person.
In your scenario you would want one person to have multiple locations and one location could have multiple person(s). This would be a ManyToMany relationship.
In Doctrine when you use a ManyToMany you will specify a JoinTable that Doctrine will manage (You don't have to create an entity for a JoinTable). The JoinTable breaks down the ManyToMany to something like a OneToMany such as one person to many location(s) as shown in example below. The JoinTable will store the values you want when they apply.
/**
* #ORM\ManyToMany(targetEntity="Location")
* #ORM\JoinTable(name="hometown_location",
* joinColumns={#ORM\JoinColumn(name="person_id", referencedColumnName="id", unique=true)},
* inverseJoinColumns={#ORM\JoinColumn(name="location_id", referencedColumnName="id")}
* )
**/
protected $hometown;
/**
* #ORM\ManyToMany(targetEntity="Location")
* #ORM\JoinTable(name="current_location",
* joinColumns={#ORM\JoinColumn(name="person_id", referencedColumnName="id", unique=true)},
* inverseJoinColumns={#ORM\JoinColumn(name="location_id", referencedColumnName="id")}
* )
**/
protected $current;
public function __construct() {
$this->hometown = new \Doctrine\Common\Collections\ArrayCollection();
$this->hometown = new \Doctrine\Common\Collections\ArrayCollection();
}
If there is no location to assign to hometown or current that is fine, no space is taken up.
When you do have a location to assign to either hometown or current it will have to be a valid location from the location table.
It looks like you are looking for FOREIGN KEY
http://dev.mysql.com/doc/refman/5.6/en/create-table-foreign-keys.html
ALTER TABLE `Person` ADD INDEX ( `hometown_id` ) ;
ALTER TABLE `Person` ADD FOREIGN KEY ( `hometown_id` ) REFERENCES `Location` ( `id` )
ON DELETE RESTRICT ON UPDATE RESTRICT ;
ALTER TABLE `Person` ADD INDEX ( `current_id` ) ;
ALTER TABLE `Person` ADD FOREIGN KEY ( `current_id` ) REFERENCES `Location` ( `id` )
ON DELETE RESTRICT ON UPDATE RESTRICT ;

Categories