Problem with codeigniter 4 Query builder classes - mysql

On terminal, in mysql , running the following query gives this result
mysql> SELECT DISTINCT(city) FROM outlets_data;
+-----------+
| city |
+-----------+
| Paris |
| New York |
| Kolkata |
| Moscow |
| Mumbai |
| Hyderabad |
| Delhi |
| Chennai |
+-----------+
8 rows in set (0.00 sec)
I want to store the names of these cities, in an array, in codeigniter 4 models class file.
Models/DashboardModels.php
<?php
namespace App\Models;
use CodeIgniter\Model;
class DashboardModel extends Model
{
protected $table = 'outlets_data';
protected $primaryKey = 'shop_id';
public function not_defined_yet()
{
$city_names = $this->select('city')->distinct(); // This should be equivalent to "SELECT DISTINCT(city) FROM outlets_data";
return $city_names;
}
}
Controller/Home.php
<?php
namespace App\Controllers;
use App\Models\DashboardModel;
use CodeIgniter\Model;
class Home extends BaseController
{
public function index()
{
$model = new DashboardModel();
$data['undefined'] = $model->not_defined_yet();
echo view('dashboard', $data);
}
}
Views/Dashboard.php
<?php echo "<pre>"; print_r($undefined); echo "</pre>"; ?>
I expect to get names of the cities in output array, but I am getting whole database as associative array.

Your function should be:
public function not_defined_yet()
{
$city_names = $this->select('city')->distinct(); // This should be equivalent to "SELECT DISTINCT(city) FROM outlets_data";
return $this;
}
Then your function be
$data['undefined'] = $model->not_defined_yet()->findAll();
Other way you can do it is loading a new instance of the database object.
public function not_defined_yet()
{
$db = \Config\Database::connect();
$builder = $db->table('outlets_data');
$city_names = $builder->select('city')->distinct();
return $city_names->resultArray();
}
You can even remove the function all together and in your controller do this:
$data['undefined'] = $model->select('city')->distinct()->findAll();
This would get the same exact result.

Related

How to get data of joined table using query() method

I have an issue with getting data of the joined table if I use query().
I did not get the data of product table, how can I solve this using query() without using Active record?
Here is my db table structure
category table
+--------------------------+
| cId | added | category |
+-----+--------+-----------+
| 1 | 1.2.20 | PC |
| 2 | 1.7.20 | electron |
+-----+--------+-----------+
product table
+--------------------------+
| id | cId | cost |
+-----+--------+-----------+
| 1 | 1 | 3000 |
| 1 | 2 | 9000 |
+-----+--------+-----------+
My Model
protected $table = 'category';
public function showProduct()
{
$sql = "SELECT
category.*, COALESCE(SUM(product.cost),0) as price
FROM category
JOIN product
ON product.cId = category.cId
GROUP BY category.cId
";
$this->db->query($sql);
return $this;
}
My Controller
public function index()
{
$result = $this->model->showProduct();
echo "<pre>";
print_r($result->asObject()->paginate(1));
//pagination
$pager = $this->model->showProduct()->pager->links();
}
Result I get
Array
(
[0] => stdClass Object
(
[cId] => 1
[added] => 1.2.20
[category] => PC
)
[1] => stdClass Object
(
[cId] => 2
[added] => 1.7.20
[category] => electron
),
)
You are requested to run this code.
SELECT category.cId,category.added,category.category,product.id,COALESCE(SUM(product.cost),0) as price
FROM category,product
WHERE category.cId=product.cId
GROUP BY category.cId;
If you are using CodeIgniter-4 then you can easily write this using Query Builder Class.
$sql="SELECT category*,product* FROM category INNER JOIN product.cid=category.id WHERE category.id";
I found solution to this by setting this table property within a function.
Model
$protected $table = array("default bd table here");
public function showProduct()
{
$this->table = array('category');
$sql = "SELECT
category.*, COALESCE(SUM(product.cost),0) as price
FROM category
JOIN product
ON product.cId = category.cId
GROUP BY category.cId
";
$this->db->query($sql);
return $this;
}
My Controller
public function index()
{
$result = $this->model->showProduct();
//pagination
$pager = $this->model->showProduct()->pager->links();
}

Eloquent relation on data types (fields) table

Here my tables, User is the classic Auth table:
DataTypes
+----+--------------+
| id | field |
+----+--------------+
| 1 | address |
| 2 | mobile_phone |
| 3 | city |
+----+--------------+
UserData
+----+----------+--------------+---------+
| id | value | data_type_id | user_id |
+----+----------+--------------+---------+
| 1 | Milan | 3 | 1 |
| 2 | 99123233 | 2 | 1 |
+----+----------+--------------+---------+
My current crazy model:
class User extends Authenticatable {
public function field($field){
$field=DataType::where('field',$field)->first();
return $this->hasMany('App\UserData')->where('datatype_id',(isset($field->id) ? $field->id : 0));
}
}
Of course it works fine when I have to find values:
auth()->user()->field('mobile_phone')->get();
But how do I set or update a new mobile phone?
Another approach is link User and DataTypes as a many to many relationship and use a scope to handle it.
First of all, change UserData table to dataTypeUser, so you could use default eloquent relationships.
In any model put the belongsToMany relationship.
User Model
public function dataTypes()
{
return $this->belongsToMany('App\DataType')
->withPivot(['value']);
}
public function scopeField($query, $field)
{
return $query->whith(['dataTypes'=>function($q)use($field){
$q->where('data_types.field',$field);
}]);
}
DataType Model
public function users()
{
return $this->belongsToMany('App\User')
->withPivot(['value']);
}
To get some value you can use $field = $user->field('something')->first() to save new data could use $user->field('something')->updateExistingPivot($field->id,['value'=>$newValue]).
Anyway, if you don't have many data of same type attached to your user (more than one phone number for example), it could be a better approach to use one single table extending user migration, or at last an userData with columns for each datatype. In small applications you have no problems but as your application grows performance will be a problem with many tables and many relationships.
To avoid a long declaration, you could overwrite magic methods __get and __set. Both are declared in Illuminate\Database\Eloquent\Model so put in your User Model:
public function __get($key)
{
return $this->getAttribute($key) ?? $this->getAttributesFromPivot($key);
}
public function __set($key, $value)
{
if(in_array($key,array_keys($this->original)))
$this->setAttribute($key, $value);
else
$this->setAttributeFromPivot($key, $value);
}
public function setAttributeFromPivot($key, $value)
{
$this->dataTypes()->where('field',$key)->update(['value'=>$value]);
}
protected function getAttributesFromPivot($key)
{
return $this->dataTypes()
->where('field',$key)
// use this to get only one value direct
->first()->pivot->value ?? null;
// or use this to get all of them as array
// ->get()
// ->map(function($item){ return $item->pivot->value;}) ?? null;
}
In this approach, you could use $user->city to get field city or another one replacing ->city. Or you could use $user->address = 'foo'; to set a new value in pivot table. Note that it will update database directly.
Now, if you are not comfortable to overwrite those methods, you don't need to. Change the signatures of setAttributeFromPivot and getAttributeFromPivot to public function getField($key) and public function setField($key, $value). Now you can use them as common methods.

Symfony 4 Doctrine multiple table joins in one property

Very new to symfony and Doctrine. I have the following tables in my database.
mo_user
id | email | password
__________________________________
9144 | summer#h.com | !password!
mo_user_role
user_id| role_id
_________________
9144 | 5
mo_permission
id | namespace | name | description
______________________________________________
1 | admin | - | -
2 | users | - | -
3 | view_summary_report | - | -
4 | view_user_statement | - | -
mo_role_permission
role_id | permission_id
________________________
5 | 3
5 | 4
I am trying to return an array of the permissions of the current user in this case user with id = 9144 which should be array('view_summary_report','view_user_statement').
I have mapped all the tables to their corresponding entity classes. and in MoUser.php entity class which corresponds to mo_user table, I have a
permissions method which should return the array but my join from annotations is failing,
My getPermissions() method in MoUser.php
/**
* #var Collection|MoPermission[]
* #ORM\ManyToMany(targetEntity="App\Entity\MoPermission")
* #ORM\JoinTable(
* name="mo_user_role",
* joinColumns={#ORM\JoinColumn(name="user_id",referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="role_id",referencedColumnName="id")}
* )
*/
private $permissions;
public function getPermissions()
{
$currentPermissions = array();
foreach ($this->permissions->toArray() as $index => $permission) {
$currentPermissions[] = $permission->getNamespace();
}
//Return default role if Roles are not assigned to this user.
if(count($currentPermissions)>0) {
return $currentPermissions;
} else {
return array('DEFAULT_PERMISSION');
}
}
So I figured out the raw sql to achieve what I wanted which is below, but I would like to know the Symfony/Doctrine annotated way of achieving the following raw SQL.
SELECT t0.id AS id_1, t0.namespace AS namespace_2, t0.name AS name_3, t0.description AS description_4
FROM mo_permission t0
LEFT JOIN mo_role_permission ON t0.id = mo_role_permission.permission_id
LEFT JOIN mo_user_role ON mo_role_permission.role_id = mo_user_role.role_id
WHERE mo_user_role.user_id = 9144;
I don't think there is a proper way to achieve what you're trying to do directly through property annotations with your current setup.
You could achieve what you want with one of these solution though :
One of mo_user_role and mo_role_permission is not needed, since none of them have additional field. You should just have a mo_user_permission table generated by a ManyToMany relationship between MoUser and MoPermission, which would grant you direct access to MoPermission from MoUser's getPermissions()
Another way would be to create a service which would have a GetPermissionsFromUser(MoUser $moUser) method (for example), calling the proper query from the entity's repository, which you would call when needed.
You could still achieve what you want in your getPermissions() method with your current setup, but you would have to loop through each relation's items to build your new result array manually.
e.g. for last point :
public function getPermissions() {
$permissions = [];
foreach($this->roles as $role) {
foreach($role->getPermissions() as $permission) {
permissions[] = $permission->getNamespace();
}
}
return $permissions;
}
This would assume you have a MoRole entity, which would make sense regarding your current setup, but you didn't mention it. Otherwise, same logic could still be applied though, it's just a naming matter.
I'm pretty sure that you could do that query using Doctrine (and a QueryBuilder) like...
use Doctrine\ORM\EntityRepository
class PermissionRepository extends EntityRepository
{
//....
/**
* #param UserInterface $user
* #return array|Permission[]
*/
public function getPermissionsForUser(UserInterface $user)
{
$queryBuilder = $this->createQueryBuilder('permission');
/**
* permissions will be in a multi-dimensional array
* with a single key per array of 'namespace'
*/
$permissions = $queryBuilder
->select('permission.namespace')
->join('permission.role', 'role')
->join('role.user', 'user')
->where($queryBuilder->expr()->eq('user', ':user'))
->setParameter('user', $user)
->getQuery()
->getArrayResult();
if (count($permissions) > 0) {
/**
* If there are any permissions found just return
* the 'namespace' property from each "sub-array"
*/
return array_column($permissions, 'namespace');
}
return ['DEFAULT_PERMISSION'];
}
//...
}
And then you would call it like..
$permissions = $repository->getPermissionsForUser($user);

Add 'where' in query with foreach or something similar

this is my problem.
product_field_glue table:
id | field_id | value
----------------------
1 | 50 | blue
2 | 51 | wood
3 | 50 | red
4 | 35 | car
I need to search products by this fields according _GET params
/?field[50][]=blue&field[35][]=car
this should return all blue cars
$result = Products::select('products.*')
->leftJoin('product_field_glue', function ($join) {
$join->on('product_field_glue.product_code', '=', 'products.product_code');
})
->where(function($query) use ($id, $value){
$query->where('product_fields.field_id', $id);
$query->where('product_fields.value', $value);
})
This is modified (part of) query. As you can see I need to set 'where' for all _GET['field'] and $key=>$value of GET parameter need to be in grouped where.
Maybe there is better solution for this kind of search which you can recommend.
I like to use scopes for this.
Inside your Products class:
/**
* Warning: requires a join on product_field_glue table
*
* #param \Illuminate\Database\Eloquent\Builder $query
* #param array $fields
* #return \Illuminate\Database\Eloquent\Builder
*/
public function scopeSearchFields($query, $fields) {
if (empty($fields)) {
return $query;
}
foreach ($fields as $id => $values) {
$query = $query->where(function ($subquery) use($id, $values) {
$subquery->where('product_field_glue.field_id', $id);
$subquery->whereIn('product_field_glue.value', $values);
});
}
return $query;
}
Notice that the method is called scopeSearchFields. You can attach it to any Products query as a chained searchFields method:
$result = Products::select('products.*')
->leftJoin('product_field_glue', function ($join) {
$join->on('product_field_glue.product_code', '=', 'products.product_code');
})
->searchFields($request->input('fields'))
->get();
The beauty of this is that it's extremely reusable and keeps all of the if and for loops inside of the scope method.

Saving a hasOne association in cakephp 3

I have two Models(or Tables in Cakephp 3), one of them is Department and the other is Manager, I did a 'hasOne' association between those two:
Department Table (Database):
------------------------
| id | name | location |
------------------------
| 1 | IT | New York |
------------------------
Manager Table (Database):
-----------------------------------------
| id | name | address | department_id |
-----------------------------------------
| 1 | Mike | New York | 1 |
-----------------------------------------
DepartmentsTable.php
<?php
namespace App\Model\Table;
use Cake\ORM\Table;
use Cake\Validation\Validator;
class DepartmentsTable extends Table {
public function initialize(array $config){
}
}
ManagersTable.php
<?php
namespace App\Model\Table;
use Cake\ORM\Table;
use Cake\Validation\Validator;
class ManagersTable extends Table {
public function initialize(array $config){
$this->hasOne('Departments');
}
}
I created a Form so I can save a person with his address :
<?php echo $this->Form->create($manager); ?>
<?php echo $this->Form->input('Manager.name'); ?>
<?php echo $this->Form->input('Manager.address'); ?>
<?php echo $this->Form->input('Manager.department.name'); ?>
<?php echo $this->Form->input('Manager.department.location'); ?>
<?php echo $this->Form->end('valider'); ?>
and this is the controller :
<?php
namespace App\Controller\Adminocity;
use App\Controller\AppController;
use Cake\Core\Configure;
use Cake\Network\Exception\NotFoundException;
use Cake\View\Exception\MissingTemplateException;
use Cake\Cache\Cache;
use Cake\ORM\TableRegistry;
use Cake\Event\Event;
class ManagersController extends AppController {
public function edit_manager($id=null)
{
$this->loadModel('Departments');
$manager = $this->Managers->newEntity();
if($this->request->is('post')){
$managersTable = TableRegistry::get('Managers');
$manager = $managersTable->patchEntity($manager,$this->request->data(), ['associated' => ['Departments']]);
if($managersTable->save($manager)){
$this->Flash->success(__('Manager ajouté avec succès'));
return $this->redirect(['action' => 'list_managers']);
}
}
$this->set('manager', $manager);
}
}
When I try to submit the form after filling it, the two new rows of 'Departments' and 'Manager' are added successfully, but the new row of 'Manager' has the value 'NULL' on 'department_id' column instead of the value of the id of the new 'Department' row recently created.
Please, can anyone help me solve this problem ?
The relationship type should be belongsTo()
As a general rule, you can know when to use belongsTo or hasOne:
belongsTo: The foreign key is in the same table (Managers has a department_id)
hasOne: The foreign key is in the other table. (Department hasOne Manager)