laravel 9 route model binding with multiple optional parameters - mysql

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.

Related

How to Fetch data from two tables in cakephp 3

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');
}
}

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.

elm - updating a record within a record

What is the correct/best way to update a record within a record?
The following attempt:
type alias Model =
{ pageView : PageView
, landingPageModel : Dict
}
--update
update : Action -> Model -> Model
update action model =
case action of
ChangePage pView ->
{ model | pageView = pView }
PostCode pCode ->
let
lPModel =
model.landingPageModel
newlPModel =
{ lPModel | postCode = pCode }
in
{ model | landingPageModel = newlPModel }
gave this error:
The type annotation for `update` does not match its definition.
19│ update : Action -> Model -> Model
^^^^^^^^^^^^^^^^^^^^^^^^
The type annotation is saying:
Action
-> { ..., landingPageModel : Dict }
-> { ..., landingPageModel : Dict }
But I am inferring that the definition has this type:
Action
-> { ..., landingPageModel : { a | postCode : String } }
-> { ..., landingPageModel : { a | postCode : String } }
This is somewhat surprising - isn't a literal Dict update of type Dict?
I simply needed to expand the Model definition :
type alias Model =
{ pageView : PageView
, landingPageModel : { postCode : String }
}
Also, as per halfzebra's comment, Dict's have no relevance here - just records.

PDO - Update And Set Query (2 arrays)

So I have two arrays that I need to update and set with in MySQL. item_id [1,2,3] and item_order[2,1,3]
Here is the items table before the array insert:
item_id item_order
1 1 //should become 2
2 2 // should become 1
3 3 // should become 3
The arrays should be in pairs for the insert, 1-2, 2-1, 3-3.
How can I do this with a prepared statement efficiently and how can I test if the array items are indeed numbers?
Assuming you have input like this:
$item_id = array(1, 2, 3);
$item_order = array(2, 1, 3);
// and a PDO connection named $pdo
You can try something like this. (I'm also assuming you have PDO configured to throw exceptions when problems arise).
function all_numbers($input) {
foreach($input as $o) {
if(!is_numeric($o)) {
return false;
}
}
return true;
}
if(count($item_id) != count($item_order)) {
throw new Exception("Input size mismatch!");
}
if(!all_numbers($item_id) || !all_numbers($item_order)) {
throw new Exception("Invalid input format!");
}
$pairs = array_combine($item_id, $item_order);
// now $pairs will be an array(1 => 2, 2 => 1, 3 => 3);
if($pdo->beginTransaction()) {
try {
$stmt = $pdo->prepare('UPDATE `items` SET `item_order` = :order WHERE `item_id` = :id');
foreach($pairs as $id => $order) {
$stmt->execute(array(
':id' => $id,
':order' => $order,
));
}
$pdo->commit();
} catch (Exception $E) {
$tx->rollback();
throw $E;
}
} else {
throw new Exception("PDO transaction failed: " . print_r($pdo->errorInfo(), true));
}
But it might be better to redesign your input - only pass the item_ids in the desired order and compute their item_order values automatically.
Here is an example:
UPDATE mytable
SET myfield = CASE other_field
WHEN 1 THEN 'value'
WHEN 2 THEN 'value'
WHEN 3 THEN 'value'
END
WHERE id IN (1,2,3)

Does linq to sql have a with ties option?

I'm trying to move the following query to Linq-to-sql, is it possible?
select * from (
Select top (#Percent) percent with ties *
from(
Select distinct
LoanNumber as LoanNo
From CHE
Left Join RecordingInfo as Rec
On CHE.LoanNumber = Rec.LoanNo
Where Channel = 'LINX'
and CHE.Doc in ('MTG','MOD')
and Rec.LoanNo is null
and LoanNumber >= '#LoanNo'
) A
order by LoanNo #Order
) B
order by LoanNo
I have not seen anyway to do with ties in linq.
I think something like this will work for you.
public static IQueryable<T> TopPercentWithTies<T, TKey>(this IOrderedQueryable<T> query, Expression<Func<T, TKey>> groupByExpression, double percent)
{
var groupedQuery = query.GroupBy(groupByExpression);
int numberToTake = groupedQuery.Count() * percent / 100;
return groupedQuery.Take(numberToTake).SelectMany(t => t);
}
I only tested it with IEnumerable, so I don't know for sure that it'll work properly with IQueryable. I also sorted the list before calling TopPercentWithTies().
Here's the code I used to test it.
int percent = 50;
var people = new []
{
new { Age = 99, Name = "Adam" },
new { Age = 99, Name = "Andrew" },
new { Age = 89, Name = "Bob" },
new { Age = 50, Name = "Cecil" },
new { Age = 50, Name = "Doug" },
new { Age = 50, Name = "Everett" },
new { Age = 35, Name = "Frank" },
new { Age = 25, Name = "Greg" },
new { Age = 15, Name = "Hank" }
};
var sortedPeople = people.AsQueryable().OrderByDescending(person => person.Age);
var results = sortedPeople.TopPercentWithTies(person => person.Age, percent);
foreach (var person in results)
Console.WriteLine(person);
Hope it helps or at least gets you in the right direction. You may want to tweak the logic for calculating numberToTake.