Cakephp ORM is pulling every column - php

I have the snippet of code, I want the the following query to pull all products associated with category, a belongs to many relationship.
That works, but even though I have declare a select, it still retrieves every column in product, i just want the four columns which I declare.
I look over and read the documentation at http://book.cakephp.org/3.0/en/orm/retrieving-data-and-resultsets.html
$Product= $categoryObj->find()->contain([
'Product' => function ($q) {
return $q
->select(['name', 'url', 'published', 'site_id'])
->where(['published' => 1, 'site_id' => 2]);
}
])
->where(['id'=> $id])
->toArray();

Try adding autoFields(false) after the select(). The code would be as follows:
$Product= $categoryObj->find()
->contain([
'Product' => function ($q) {
return $q
->select(['name', 'url', 'published', 'site_id'])
->autoFields(false)
->where(['published' => 1, 'site_id' => 2]);
}
])
->where(['id'=> $id])
->toArray();

Related

Laravel pagination Relation with API resources

I have user resource as follow:
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'posts' => PostResource::collection($this->posts()->paginate(10)),
];
}
In User model, there is hasMany relation posts
My problem with paginating, the links and meta of post paginate does not show just get 10 posts without links of paginate
My controller
$user = User::query()
->where('id', $userId)
->with('posts')
->firstOrFail();
return new UserResource($user);
I think it's because you return the posts attribute as a collection not pagination.
Try
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'posts' => $this->posts()->paginate(10),
];
Laravel doesn't handle this case for you.
You have just 2 options:
Write a custom pagination logic
Just use two different API's with dedicated pagination in the posts API (recommended)

How to fetch associated belongsToMany entities with CakePHP3

I have Users and Courses table with belongsToMany relation. UserTable has
$this->belongsToMany('Courses', [
'foreignKey' => 'user_id',
'targetForeignKey' => 'course_id',
'joinTable' => 'courses_users'
]);
and CoursesTable has
$this->belongsToMany('Users', [
'foreignKey' => 'course_id',
'targetForeignKey' => 'user_id',
'joinTable' => 'courses_users'
]);
Now, I want to fetch courses with user_id. In my CoursesController, I tried
public function myCourses()
{
$id = $this->Auth->user('id');
$courses = $this->Courses->find('all',
['contain' => ['Users'],
'condition' => ['Courses.user_id' => $id]
]);
$this->set('courses', $courses);
}
when I debug($courses) with this code, I got '(help)' => 'This is a Query object, to get the results execute or iterate it.' message. I'm searching information and trying to do it for many hours but I can't make it. How can I fetch Courses datas with user_id? Thanks in advance.
If it's a has-and-belongs-to-many (HABTM) association with a join table of courses_users, you shouldn't even have a user_id field in your Courses table.
So now that we've determined you can't do what you were trying (Courses.user_id), we can look at what you thought you were trying:
$courses = $this->Courses->find('all',
['contain' => ['Users'],
//'condition' => ['Courses.user_id' => $id]
]);
This says "find all courses and any users that are associated with those courses".
But what you really WANT (I believe) is: "find all courses that belong to this specific user".
To do that, you'll want to use an matching() instead.
According to the CakePHP book:
A fairly common query case with associations is finding records
‘matching’ specific associated data. For example if you have ‘Articles
belongsToMany Tags’ you will probably want to find Articles that have
the CakePHP tag. This is extremely simple to do with the ORM in
CakePHP:
$query = $articles->find();
$query->matching('Tags', function ($q) {
return $q->where(['Tags.name' => 'CakePHP']);
});
So in your case, it would be something like this:
$query = $courses->find();
$query->matching('Users', function ($q) use ($id) {
return $q->where(['Users.id' => $id]);
});

Join models in findall active record yii

I am writing a function to get posts and ordered by the number of likes.
I can do it fine in pure SQL, but I wanted to have it written in the yii way.
Here is what I have so far:
public static function getPopularPosts($limit) {
$posts = Tip::model()
->with( array(
'Like' => array(
'select' => 'sum(`Like`.value) as likes',
'join' => 'LEFT JOIN `Like` ON `Tip`.idTip = `Like`.relatedTableIdFK',
'condition'=>'`Like`.relatedTableName like "Tip" AND `Like`.status=1',
)));
$posts ->together();
$posts ->findAll(array(
'group'=>'idTip',
'order'=>'likes DESC',
'limit' => $limit,
));
return $posts;
}
And the get the error:
Relation "Like" is not defined in active record class "Tip".
Read first please http://www.yiiframework.com/doc/guide/1.1/en/database.arr#declaring-relationship
Then open your model Tip find method relations or create new and add there relation according to documentation above.
public function relations(){
return array(
'Like'=>array(self::HAS_MANY, 'Like', 'relatedTableIdFK')
);
}

Eloquent automatically selects unwanted columns upon eager-loading model relationship

I am converting an internal API from HTML (back-end) processing to JSON (using Knockout.js) processing on the client-side to load a bunch of entities (vehicles, in my case).
The thing is our database stores sensitive information that cannot be revelead in the API since someone could simply reverse engineer the request and gather them.
Therefore I am trying to select specifically for every relationship eager-load the columns I wish to publish in the API, however I am having issues at loading a model relationship because it seems like Eloquent automatically loads every column of the parent model whenever a relationship model is eager loaded.
Sounds like a mindfuck, I am aware, so I'll try to be more comprehensive.
Our database stores many Contract, and each of them has assigned a Vehicle.
A Contract has assigned an User.
A Vehicle has assigned many Photo.
So here's the current code structure:
class Contract
{
public function user()
{
return $this->belongsTo('User');
}
public function vehicle()
{
return $this->belongsTo('Vehicle');
}
}
class Vehicle
{
public function photos()
{
return $this->hasMany('Photo', 'vehicle_id');
}
}
class Photo
{
[...]
}
Since I need to eager load every single relationship listed above and for each relationship a specific amount of columns, I need to do the following:
[...]
$query = Contract::join('vehicles as vehicle', 'vehicle.id', '=', 'contract.vehicle_id')->select([
'contract.id',
'contract.price_current',
'contract.vehicle_id',
'contract.user_id',
'contract.office_id'
]);
[...]
$query = $query->with(['vehicle' => function ($query) {
$query->select([
'id',
'trademark',
'model',
'registration',
'fuel',
'kilometers',
'horsepower',
'cc',
'owners_amount',
'date_last_revision',
'date_bollo_expiration',
'bollo_price',
'kilometers_last_tagliando'
]);
}]);
$query = $query->with(['vehicle.photos' => function ($query) {
$query->select([
'id',
'vehicle_id',
'order',
'paths'
])->where('order', '<=', 0);
}]);
$query = $query->with(['user' => function ($query) {
$query->select([
'id',
'firstname',
'lastname',
'phone'
]);
}]);
$query = $query->with(['office' => function ($query) {
$query->select([
'id',
'name'
]);
}]);
[...]
return $this->response->json([
'error' => false,
'vehicles' => $vehicles->getItems(),
'pagination' => [
'currentPage' => (integer) $vehicles->getCurrentPage(),
'lastPage' => (integer) $vehicles->getLastPage(),
'perPage' => (integer) $vehicles->getPerPage(),
'total' => (integer) $vehicles->getTotal(),
'from' => (integer) $vehicles->getFrom(),
'to' => (integer) $vehicles->getTo(),
'count' => (integer) $vehicles->count()
],
'banner' => rand(0, 2),
'filters' => (count($input) > 4),
'filtersHelpText' => generateSearchString($input)
]);
The issue is: if I do not eager load vehicle.photos relationship, columns are loaded properly. Otherwise, every single column of Vehicle's model is loaded.
Here's some pictures so you can understand:
Note: some information have been removed from the pictures since they are sensitive information.
You can set a hidden property on your models which is an array of column names you want to hide from being output.
protected $hidden = ['password'];

'Through' association with 'foreign_key' & 'model' fields in CakePHP 3

This is a pretty standard thing that I've done probably 600 times in CakePHP 2, but for the life of me, I can't get it to work in CakePHP 3.
I have Videos, Photos, Articles. I also have Categories.
Videos, Photos, and Articles can all belong to one or more Categories.
The goal of the current problem is to pull videos that are under a certain category.
So, I tried this:
// VideosTable
$this->belongsToMany('Categories', [
'joinTable' => 'categorized',
'className' => 'Categorized',
'foreignKey' => 'foreign_key',
'conditions' => [
'Categorized.model' => 'Videos',
]
]);
public function getTopVideosByCategory($categorySlug)
{
return $this->Categories->find('all')
->where(['Categories.slug' => $categorySlug])
->contain([
'Videos' => function ($q) {
return $q
->limit(8)
->contain([
'Tags',
'Categories' // tried with and without this
])
->order([
'Videos.featured' => 'DESC',
'Videos.created' => 'DESC'
]);
}
])
->first();
}
Error: SQLSTATE[42S22]: Column not found: 1054 Unknown column
'Categorized.model' in 'where clause'
I've tried a number of other ways including creating the join table's model, and a few others, but keep getting errors. I've tried with every option, and with limited number of options. I've tried using an actual Table class, and I've tried a pseudo one (like "Categorized" above).
I have to assume this is pretty standard, but can't find an example in the book, and I just can't seem to get it to work.
Edit:
I've also tried this:
//VideosTable
public function initialize(array $config)
{
$this->belongsToMany('Categories', [
'through' => 'Categorized',
'conditions' => [
'Categorized.model' => $this->alias(),
]
]);
}
public function getTopVideosByCategory($categorySlug)
{
return $this->find('all')
->matching('Categories', function ($q) use ($categorySlug) {
return $q
->where(['Categories.slug' => $categorySlug]);
})
->contain([
'Tags',
'Categories'
])
->limit(8)
->order([
'Videos.featured' => 'DESC',
'Videos.created' => 'DESC'
])
->first();
}
But get this error:
Error: SQLSTATE[42S22]: Column not found: 1054 Unknown column
'Categorized.model' in 'on clause'
Since Videos and Categories is not a 1-1 o n-1 (hasOne or belongsTo), it is impossible to build a SQL expression that can include conditions for the other table. For those cases, CakePHP implements the matching() function. It works similar to contain() but what it does is using an INNER join to get the data from the external associations:
http://book.cakephp.org/3.0/en/orm/retrieving-data-and-resultsets.html#filtering-by-associated-data
You can also look an an example of using it here:
http://book.cakephp.org/3.0/en/tutorials-and-examples/bookmarks/intro.html#creating-the-finder-method
I ended up getting it to work like this:
class VideosTable extends Table
{
public function initialize(array $config)
{
$this->hasMany('Categorized', [
'foreignKey' => 'foreign_key',
'conditions' => [
'Categorized.model' => $this->alias(),
]
]);
}
public function getTopVideosByCategory($categorySlug)
{
return $this->find()
->matching(
'Categorized.Categories', function ($q) use ($categorySlug) {
return $q
->where(['Categories.slug' => $categorySlug]);
})
->limit(8)
->order([
'Videos.featured' => 'DESC',
'Videos.created' => 'DESC'
])
->all();
}
I've up-voted José's answer, as it led me down the road of figuring it out, but will mark this as the answer, as I think it more-quickly helps users trying to figure this particular problem out.
José, if you want to append this (with any tweaks you see fit) to your answer, I'll change the marked answer to yours.
It seem that what you want is:
$this->belongsToMany('Categories', [
'through' => 'Categorized',
'conditions' => ['Categorized.model' => $this->alias()]
]);

Categories