How to Reduce Code Duplication in Symfony2 - php

I have 3 "main" entities : TypeA and TypeB linked to User by a ManyToOne relation.
I have 2 "secondary" entities : UserTypeA and UserTypeB, which contain the attributes of the ManyToOne relations (for example, the comment a user has assigned to a product of type A). These two entities and their repository are similar (except that one is linked to TypeA and the other to TypeB).
Here is a part of my code :
public function typea_commentAction(TypeA $typea)
{
$user = $this->getUser();
$userTypeA = $this->getDoctrine()
->getManager()
->getRepository('GamUserBundle:UserTypeA')
->getComment($user, $typea);
//...
}
public function typeb_commentAction(TypeB $typeb)
{
$user = $this->getUser();
$userTypeB = $this->getDoctrine()
->getManager()
->getRepository('GamUserBundle:UserTypeB')
->getComment($user, $typeb);
//...
}
As you can see, I need to duplicate each action to make them work with each entity. Is there any way to combine these actions ? Same question about the secondary entities and their repositories.
Thanks.

Create a service class that performs the logic and takes say, the user type as a parameter.

Related

How To use Eloquent in From One Model to another model through intermediate table

i have three models
Article
id
title
Comment
id
title
user_id
article_id
User
id
name
what i wanna achieve is to select one article based on its id with comments and user info that made that comment
like that :
$article = Article::find($id -- say 1)->with('comments' -- this is a relation in Article Model)->get();
this gives me article with related comments as an array of objects say comment one - comment two etc ....
what i want instead of user_id in comment object i wanna it to be a user object
see this pic thats what i reached so far
using laravel 5.4
You can use following:
$articles = Article::find($id)->with('comments', 'comments.user')->get();
Here 'user' is the relationship you mentioned in the comments model for User.
If you have defined the foreign key relationship in Schemas, you can define functions for Eloquent Relationship as defined in following reference link -
Laravel - Eloquent Relationships.
You can define functions in models as follows -
Article -
class Article extends Model
{
...
public function comments(){
// Accessing comments posted to that article.
return $this->hasMany(\App\Comment::class);
}
// Create a foreign key to refer the user who created the article. I've referred it here as 'created_by'. That would keep relationship circle complete. You may ignore it if you want.
public define user(){
// Accessing user who posted the article
return $this->hasOne(\App\User::class, 'id', 'created_by');
}
}
Comment -
class Comment extends Model
{
...
public function article(){
// Accessing article to which the particular comment was posted
return $this->hasOne(\App\Article::class, 'id', 'article_id');
}
public function user(){
// Accessing user who posted the comment
return $this->hasOne(\App\User::class, 'id', 'user_id');
}
}
User -
class User extends Models
{
...
public function articles(){
// Accessing articles posted by a user
return $this->hasMany(\App\Article::class);
}
public function comments(){
// Accessing comments posted by a user
return $this->hasMany(\App\Comment::class);
}
}
Now you can use like following -
$article = Article::findOrFail($id);
$comments = $article->comments;
$article_user = $article->user;
$comment_user = Comment::findOrFail($commnet_id)->user;
$users_comments = User::findOrFail($user_id)->comments;
$users_articles = User::findOrFail($user_id)->articles;
and so on...
It is far better to use ->find() at last instead of ->get() because get() returns a Collection.
This way you will get a single object which you want instead of a Collection.
For example:
$commentableObj = Post::with(['comments'])
->withCount(['comments'])
->findOrFail($commentable->id);

Laravel insert relationship in newly created model

I have a relationship set up between two models. The two models are set up like:
app/Character.php
public function characteristics() {
return $this->hasMany('App\Characteristics');
}
app/Characteristics.php
public function character() {
return $this->belongsTo('App\Character');
}
then in a controller I have a method to create a new character with a predetermined set of characteristics as follows:
app/Http/Controllers/CharacterController.php
public function newCharacter(Request $request) {
$character = new Character();
$characteristics = getCharacteristics($character->id);
// Save basic character stuff
$character->characteristics()->saveMany($characteristics);
$character->save();
}
The highlighted line is throwing an error because saveMany is not a function of Builder so how can I get the created character without having to do a find that would have to hit the database again?
I think you need to save the character model first, because if you're building a hasMany/belongsTo relationship your characteristics table must have a column for character_id, and when you do $character->characteristics()->saveMany($characteristics); Laravel will try to insert the ID from the parent model. And as per the code shared by you, you've just instantiated the model, by this point of time it doesn't have an ID associated with it. So you need to save the character model first and assuming the getCharacteristics() method is returning an array/collection of Characteristics Models, Following should work:
public function newCharacter(Request $request) {
$character = new Character();
$character->save();
$characteristics = getCharacteristics();
// Save basic character stuff
$character->characteristics()->saveMany($characteristics);
}
And to further clarify this for you, from the characteristics method in your Character model an instance of HasMany not a Builder is being returned, thus saveMany works here.

Eager load extra model attributes

I am using Eloquent with Laravel.
The case: I'm building an API where there is possibility to include relations for a Resource. So for example /api/teams?include=users will add the User model for every Team. For the logic that includes the relationship I'm using Fractal. So I need to have some logic that determines which relationship has to be included, so I can create a optimized query for it.
Problem: When I want to render a collection of a Team with the related User models. I can eager-load the models just fine. The problems comes when I have custom attributes on the User model. These will cause a N+1 query problem because for every eager-loaded team, because the query for the custom attributes will be executed for every model.
Example code:
// The Team model with the custom attribute
class Team extends Model {
protected $appends = ['is_member'];
public function getIsMemberAttribute() {
$loggedUser = Auth::currentUser();
$result = DB::table('team_user')
->where('team_id', $this-id)
->where('user_id', $loggedUser->id)
->get();
return !is_null($result);
}
}
// The controller code
$team = Team::findOrFail($teamId);
// So this will return all the User models that belong to the Team.
// The problem is this will execute the query inside the getIsMemberAttribute() for every User model.
dd($team->users);
Is there a good pattern to solve this issue?
You could iterate through the User models and see if one of them matches the logged in user. It's more efficient than looking it up in the database.
class Team extends Model {
protected $appends = ['is_member'];
public function getIsMemberAttribute() {
$loggedUser = Auth::currentUser();
foreach ($this->users as $user) {
if ($user->id == $loggedUser->id) {
return true;
}
}
return false;
}
}

Check for a many to many relation when listing a resource

I'm implementing relationships in Eloquent, and I'm facing the following problem:
An article can have many followers (users), and a user can follow many articles (by follow I mean, the users get notifications when a followed article is updated).
Defining such a relationship is easy:
class User extends Eloquent {
public function followedArticles()
{
return $this->belongsToMany('Article', 'article_followers');
}
}
also
class Article extends Eloquent {
public function followers()
{
return $this->belongsToMany('User', 'article_followers');
}
}
Now, when listing articles I want to show an extra information about each article: if the current user is or is not following it.
So for each article I would have:
article_id
title
content
etc.
is_following (extra field)
What I am doing now is this:
$articles = Article::with(array(
'followers' => function($query) use ($userId) {
$query->where('article_followers.user_id', '=', $userId);
}
)
);
This way I have an extra field for each article: 'followers` containing an array with a single user, if the user is following the article, or an empty array if he is not following it.
In my controller I can process this data to have the form I want, but I feel this kind of a hack.
I would love to have a simple is_following field with a boolean (whether the user following the article).
Is there a simple way of doing this?
One way of doing this would be to create an accessor for the custom field:
class Article extends Eloquent {
protected $appends = array('is_following');
public function followers()
{
return $this->belongsToMany('User', 'article_followers');
}
public function getIsFollowingAttribute() {
// Insert code here to determine if the
// current instance is related to the current user
}
}
What this will do is create a new field named 'is_following' which will automatically be added to the returned json object or model.
The code to determine whether or not the currently logged in user is following the article would depend upon your application.
Something like this should work:
return $this->followers()->contains($user->id);

Repository Methods From Entity In ArrayCollection

So, we have two entities. One with repository and another is not. When we trying to get the data from another table we will get the ArrayCollection data. Question is how to call this entity repository methods? Is it real?
Example:
$system = $this
->getDoctrine()
->getEntityManager()
->getRepository('SomeBundle:FirstEntity')
->findOneByColumnID($id);
$den = $system->getDataFromSecondTable(); // ArrayCollection of SecondEntity
And then i want to use some kind of:
$den[0]->functionFromSecondEntityRepository();
So, method "functionFromSecondEntityRepository" is in Repository of class SecondEntity and i can't call it - error on undefined method call "functionFromSecondEntityRepository".
So how can i do it in right way?
You didnt provide too many details so I will make some example up here.
Let's say you have an Entity FriendsList and a One-to-Many relationship with Entity Friend.
$List = $this->getDoctrine()
->getEntityManager()
->getRepository('SomeBundle:FriendsList')
->find($id);
// The list you pulled in by ID can now be used
$List->getId();
foreach($List->getFriends() as $Friend)
{
// Each friend will be output here, you have access
// to the Friend methods now for each.
$Friend->getId();
$Friend->getFirstName();
$Friend->getLastName();
$Friend->getDOB();
$Friend->getFavoriteColor();
}
By default when you create relationships a method to acquire the collection is created, in this example getFriends which returns an array of Entities. After you generate the entities look at your Entity Model to see which methods are available. By default one is created for each property in your entity and additional ones for Collections.
SomeCool/Bundle/Entity/FriendsList
Somecool/Bundle/Entity/Friend
The following is what a one-to-many relationship would look like if you use YAML configuration.
SomeCool\Bundle\Entity\FriendsList:
type: entity
table: null
oneToMany:
friend:
targetEntity: Friend
mappedBy: friendslist
cascade: ["persist"]
SomeCool/Bundle/Entity/Friend
manytoOne:
friends:
targetEntity: FriendsList
mappedBy: friend
cascade: ["persist"]
Accessing a Repository
YAML Configuration (services.yml)
somebundle.bundle.model.friends:
class: SomeBundle/Bundle/Model/Friends
arguments: [#doctrine.orm.entity_manager]
On the Controller
$friendsModel = $this->get('somebundle.bundle.model.friends');
$Friends = $friendsModel->findByFirstName('Bobby');
foreach($Friends as $Friend)
{
$Friend->getLastName();
}
Repository methods are not available in Entities. You would need a function in your AnotherEntity to grab the ArrayCollection. IE:
class FirstEntity {
public function getAnotherEntity()
{
return $this->anotherEntity;
}
}
class AnotherEntity
{
public function getArrayCollection()
{
return $this->myArrayCollection;
}
}
$firstEntity->getAnotherEntity()->getArrayCollection();
Another option would be to get the AnotherEntity's repository based on results from first:
$system = $this
->getDoctrine()
->getEntityManager()
->getRepository('SomeBundle:SomeEntity')
->findOneByColumnID($id);
$anotherEntity = $system->getAnotherEntity();
$anotherEntityResult = $this->getDoctrine()
->getRepository(get_class($anotherEntity))
->functionFromAnotherEntityRepository($anotherEntity->getId());
If using the second solution, I'd make sure that $anotherEntity is not null before attempting to retrieve the repository.

Categories