Can I include a convenience query in a Doctrine 2 Entity Method? - doctrine

I'd like to include some additional functions in my Doctrine 2 entities to contain code that I'm going to have to run quite frequently. For example:
User - has many Posts
Post - has a single user
I already have a function $user->getPosts(), but this returns all of my posts. I'm looking to write a $user->getActivePosts(), which would be like:
$user->getPosts()->where('active = true') //if this were possible
or:
$em->getRepository('Posts')->findBy(array('user'=>$user,'active'=>true)) //if this were more convenient
As far as I can tell, there's no way to get back to the entity manager though the Entity itself, so my only option would be
class User {
function getActivePosts() {
$all_posts = $this->getPosts();
$active_posts = new ArrayCollection();
foreach ($all_posts as $post) {
if ($post->getActive()) {
$active_posts->add($post);
}
}
return $active_posts;
}
However, this requires me to load ALL posts into my entity manager, when I really only want a small subset of them, and it requires me to do filtering in PHP, when it would be much more appropriate to do so in the SQL layer. Is there any way to accomplish what I'm looking to do inside the Entity, or do I have to create code outside of it?

I think you should implement the method on the PostRepository rather than on the entity model.
I try to keep all model related logic in the repositories behind "domain specific" methods. That way if you change the way you represent whether a post is active or not, you only have to change the implementation of a single method instead of having to find all the active = true statements scattered around in your application or making changes in an "unrelated" entity model.
Something like this
PostRepository extends EntityRepository {
public function findActiveByUser($user){
// whatever it takes to get the active posts
}
}

Related

Overriding Laravel get and first methods

I need to override above mentioned methods to skip some database records. Using where is not an option since I would have to use it every single time as there are records in database that I do not need most of the time and I am not allowed to delete them from DB. Here is my attempt of doing this:
class SomeTable extends BaseModel {
public static function first() {
$query = static::query();
$data = $query->first();
if($data && $data->type == 'migration_type') return null;
return $data;
}
public static function get() {
$query = static::query();
$data = $query->get();
foreach($data as $key => $item) {
if($item->type == 'migration_type') unset($data[$key]);
}
return $data;
}
}
The problem with this code is that it works only when direct called on model. If I am using some other functions, like where, before get or first methods, it just skips my overridden method.
What would be the right way to do this and should I put this code within model?
My question is not duplicate as in the answer from mentioned question it is said:
all queries made from Models extending your CustomModel will get this new methods
And I need to override those two functions only for specific model, not for each one in application as not all tables have type column. That's the reason why I have written them within model class.
I need to override above mentioned methods to skip some database records.
Consider a global query scope on the model.
https://laravel.com/docs/5.8/eloquent#global-scopes
Global scopes allow you to add constraints to all queries for a given model. Laravel's own soft delete functionality utilizes global scopes to only pull "non-deleted" models from the database. Writing your own global scopes can provide a convenient, easy way to make sure every query for a given model receives certain constraints.
The issue here is that the where() method on the model returns a QueryBuilder instance where get() will return a Collection instance.
You should be able to override collection's default methods by adding a macro in it's place and can be done like so...
Collection::macro('toUpper', function () {
return $this->map(function ($value) {
return Str::upper($value);
});
});
Extending the query builder instance is not so easy but a good tutorial exists here and involves overriding the application's default connection class, which is not great when it comes to future upgrades.
Because after calling where you're dealing with the database builder and theses methods inside your model aren't being called .. about the issue you might overcome it by using select instead of first directly so will deal with the builder ..
example:
SomeTable::select('col1','col2')->take(1)->get();
another thing overriding these kind of methods is not a good idea if you're working with other developer on the same project.
good luck

Relationship mapping with NeoEloquent

I'm tinkering with Neo4j 2.3.0 and Laravel 5.1 using NeoEloquent. I've set up a couple of dummy nodes and some relationships between them:
image of Neo4j model - apologies, I cannot insert images directly yet :)
So articles can use a template. The inverse of this relationship is that a template is used by an article.
I've set up the classes like so:
Class Template extends Model
{
public function articles()
{
return $this->hasMany('App\Article', 'USED_BY');
}
}
And:
Class Article extends Model
{
public function template()
{
return $this->belongsTo('App\Template', 'USES');
}
}
So far, so good, I think.
I have a page where I am wanting to eventually list all of the articles in the system, along with some useful metadata, like the template each ones uses. For this, I have set something up in the controller:
$articles = array();
foreach (Article::with('template')->get() as $article) {
array_push($articles, $article);
}
return $articles;
Not the most elegant, but it should return the data for both the article and it's associated template. However:
[{"content":"Some test content","title":"Test Article","id":28,"template":null},{"content":"Some flibble content","title":"Flibble","id":31,"template":null}]
So the question is - why is this returning null?
More interestingly, if I set up the relationship to the same thing in BOTH directions, it returns the values. i.e. if I change the USED_BY to USES, then the data is returned, but this doesn't make sense from an architectural point of view - a template does not 'use' an article.
So what am I missing?
More interestingly, if I set up the relationship to the same thing in BOTH directions, it returns the values.
That's correct, because this is how it operates. It is worth knowing that the relationship methods you have defined represent the relationship itself, which means for both models Template and Article to target the USED_BY relationship from any side it has to be the same in articles() and template.
The solution would be to use something like USES (or any notion you like) on both sides. This reference should help you make good decisions regarding your relationships.
On the other hand, if you still wish to have different relations on the sides then kindly note that in your model (image) both relationships are in outgoing direction. i.e. Fibble-[:USES]->Template and Template-[:USED_BY]->Fibble which means template() should be an outgoing relationship such as hasOne instead of belongsTo which is incoming.

Validate breeze save bundle before going to the breeze controller

Everywhere I look about validating the response before saving to the DB, in breeze, they are overriding the BeforeSaveEntity or BeforeSaveValidation.
e.g. breezejs issues with the save bundle. Is there anyway we can validate the savebundle before calling the saveChanges(), like in the repository level?
I want to pass the JObject savebundle from the controller to the relevant repository and do a few things there:
1) Check if the user has permission to save this entity
2) Do business-logic level validation
3) Do entity level operations such as updating the changedDate and changedUser, add default values to some other entity etc...
These are more like business-logic level operations and in our application we have like 20+ such entities that get saved from different parts of the application. If we override BeforeSaveEntity() we are doing all such business logic level validations for all entities inside the DataContext. Like
`if (entityInfo.Entity.GetType() == typeof(MyEntityTypeModel)) {
}`
I don't think if-else or case condition for 20+ entities is a good design. Besides, we have a clear separation of concerns through the use of repositories, so I think that's where this should be done.
How do we manipulate/validate the savebundle in such case?
Use the BeforeSaveEntities method ( documented here: http://www.getbreezenow.com/breeze-sharp-documentation/contextprovider). With this method you can work with ALL of the entities of a specified type without having to perform an 'if' test on each one.
Code might look something like this:
ContextProvider.BeforeSaveEntitiesDelegate = CheckFreightOnOrders;
return ContextProvider.SaveChanges(saveBundle);
private Dictionary<Type, List<EntityInfo>> CheckFreightOnOrders(Dictionary<Type, List<EntityInfo>> saveMap) {
List<EntityInfo> entityInfos;
// extract just those entities of type 'Order'
if (saveMap.TryGetValue(typeof(Order), out orderEntityInfos)) {
// then iterate over them.
foreach (var entityInfo in orderEntityInfos) {
CheckFreight(entityInfo);
}
}
return saveMap;
}

How can I "defang" a Doctrine2 Entity?

I'd like to create an "API-like" layer in my code that effectively cordons-off database access to higher level code. For example, I might have the following function:
class MyApi {
private $my_user_id;
function getContacts() {
$contacts = $em->getRepository('Contacts')->findByOwner($this->my_user_id);
$em->clear();
return $contacts;
}
function getGroups() {
$groups = $em->getRepository('Groups')->findByOwner($this->my_user_id);
//hydrate each group's contacts list
foreach ($groups as $group) {
$group->getContacts();
}
$em->clear();
return $groups;
}
}
I'm using $em->clear() to detach the Entities from the EntityManger before returning them, so my Views can't accidentally modify managed entities. However, I run into problems when I want to compare entities returned by two sequential API functions. Ideally, I'd like a view/controller to contain:
$my_contacts = $myapi->getContacts();
$my_groups = $myapi->getGroups();
foreach($my_groups as $group) {
foreach ($my_contacts as $contact) {
if ($group->getContacts()->contains($contact)) {
echo "{$group->getName()} contains {$contact->getName()}!<br/>";
} else {
echo "{$group->getName()} does not contain {$contact->getName()}!<br/>";
}
}
}
However, since I detached all of the Contacts from the EntityManager at the end of the getContacts API call, the objects returned by $group->getContacts() are different PHP objects than those returned by $api->getContacts(), so the contains() function doesn't work properly.
Do I have any options for "defanging" my entities, making them effectively read-only, without giving up the benefits that the EntityManager provides? (Such as all managed entities representing the same database entry being the same object, being able to further hydrate associated objects after they've been passed back from the API, etc.)
Why would you worry that your views are going to make changes that will be committed back to the database? If your views don't know about the EM (and they shouldn't), any changes they make to the entities will disappear at the end of the request.
The only other option I can think of is to hydrate your results as arrays when they're destined to be fed to the view script. But that gives up a lot of handy functionality.
Maybe this is a little late, but it can be useful for anyone who still needs answer on this issue...
I think there is missing a Domain Driven Design principle here: Command Query Separation.
On every object you can only have two kind of methods:
doSomething() {
//this kind of method can change the internal state
}
getSomething() {
//this kind of method NEVER changes internal state
}
Keeping proper MVC in mind, views should only need get-methods and they can never change a thing.
Detaching you entities is not necessary and no worries are needed, if keeping just CQS and MVC in mind.

Filter every call made by a DataContext when using LinQ Entities

I'm using logical delete in my system and would like to have every call made to the database filtered automatically.
Let say that I'm loading data from the database in the following way :
product.Regions
How could I filter every request made since Regions is an EntitySet<Region> and not a custom method thus not allowing me to add isDeleted = 0
So far I found AssociateWith but I'd hate to have to write a line of code for each Table -> Association of the current project...
I'm looking into either building generic lambda Expressions or.. something else?
You could create an extension method that implements your filter and use that as your convention.
public static class RegionQuery
{
public static IQueryable<Region> GetAll(this IQueryable<Region> query, bool excludeDeleted=true)
{
if (excludeDeleted)
return query.Regions.Where(r => !r.isDeleted);
return query.Regions;
}
}
So whenever you want to query for regions you can make the following call to get only the live regions still providing an opportunity to get at the deleted ones as well.
context.Regions.GetAll();
It my be a little wonky for access the Products property, but still doable. Only issue is you would have to conform to the convention. Or extend the containing class.
someProduct.Regions.GetAll();
I hope that helps. That is what I ended up settling on because I haven't been able to find a solution to this either outside of creating more indirection. Let me know if you or anyone else comes up with a solution to this one. I'm very interested.
It looks to me like you're using a relationship between your Product and Region classes. If so, then somewhere, (the .dbml file for auto-generated LINQ-to-SQL), there exists a mapping that defines the relationship:
[Table(Name = "Product")]
public partial class Product
{
...
private EntitySet<Region> _Regions;
[Association(Storage = "_Regions")]
public EntitySet<Region> Regions
{
get { return this._Regions; }
set { this._Regions.Assign(value); }
}
...
}
You could put some logic in the accessor here, for example:
public IEnumerable<Region> Regions
{
get { return this._Regions.Where(r => !r.isDeleted); }
set { this._Regions.Assign(value); }
}
This way every access through product.Regions will return your filtered Enumerable.

Resources