Best practice for Phalcon model relationship - phalcon

Loving working with Phalcon, but have concerns about the best practice for model relationships. For example, if I have the following db relationship:
http://i.imgur.com/8kZYomW.png
And create a relationship in the Thing model like:
public function initialize()
{
$this->hasOne('categoryId', 'Categories', 'id', array('alias' => 'cat'));
}
The following easily prints off beautifully:
{{ thing.cat.name }}
Whereas pre-phalcon I would join the 2 to make 1 round trip, the elegance is worth the 2 DB hits...
... until you are grabbing a list of say 1,000 Things and looping through like:
{{ for thing in things }}
<li>{{ thing.cat.name }}</li>
{{ endfor }}
Then I am getting 1,001 DB calls (assuming there are 1,000 different categories I guess).
What is the best practice for dealing with this scenario? And not just for a simple relationship like this where the lookup could be fairly static and easy to cache. What about a more dynamic relationship where you may be listing invoices between historical dates, their customers and the category of the customer?

For building more complex queries you probably want to check those query builder examples.
Essential (not tested) is:
$things = $this->modelsManager->createBuilder()
->from('Things', 'thing')
->leftJoin('Categories', 'category.id = thing.categoryId', 'category')
->getQuery()
->execute();
var_dump($things->toArray());

Adding 'reusable' => true may help.
public function initialize()
{
$this->hasOne('categoryId', 'Categories', 'id', array(
'alias' => 'cat', 'reusable' => true));
}
http://docs.phalconphp.com/en/latest/reference/models-cache.html#reusable-related-records

I am not 100% sure what you're asking. Though if I understand correctly are you looking for something like:
// Thing model
public function initialize()
{
$this->belongsTo('categoryId', 'Categories', 'id');
}
// Category model
public function initialize()
{
$this->hasMany('id', 'Things', 'categoryId', array('alias' => 'things'));
}
// Output things in certain category
foreach($category->things as $thing)
{
echo $thing->name;
}
If I have completely misunderstood your question, I apologise.
Hope it helps!

Related

Optimal way to use function for property in AutoMapper ProjectTo IQueryable tree

I'm using AutoMapper .ProjectTo<OrderDto>() to map some Order to OrderDto.
The original Order is a pretty big object with around 30 properties and 15 collections.
For my OrderDto I have a property DisplayStatus with some custom logic based on 4 properties in the original Order. I don't need those properties in my final OrderDto, I just need them to do something like GetDisplayStatus().
I have tried several ways of doing this and none of them feels satisfying to me so my guess is I must be doing something wrong. There has to be a better way.
FIrst thing I tried was to use a custom value resolver but you can't use them inside an IQueryable projection because they can't be translated to Linq (if I understood correctly).
Then I tried doing something like this
.ForMember(target => target.DisplayStatus, option => option.MapFrom(source => GetDisplayStatus(source))
The problem with this is that Entity Framework consider this to be a "blackbox" and have no way to know what I need and what I don't. Therefore the whole source object is passed to the function. The consequence is that the final SQL Query applies a Select on all the columns from Order, bloating the query with a big chunk of unnecessary data and performance goes down by a big margin.
In the end the current way of doing this in our codebase is something like this
.ForMember(target => target.DisplayStatus, option => option.MapFrom(source =>
GetDisplayStatus(new TempOrder{
someProperty = source.someProperty
anotherPropery = source.anotherProperty
}))
By doing this I'm able to narrow down exactly what properties of the original Order I want to pass to my GetDisplayStatus() function and the final SQL Query stay clean.
Downside to this is that the code can quickly become pretty ugly when the things you want to pass to your function require a bit a work themselves.
.ForMember(target => target.DisplayStatus, option => option.MapFrom(source =>
GetDisplayStatus(source.OrderProducts.Where(//a lot of work here too//).Select(op =>
new TempOrderProduct
{
ProductTypeId = op.ProductTypeId,
HasFiles = op.OrderFiles.Any(),
VisitDate = op.VisitDate
}), source.OrderStatus)))
Again, this does not feel right. Is there a better way to do this ?
Thank you.
Edit
Here is the same query without AutoMapper as requested in the comments
public static IQueryable<OrderDTO> MapOrderDTO(this IQueryable<Orders> orders)
{
return orders
.Select(order => new OrderDTO
{
OrderID = order.OrderId,
OrderCreationDate = order.OrderCreationDate,
OrderStatus = order.OrderStatus,
DisplayStatus = GetDisplayStatus(order.OrderProducts
.Where(//a lot of work//)
.Select(op => new TempOrderProduct
{ ProductTypeId = op.ProductTypeId,
HasFiles = op.OrderFiles.Any(),
VisitDate = op.VisitDate }),
order.OrderStatus)
})
});
}

Undefined model by using Join Query in Laravel

I am indexing apples with their specified properties (such as color) using API Laravel. I use join to retrieve apples which are related to a specified brand. but it does not retrieve apples with their own specified properties which are defined in another DB and models.
public function index(Brand $brand)
{
$apples = Apple::join('brands', 'brand_id', 'brands.id')->where('brand_id', $brand->id)->get();
return returnSuccessfulResponse(
trans('api.response.successful.index'),
Resource::collection($apples)
);
}
Apple model:
public function brand()
{
return $this->belongsTo(Brand::class);
}
public function appleProperties()
{
return $this->hasMany(AppleProperty::class);
}
Resource:
return [
'id' => $this->brand->id,
'name' => $this->brand->name,
'apple-properties' => $this->appleProperties,
];
Route:
Route::apiResource('brands/{brand}/apples', 'AppleController');
It is not retrieving appleProperties. I do not understand that reason!
When you use join() method in your queries, it is recommended to use select() as well, so that is no longer ambiguous which table you referenced to. In your code, the query may be something like this:
$apples = Apple::join('brands', 'brand_id', 'brands.id')->select('apples.*')->where('brand_id', $brand->id)->get();

By code mapping of many-to-many with OrderBy

I'm using by code mappings and trying to map a manytomany. This works fine but I need OrderBy for the child collection items. I noticed this has been omitted (it does exist in the HBM mappings). e.g.
public class Mapping : EntityMapper<Category>
{
public Mapping()
{
Set(x => x.Items, m =>
{
m.Table("ItemCategories");
m.Key(k => k.Column("CategoryId"));
m.Inverse(true);
m.Cascade(Cascade.None);
}, col => col.ManyToMany(m =>
{
m.Columns(x => x.Name("ItemId"));
//m.OrderBy("Score desc"); // missing in Nh4.x?
}));
}
}
Is there a workaround for this? I tried following the suggestion in this article whereby I can set the property before the session factory is built but it has no effect. e.g.
cfg.GetCollectionMapping(typeof(Category).FullName + ".Items").ManyToManyOrdering = "Score desc";
cfg.BuildSessionFactory();
Am I doing something wrong or is OrderBy on manytomany not supported in Nh4?
Also, is it possible to restrict the maximum number of items retrieved in the collection?
Replaced the many to many with one to many and introduced an entity that represents the relationship (followed advice from this article).
This has the upside of allowing you to map the order-by column as well as other columns, and also solved the issue of restricting the number of items in the collection by using the one-to-many's Where() and Filter() clauses.

Kohana 3.3 ORM - how to find all ancestors in a tree (self-referencing table)

How to find all the ancestors (not only direct parent) of a record with the following model:
class Model_Category extends ORM {
protected $_belongs_to = array(
'parent' => array('model' => 'Category', 'foreign_key' => 'category_id'),
);
protected $_has_many = array(
'children' => array('model' => 'Category', 'foreign_key' => 'category_id'),
);
$category->parent->find() is only giving the direct parent and even when trying to fetch the parent of the parent with a recursive function it throws Kohana_Exception: Method find() cannot be called on loaded objects.
This occurs so natural to me that I guess there must an easy way to do it - I'm not sure if it's me or the lack of documentation on ORM relations - halp!
You should be able to do this with a recursive function or a while loop
$current = $category;
while ($current->parent->loaded())
{
//save $current
$current = $category->parent;
}
Use Nested Sets. For example, https://github.com/evopix/orm-mptt. It has special methods like parents(), children(), siblings() etc. Of course, this requires modifications in your DB table.
Ok, turns out the related models actually ARE included, cause if you do
$category->parent->parent->name it will be the name of the grand-parent of $category.
My problem was that I was printing $category->parent->as_array() and expecting to see the parent relations in the (multidimensional) array which simply appears not to be the case with kohana.

add a common scope to all models in cakephp

I am building an online accounts application where each business has its own data and therefore each table has a field business_id.
Is there a way to automatically add to each Model the condition
Model.business_id => x
For example if a user did a search for all Transactions containing Project the conditions added would be:
Transaction.business_id => x
Project.business_id => x
I am guessing that this would be best placed in a behavior as it applies to all but two models
Thanks
you can modify query data in beforeFind(), which can be within a behavior as well
in your models:
$actsAs = array('BusinessCondition');
The behavior would be something like:
<?php
App::uses('AuthComponent', 'Controller/Component');
class BusinessCondition extends ModelBehavior {
public function beforeFind(Model $Model, $query) {
$query['conditions'][$Model->alias . '.business_id'] = AuthComponent::user('business_id');
return $query;
}
}