I'm starting to use Doctrine 2 in a project with a "Group" entity that can inherit from another Group, having the following schema: id | parent_id | name
Because the hierarchy can go deep, I use a linking table, "group_group", using this schema: ancestor_id | descendant_id | depth
The idea is that any group is linked to all of its ancestors and descendants, and the depth field indicates the distance of the relationship, so that I don't have to iterates through parents or children using many SQL requests, a single one can get all the results.
I tried to use Doctrine's ManyToMany relation but I could not get it to be ordered by the depth field, so instead I use the entity's repository to get the related ancestors and descendants.
Because an entity can not access its repository, I would like to know if there is a way for an entity to dispatch events that can be listened by its repository, so that when an entity tries to access its ancestors/descendants, the repository can respond?
Thanks for your help.
An Entity shouldn't have a concrete reference to a Repository but there isn't anything wrong with defining an Interface and have your Repository implement this interface and injecting it into the Entity.
Similar to this solution Doctine 2 Restricting Associations with DQL
interface TreeInterface
{
public function findParent();
public function findChildren();
}
Then your Entities.
class Group
{
$repository;
setRepository(TreeInterface $repository)
{
$this->tree = $repository;
}
public function getParent()
{
return $this->repository->findParent($this);
}
public function getChildren()
{
return $this->repository->findChildren($this);
}
}
class GroupRepository extends EntityRepository implements TreeInterface
{
public function findParent(Group $group)
{
return //stuff
}
public function findChildren(Group $group)
{
return //stuff
}
}
And you use it this way.
$group = $em->find('Group', 1);
$group->setRepository($em->getRepository('Group'));
$children = $group->getChildren();
To avoid setting the repository each time you get a child, I would take a look at the EventManager and postLoad event and see if you can inject a TreeInterface into the Entity on load.
Related
I'm working with a situation where people and employers are related through licenses. So my models look something like this:
class Employer extends \Illuminate\Database\Eloquent\Model
{
public function people()
{
return $this->belongsToMany(Person::class, 'licenses');
}
// &c
}
class Person extends \Illuminate\Database\Eloquent\Model
{
public function employer()
{
return $this->belongsToMany(Employer::class, 'licenses');
}
// &c
}
However, person-employer pairs can have multiple licenses. So the aforementioned relationship returns duplicate Person entities, namely one Person entity for every License entity associated with the employer.
How do I fix this issue so that these relationships only return unique entities?
You can try laravel $collection->unique('licence_number')
so whatever eloquent returns you in collection just use unique with attribute
on the basis of you want to make the result unique like
licence_number is in the collection
[ { licence_number:1, name:'test' }, { licence_number:2, name:'jhon' } ]
I am trying to update/delete/create in belongsTo relations.
Company has many sports
sports is belonging to Company
Here is two models.
class CompanySports
{
public function company()
{
return $this->belongsTo(Company::class, "company_id","id");
}
class Company
public function sports()
{
return $this->hasMany(CompanySports::class,"company_id","id");
}
}
at controller, when sports is added or modified or remove, what is the best practice to update?
i know that many to many, sync can be used. In this, what is the best solution? Should i compare everytime after loading all from database which is not good practice i believe.
From your code, I would first recommend putting your models in separate files, and ensuring they are singular. If you use the artisan make:model command to generate the stubs, it should do this for you.
// app/CompanySport.php // <-- NOTE singular
class CompanySport // <-- NOTE singular
{
public function company()
{
return $this->belongsTo(Company::class, "company_id","id");
}
}
// app/Company.php
class Company {
public function sports()
{
return $this->hasMany(CompanySport::class,"company_id","id"); // singular
}
}
From there, I find it helpful to build helper methods in the various classes so that the grammar sounds natural and more importantly, belongs to the model. For example:
// app/Company.php
class Company
{
...
public function addSport(CompanySport $sport)
{
$this->sports()->save($sport);
}
public function removeSport(CompanySport $sport)
{
$this->sports()->find($sport->id)->delete();
}
}
These helper functions can then be easily called from anywhere, e.g. controller:
// CompanySportsController.php
public function store(Company $company, CompanySport $sport)
{
$company->addSport($sport);
return redirect('/company/' . $company->id);
}
If you are using these helpers, there is no comparing or sync to be done since you are only using a one to many relationship. Eloquent does everything for you.
Also, I've found this cheatsheet particularly helpful when building out the initial relationships and scaffolding of a new app.
While adding new record of Company Model, you need not to do anything as there is no child for it yet.
While updating an instance of a Company model, again you need not to update anything on its children. As relationship are based on id(primary key) which I believe you don't change while updating.
And now for deleting there are some questions. Do you want to delete the children when the parent is deleting? If so, you can use ON DELETE CASCADE which you can set up in migration like
$table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade');
in your spors table.
Well you can make your own function too like answered in here
Well if you don't want to delete the children, you can use softdelete on your Model. set up the relations then like
CompanySports
public function company()
{
return $this->belongsTo(Company::class, "company_id","id")->withTrashed();
}
This way you can get the parent of a children without any error though the parent is deleted.
I have 4 tables,
props, listing, offers, contact
props has many listing, listing belongs to props
public function listings()
{
return $this->hasMany('App\Models\Listing\Listing');
}
offer belongs to listing,
public function property()
{
return $this->belongsTo('App\Models\Property\Property')->with('owners');
}
then
offer belongsToMany contact trough offer_contact table
public function buyers()
{
return $this->belongsToMany(Contact::class, 'offer_contact', 'offer_id', 'contact_id')->with('primary_email');
}
My question is, how to access buyers()?
Something like $props->buyers()
In props model, what I did is
return $this->hasManyThrough('App\Models\Offer\Offer', 'App\Models\Listing\Listing');
You cannot. You may use nested iterations to get properties, listings belongs to each property, offers belongs to each listing and then customers belonging with the offer.
Alternatively, you may use the raw query to get the desired result using DB::statement();
I created a HasManyThrough relationship with unlimited levels: Repository on GitHub
After the installation, you can use it like this:
class Property extends Model {
use \Staudenmeir\EloquentHasManyDeep\HasRelationships;
public function buyers() {
return $this->hasManyDeep(Contact::class, [Listing::class, Offer::class, 'offer_contact']);
}
}
I have a relation that can be inherited from a parent if not set for the object itself.
For an example setup let's say we have events that have a venue.
class Event extends Model
{
public function venue()
{
return $this->belongsTo('App\Venue');
}
public function activities()
{
return $this->hasMany('App\Activity');
}
}
And there are activities in the events that mostly take place in the same venue, but sometimes could be elsewhere while still belonging to the same event.
class Activity extends Model
{
public function event()
{
return $this->belongsTo('App\Event');
}
public function venue()
{
if ($this->venue_id)
return $this->belongsTo('App\Venue');
return $this->event->venue();
}
}
If I simply request activities for an event and work with them it is fine. But if I try to eager load the venues for activities, I only get the ones that are set directly on the activity, never requesting one from parent.
$activities = $event->activities;
$activities->load('venue'); // Works correctly without this line
foreach ($activities as $activity)
if ($activity->venue) // Doesn't take venue from the parent (event)
echo $activity->venue->name; //Only shows if venue_id is set on activity
Is there any chance to fix the relations so I could load them in bulk?
By their very nature, eager loaded relationships do not have the relationship method run for each parent model. If they did, you would have the N+1 issue, which is exactly what eager loading is meant to solve.
The relationship method is run once on the model that is used to start the query. This gets the base query to run, and then all of the parent model ids are injected into that query.
In order to do what you want, you need to change things up a little bit. First, your Activity can be directly related to venues, so setup that relationship without any conditions. Next, create an accessor method that will return the proper venue for the Activity.
So, your code would look something like:
class Activity extends Model
{
public function event()
{
return $this->belongsTo('App\Event');
}
public function venue()
{
return $this->belongsTo('App\Venue');
}
public function getActivityVenueAttribute()
{
return $this->venue ?? $this->event->venue ?? null;
}
}
The other option would be to always assign the venue_id on the Activity, even if it is the same as the Event venue_id. Then you don't need to worry about the venue id missing on the activity.
I am attempting to create a clean cut service layer, whereby the service layer acts upon one or more repositories, and each repositories acts on its own eloquent model.
For example, I may have:
ForumService
|
+-- PostRepo extends PostInterface
| |
| +-- Post (Eloquent)
|
+-- UserRepo extends UserInterface
|
+-- User (Eloquent)
Each service defines it's required dependencies via ioc. So, something like:
// MessageService
// ..
public function __construct(UserInterface $userRepository,
MessageInterface $messageRepository) {
// ..
}
My repositories are resolved via their bindings in their respective service providers, such as:
class UserRepositoryServiceProvider extends ServiceProvider
{
public function register()
{
$this->app>bind(
'App\Models\Repositories\User\UserInterface',
'App\Models\Repositories\User\UserRepository');
}
}
This all works just fine. Each service gets the repositories it requires.
To keep the service layer clear of any specific dependency on eloquent, anything that leaves a repo is a simple, immutable, data object.
Key points in everyday language:
Only the repo's talk to their own models directly
Repo's return simple, immutable, data objects
Services act to tie multiple repo's together and present simplified objects back to the controllers, and ultimately the views.
However I can't come up with a clean pattern to associate eloquent models to each other at the service or repo layer.
Given the Post model has a belongsTo(User::class) relationship, how do I cleanly create that relationship at the Post repository layer.
I have tried:
public function associate($authorId)
{
$post->author()->associate($authorId);
}
But associate expects a user eloquent object, not just an id. I could do:
public function associate($authorId)
{
$post->from()->associate($userRepo->findEloquent($authorId));
}
But I feel like I am surfacing a eloquent model up into a repo that shouldn't be acting on it.
The easy way:
public function assignToAuthor($postId, $authorId)
{
$post = $this->find($postId); // or whatever method you use to find by id
$post->author_id = $authorId;
}
Now, the above implies that you know the foreign key author_id of the relation. In order to abstract it just a bit, use this:
public function assignToAuthor($postId, $authorId)
{
$post = $this->find($postId);
$foreignKey = $post->author()->getForeignKey();
$post->{$foreignKey} = $authorId;
}
Mind, that you still need to save the $post model, but I suppose you already know that.
Depending on your implementation of the simple, immutable, data object that you use, you could also allow passing the objects instead of raw ids. Something between the lines:
public function assignToAuthor($postId, $authorId)
{
if ($postId instanceof YourDataOject) {
$postId = $postId->getId();
}
if ($authorId instanceof YourDataOject) {
$authorId = $authorId->getId();
}
// ...
}
What I've done in the past that has brought some sanity to this situation for me was do things similar to what you are doing in your second associate method and prefix the repository with Eloquent so in the event I use something besides Eloquent, I just create a new implementation of the repository.
So in this case, I'd end up with class EloquentUserRepository implements UserInterface. I usually end up with some public methods which take and return only primitives and possibly some private methods which would be coupled to Eloquent so what I end up doing then is dropping those public methods into a AbstractUserRepository, or a trait if it makes more sense, to keep the code DRY.
It really depends on the situation, I had many thoughts on those actions as well on my repositories.
What I would suggest is to simply not use the "associate" function, you can simply do:
$post->user_id = $userID;
$post->save();
** of course you need to make sure that the user with that id exists.
A) You can do it outside with a special service for "associatingUser"
B) You can do it like you did with using the UserRepositoryInterface,
I see no problem adding the interface as a dependency.
Option A:
class AssociateUserToPost {
private $userRepo;
private $postRepo;
public function __construct(UserRepoInterface $userRepo, PostRepoInterface $postRepo) {
$this->userRepo = $userRepo;
$this->postRepo = $postRepo;
}
public function associate($userId, $postId) {
$user = $this->userRepo->getUser($userId);
if ( ! $user )
throw new UserNotExistException();
$post = $this->postRepo->getPost($postId);
if ( ! $post )
throw new PostNotExistException();
$this->postRepo->AttachUserToPost($postId, $userId);
}
}
option B (quite the same, code just sits in different places)
class PostRepository implements PostRepoInterface {
private $userRepo;
public function __construct(UserRepoInterface $userRepo) {
$this->userRepo = $userRepo;
}
public function associate($userId, $postId) {
$user = $this->userRepo->getUser($userId);
if ( ! $user )
throw new UserNotExistException();
$post = $this->getPost($postId);
if ( ! $post )
throw new PostNotExistException();
$this->AttachUserToPost($postId, $userId);
}
}
Hydration!
I'm assuming that another reason calling findEloquent within the post service seems icky is because you may have already retrieved that data within the controller. Simply put, you can access the same method that Eloquent uses to transform raw query results into fully functioning models.
$userData = array(
// simple, immutable data
);
$userCollection = User::hydrate(array($userData));
$userModel = $userCollection->first();
I think you actually need an additional layer, is what I call a Manager. This will contain all the business logic and will work only with interfaces. Under the hood it will call the services(each knowing to work with a specific resource/model)