How to Fetch data from two tables in cakephp 3 - mysql

I am new in cakephp and my table like:
city
id | name
1 | city1
2 | city2
state
id | name | cityid
1 |state1| 2
so how do i get the city name if i having state id.
In controller i have code like this.
public function getCity()
{
if( $this->request->is('ajax') ) {
$this->autoRender = false;
}
if ($this->request->isPost()) {
$sId= $this->request->data['stateid'];
}
}
In the $sId i get value so how do i write query.

If you have a BelongsTo relationship between both Model, you just have to do a query on the States which contains City:
public function getCity()
{
if( $this->request->is('ajax') ) {
$this->autoRender = false;
}
if ($this->request->isPost()) {
$stateEntity = $this->States->find('all')
->where(['id' => $this->request->data['stateid']])
->contain(['Cities'])
->first();
// Now the State Object contains City
$cityName = $stateEntity->city->name;
}
}
To create this relationship you need to do like this:
class StatesTable extends Table
{
public function initialize(array $config)
{
$this->belongsTo('Cities')
->setForeignKey('city_id')
->setJoinType('INNER');
}
}

Related

laravel 9 route model binding with multiple optional parameters

I have this route defined:
Route::get('/categories/{category}/{country}/{state?}/{county?}/{city?}', ['App\Http\Controllers\LocationController', 'show'])->withScopedBindings();
public function show(Category $category, Country $country, State $state = null, County $county = null, City $city = null) {
echo 'ok';
}
It's fine, automatically checks for relationships, works with 1, 2 or 3 of optional parameters. But... I want to extend it so it that the COUNTY is not always mandatory. Cause there are some cities that have direct relationship to state without county_id in the middle. Cities table has county_id and also state_id and always only one of them is present. If I add:
Route::get('/categories/{category}/{country}/{state?}/{city?}', ['App\Http\Controllers\LocationController', 'show'])->withScopedBindings();
Only one of the routes are working.
How can I fix this? Thanks.
You can define two separate routes
Route::get(
'/categories/{category}/{country}/{state?}/{county?}',
['App\Http\Controllers\LocationController', 'show']
)
->withScopedBindings()
->name('categories.show.county');
Route::get(
'/categories/{category}/{country}/{state?}/{county?}/{city?}',
['App\Http\Controllers\LocationController', 'show']
)
->withScopedBindings()
->name('categories.show.county.city');
And then check for the route name in the controller
public function show(
Category $category,
Country $country,
State $state = null
) {
if(
request()->route()->named('categories.show.county') &&
request()->route()->hasParameter('county')
) {
$county = County::where(
(new County)->getRouteKeyName(),request()->route('county')
)
->firstOrFail();
}
if(request()->route()->named('categories.show.county.city')) {
if(request()->route()->hasParameter('county') {
$county = County::where(
(new County)->getRouteKeyName(),request()->route('county')
)
->firstOrFail();
}
if(request()->route()->hasParameter('city')) {
$city = City::where(
(new City)->getRouteKeyName(),request()->route('city')
)
->firstOrFail();
}
}
}
So, following Donkarnash answer I finally have a solution.
Route::get('/categories/{category}/{country}/{state?}/{county_slug?}/{city_slug?}',
['App\Http\Controllers\LocationController', 'show'])->scopeBindings();
public function show(
Category $category,
Country $country,
State $state = null,
$county_slug = null,
$city_slug = null
) {
if ($county_slug && $city_slug)
{
// two parameters present, that means the chain is state -> county -> city
$county = County::where('state_id', $state->id)
->where('slug', $county_slug)
->firstOrFail();
$city = City::where('county_id', $county->id)
->where('slug', $city_slug)
->firstOrFail();
} else {
if ($county_slug) {
// one parameter present, that means the chain is state -> county OR state -> city
$county = County::where('state_id', $state->id)
->where('slug', $county_slug)
->first();
if (!$county) {
$city_slug = $county_slug;
$city = City::where('state_id', $state->id)
->where('slug', $city_slug)
->first();
}
if (!$county && !$city) {
abort(404);
}
}
}
}
And then in migrations states table:
$table->unique(['slug', 'country_id']);
Counties table:
$table->unique(['slug', 'state_id']);
Cities table:
$table->unique(['slug', 'state_id']);
$table->unique(['slug', 'county_id']);
And it works. Only drawback is that if there is a county and a city with the same slug that belong to the same state. For example, county with slug "test" and state_id "15" and city with slug "test" and state_id "15". Then it won't work correctly and result as county. But usually ALL cities for a country have a chain of country -> state -> county -> city OR country -> state -> city, so this minor drawback won't affect final results for the website. Nevertheless, this also can be fixed by adjusting request validation rules.

Yii2 - Where clause for 1:M relation

I have 2 tables :
Product Option Group
id | opt_name | active_flag
------------------------------
1 | Cook level | 0
Product Option List
id | optgrp_id | list_name | active_flag
------------------------------------------
1 | 1 | 25 | 0
2 | 1 | 50 | 1
3 | 1 | 75 | 0
4 | 1 | 100 | 0
Product Option Group Model
public function getOptList()
{
return $this->hasMany(ProdOptlist::className(),['optgrp_id'=>'id']);
}
Product Option List Model
public function getOptGrp()
{
return $this->hasOne(ProdOptgrp::className(),['id'=>'optgrp_id']);
}
Product Option Group Controller
public function actionUpdate($id)
{
$model = $this->findModel($id);
if($model->load(Yii::$app->request->post()) && $model->validate())
{
...
}
else
return $this->render('update', ['model'=>$model]);
}
protected function findModel($id)
{
if (($model = ProdOptgrp::find()
->joinWith('optList')
->where([ProdOptgrp::tableName().'.id'=>$id,
ProdOptgrp::tableName().'.active_flag'=>0,
ProdOptlist::tableName().'.active_flag'=>0])
->one()) !== null) {
return $model;
}
throw new NotFoundHttpException('The requested page does not exist.');
}
Update View
Expected output for print_r($model->optList) :
{
[id] => 1
[optgrp_id] => 1
[list_name] => 25
[active_flag] => 0
},
{
[id] => 3
[optgrp_id] => 1
[list_name] => 75
[active_flag] => 0
},
{
[id] => 4
[optgrp_id] => 1
[optList_name] => 100
[active_flag] => 0
}
Actual output :
{
[id] => 1
[optgrp_id] => 1
[list_name] => 25
[active_flag] => 0
},
{
[id] => 2
[optgrp_id] => 1
[list_name] => 50
[active_flag] => 1
},
{
[id] => 3
[optgrp_id] => 1
[list_name] => 75
[active_flag] => 0
},
{
[id] => 4
[optgrp_id] => 1
[optList_name] => 100
[active_flag] => 0
}
Yii2 debugger showing correct query but output still consist of all 4 elements.
Kindly advice if there is any mistake, thank you in advance :)
Even if you use the joinWith() the where() part of your $model = ProdOptgrp::find()... code only limits the result that is returned by the query executed by one() method call. It doesn't affect what is loaded for relations.
If you want to limit what is loaded for relations you can do it:
1) By modifing the existing relation
This is the solution you've come to. You add the where condition directly to the getOptList() method of your ProdOptgrp model. If you do it this way, the $model->optList will always return filtered relation. Depending on case that might be advantage or disadvantage.
2) By adding second relation
You can create another method in your ProdOptgrp model that will define the filtered relation while keeping the original getOptList() unfiltered.
For example like this:
class ProdOptgrp extends \yii\db\ActiveRecord
{
public function getOptList()
{
return $this->hasMany(ProdOptlist::className(),['optgrp_id'=>'id']);
}
public function getFilteredOptList()
{
return $this->getOptList()->where([
ProdOptlist::tableName() . '.active_flag' => 0
]);
}
}
In this case the $model->optList will still contain unfiltered OptLists and the $model->filteredOptList will contain filtered. This solution is good when you need to use both at different spots of your application.
3) By using callback syntax in joinWith() or with()
In case you want to filter the relation only in one particular case you don't need to modify your model. You can modify the relation with callback that is called before loading the data.
$model = ProdOptgrp::find()
->joinWith([
'optList' => function(\yii\db\ActiveQuery $query) {
$query->where([
ProdOptlist::tableName() . '.active_flag' => 0
]);
}
])->where([
ProdOptgrp::tableName().'.id'=>$id,
ProdOptgrp::tableName().'.active_flag'=>0,
ProdOptlist::tableName() . '.active_flag'=>0,
])->one();
The last condition in where() will cause the $model to be null when the active_flag is 0 in optgrp table but 1 in all related records in optlist table. I'm not sure if that is inteded behavior.
You can do it this way too:
//Controller
protected function findModel($id)
{
if (($model = ProdOptgrp::findOne($id)) !== null && $model->isActive()) {
return $model;
}
throw new NotFoundHttpException('The requested page does not exist.');
}
//Model
/*
*Check if model is active
*/
public function isActive(){
return ($this->active_flag == 0) ? true: false;
}
public function getOptList()
{
return $this->hasMany(ProdOptlist::className(),['optgrp_id'=>'id'])->where([ProdOptgrp::tableName().'.active_flag'=>0]);
}
//View
foreach($model->optList as optItem): // you get filtered result

How to handle Many To Many relationship with three or more classes with Laravel and MySQL

I'm working on a project with Laravel and Eloquent/MySQL.
I'd like to know how to handle a many-to-many relationship with three tables (users, merchants, roles).
Any user can have one or more merchant.
Any merchant can be shared between users.
Any user have a specific role for a merchant.
Is there a best practice to follow?
How can I get all the merchants by a user having a specific role?
Thanks for helping
This is my current structure:
Users
-------------------------------
| id | first_name | last_name |
-------------------------------
Merchants
-------------------
| id | trade_name |
-------------------
Roles
-------------------------
| id | name | hierarchy |
-------------------------
merchant_user_role
-----------------------------------
| merchant_id | user_id | role_id |
-----------------------------------
Merchant Model
<?php
class Merchant extends Model
{
public function users()
{
return $this->belongsToMany('App\User', 'merchant_user_role')
->withPivot('role_id')
->withTimestamps()
->orderBy('first_name', 'asc');
}
}
User Model
<?php
class User extends Model
{
public function merchants()
{
return $this->belongsToMany('App\Merchant', 'merchant_user_role')
->withPivot('role_id')
->withTimestamps()
->orderBy('trade_name', 'asc');
}
public function roles()
{
return $this->belongsToMany('App\Role', 'merchant_user_role')
->withTimestamps();
}
}
Role Model
<?php
class Role extends Model
{
public function users()
{
return $this->belongsToMany('App\User')->withTimestamps();
}
}
$merchants = $user->merchants()->where('role_id', $some_role_id)->get();

Improve SQL queries speed

I'm using MySQL and i have schema like:
|------------|-------------|------------|--------------|
| cities |category_city| categories| companies |
|------------|-------------|------------|--------------|
| id | city_id | id | id |
| name | category_id | name |subcategory_id|
| | | parent_id | city_id |
| | | |...other cols |
|____________|_____________|____________|______________|
Relationships:
City with Category has ->belongsToMany()
public function categories()
{
return $this->belongsToMany(Category::class);
}
Categories has subcategories:
public function subcategories()
{
return $this->hasMany(Category::class, 'parent_id', 'id');
}
And i'm getting companies from category and filtering by city, because i need the current city companies and for that i have a global scope:
public function getCompanies($city_id)
{
return $this->companies()->whereHas('mainCity', function ($q) use ($city_id) {
$q->where('city_id', $city_id);
});
}
mainCity method:
public function mainCity()
{
return $this->belongsTo(City::class, 'city_id');
}
Here is my method performing the query with AJAX request:
public function getPlaces(City $city, Category $subcategory, $north, $south, $east, $west)
{
$companies = $subcategory->companies()
->withCount('comments')
->companiesByBounds($north, $south, $east, $west)
->paginate(8);
$markers = $subcategory->companies()
->companiesByBounds($north, $south, $east, $west)
->get(['lat', 'lng', 'slug', 'title']);
return response()->json(['companies' => $companies, 'markers' => $markers], 200, [], JSON_NUMERIC_CHECK);
}
and by companiesByBounds scope method:
public function scopeCompaniesByBounds($query, $north, $south, $east, $west)
{
return $query->whereBetween('lat', [$south, $north])
->whereBetween('lng', [$west, $east]);
}
In companies i have ~2m records. The main problem is that the queries taking 3.5 seconds. Help please to improve my queries.
Here is the query:
select count(*) as aggregate from `companies` where `companies`.`category_id` = '40' and `companies`.`category_id` is not null and `lat` between '53.68540097020851' and '53.749703253622705' and `lng` between '91.34262820463869' and '91.51600619536134'
To improve speed you need to add indexes on the columns lat and lng.
CREATE INDEX idx_lat ON companies (lat);
The indexes are used in queries when the columns are added to conditions.

Laravel 4.2 Multiple Query in date range

I'm trying to get data for each date in range from 3 tables: Main table which is connected with 2 other tables using hasMany method.
Data stored as in example:
Main Table:
id | article title | url | created_at | updated_at |
----------------------------------------------------------------
14 | Some Title | www.example.com | TIMESTAMP | TIMESTAMP |
Views table (there is written count of views for each hour):
id | article_id | views | created_at | updated_at |
---------------------------------------------------
1 | 14 | 317 | TIMESTAMP | TIMESTAMP | (01:00:00)
2 | 14 | 186 | TIMESTAMP | TIMESTAMP | (02:00:00)
Clicks Table (there is written every click on this article):
id | article_id | ip_adress | created_at | updated_at |
---------------------------------------------------------
1 | 14 | 192.168.1.1 | TIMESTAMP | TIMESTAMP |
For example:
I need to get Articles from 01-02-2016 to 01-03-2016.
For every article I need to sum views and clicks for each day.
So in result i need to get something like this:
ID: 14, Title: Some Title, Views: 503, Clicks: 27
First, I wrote this code, but it makes lots of requests to database:
$dates = new DatePeriod($start, new DateInterval('P1D'), $stop);
foreach ($dates as $i => $date) {
$articles = Articles::with(['views' => function ($query) use ($date) {
$query->where('created_at', $date);
}, 'clicks' => function ($query) use ($date) {
$query->where('created_at', $date);
}])->get();
foreach ($articles as $article) {
foreach ($views->countOfViews as $i) {
// Code
}
foreach ($clicks->countOfClicks as $i) {
// Code
}
// Code
}
}
Then I found solution, to get exact same result as I'm getting in first example, but making only three requests:
$dates = new DatePeriod($start, new DateInterval('P1D'), $stop);
$articles = Articles::with(['views' => function ($query) use ($start, $stop) {
$query->whereBetween('created_at', array($start, $stop));
}, 'clicks' => function ($query) use ($start, $stop) {
$query->whereBetween('created_at', array($start, $stop));
}])->get();
foreach ($dates as $date) {
foreach ($articles as $article) {
foreach ($views->countOfViews as $i) if ($date->format('Y-m-d') === $i->created_at->toDateString()) {
// Code
}
foreach ($clicks->countOfClicks as $i) if ($date->format('Y-m-d') === $i->created_at->toDateString()) {
// Code
}
// Code
}
}
It solves problem with too many queries, but it takes to much time. Is it possible to do same thing faster?
Final Code:
$dates = new DatePeriod($start, new DateInterval('P1D'), $stop);
$articles = Articles::with(['views' => function ($query) use ($start, $stop) {
$query->whereBetween('created_at', array($start, $stop))
->groupBy('campaign_id', DB::raw('DATE(created_at)'))
->selectRaw('*, sum(views) as views');
}, 'clicks' => function ($query) use ($start, $stop) {
$query->whereBetween('created_at', array($start, $stop))
->groupBy('campaign_id', DB::raw('DATE(created_at)'))
->selectRaw('*, sum(clicks) as clicks');
}])->get();
foreach ($dates as $date) {
foreach ($articles as $article) {
foreach ($views->countOfViews as $i) if ($date->format('Y-m-d') === $i->created_at->toDateString()) {
// Code
}
foreach ($clicks->countOfClicks as $i) if ($date->format('Y-m-d') === $i->created_at->toDateString()) {
// Code
}
// Code
}
}