Yii2 Routing rules conflicts with each other - php

I have the following 2 rules:
'blog/<action>' => 'blog/default/<action>',
'blog/<slug:[0-9a-zA-Z\-.]+>' => 'blog/default/view',
Also I have the following actions:
public function actionCheckSlug($slug) {
}
public function actionCreate() {
}
public function actionView($slug) {
return $this->render("view");
}
When I try to access this URL for example (action URL):
/blog/check-slug?slug=test
It's working without any problems but when I try to access this URL for example (Slug URL):
/blog/test-test-test
I will get an error:
yii\base\InvalidRouteException: Unable to resolve the request: blog/default/test-test-test
Because the fist rules is being parsed instead of the second one.
I tried to reverse them for example but it didn't work (always one is not working), also tried others scenarios but no success
Any idea how to make it works?

i would suggest first of all not using the same url convention for both actions
'blog/<action>' => 'blog/default/<action>',
'blog/<slug:[0-9a-zA-Z\-.]+>' => 'blog/default/view',
could easily become
'blog/<action>' => 'blog/default/<action>',
'post/<slug:[0-9a-zA-Z\-.]+>' => 'blog/default/view',
or use 'blog/page-<slug:[0-9a-zA-Z\-.]+>', blog/post/ .. or really just any convention that doesn't clash with your existing structure
if that's not something just you wanna do, or cant? in your app, you can just use the slug to check for existing app structure.
public function view($slug){
$model = $this->findBySlug($slug);
return $this->render('view', ['model' => $model]);
}
private function findBySlug($slug){
if ($this->hasMethod('action' . Inflector::classify($slug))
// this should prevent recursion
&& $slug != $this->action->id){
$this->runAction($slug);
return null;
}
return Post::find()->where(['slug' => $slug])
}
note: this is just an example of how to (or how not to?). don't run my bad, untested code in any production environment

Related

Laravel 9 multiple tag flush not working when flushing individual tags

I've noticed an issue with my Laravel application, and i'm not quite sure if i'm just doing something stupidly wrong, or there is a genuine issue.
So i'm storing and fetching my cached data with multiple tags on my App\Models\User\User model like below
public function getSetting(String|Bool $key = false) : Mixed {
//Try
try {
return cache()->tags(["user_{$this->id}_cache", 'user_settings'])->remember("user_{$this->id}_settings", now()->addDays(1), function(){
return $this->settings()->pluck('value', 'key')->toArray();
});
} catch(\Exception $e){
//Logging errors here
}
}
This function simply grabs all of the users settings and returns an array.
I am using 2 cache tags because I want to cover both scenarios
The ability to be able to remove all cached items for a specific model (User)
The ability to be able to remove a specific type of cache across all models (Users)
The Laravel cache documentation simply states to pass the tag (or tags as an array) that you want to remove.
So my thinking is that if I want to clear user settings cache for all users, I should be able to run the following
cache()->tags('user_settings')->flush();
and if I want to remove all cache for a specific user, I should be able to run
cache()->tags('user_1_cache')->flush();
But for some reason, only the second example (using user_1_cache) works? If I run the first example and try to clear all cache with the tags user_settings, the function returns true but does not clear the cache?
Am I doing something stupidly wrong or just completely misunderstanding how the cache tags work?
Versions
PHP - 8.1
Laravel - 9.3.8
Cache driver - Redis
I reproduced your scenario here. It's working as stated in the docs.
class User extends Model
{
protected $fillable = ['id', 'name'];
public function cacheSettings()
{
return cache()->tags([$this->getUserCacheKey(), 'user_settings'])->remember("{$this->id}_settings", now()->addDay(), function () {
return $this->only('name');
});
}
public function getSettings()
{
return cache()->tags([$this->getUserCacheKey(), 'user_settings'])->get("{$this->id}_settings");
}
public function getUserCacheKey()
{
return "user_{$this->id}_cache";
}
}
These tests run with no problem:
public function test_cache_flush_all_users()
{
Cache::clear();
$alice = new User(['id' => 1, 'name' => 'alice']);
$john = new User(['id' => 2, 'name' => 'john']);
$alice->cacheSettings();
$john->cacheSettings();
Cache::tags('user_settings')->flush();
// both deleted
$this->assertNull($alice->getSettings());
$this->assertNull($john->getSettings());
}
public function test_cache_flush_specific_user()
{
Cache::clear();
$alice = new User(['id' => 1, 'name' => 'alice']);
$john = new User(['id' => 2, 'name' => 'john']);
$alice->cacheSettings();
$john->cacheSettings();
Cache::tags($alice->getUserCacheKey())->flush();
// only alice deleted
$this->assertNull($alice->getSettings());
$this->assertNotNull($john->getSettings());
}
Not having all details of your implementation, perhaps you can figure out what is causing the issue.

Laravel response transformations not working

I have the following
controller function
public function show()
{
$users_id = Request::segment(4);
// $this->user_bank_details_repository->setPresenter(new UserBankAccountPresenter);
$account = $this->user_bank_details_repository->findByField('users_id', $users_id, $columns = ['user_bank_details_id','bank_name','bank_account_number','bank_ifsc_code','beneficiary_name','bank_account_type','bank_branch','created_at']);
if(!$account)
{
return $this->response->noContent();
}
return $this->response->item($account, new UserBankAccountTransformer);
}
Transformer
<?php
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
namespace App\Api\V1\Transformers;
use App\Entities\UserBankDetails;
use League\Fractal\TransformerAbstract;
class UserBankAccountTransformer extends TransformerAbstract {
public function transform(UserBankDetails $bank_details)
{
return [
'id'=> (int) $bank_details->user_bank_details_id,
'bank_name' => $bank_details->bank_name,
'bank_account_number' => (int) $bank_details->bank_account_number,
'bank_account_type' => $bank_details->bank_account_type,
'bank_beneficiary_name' => $bank_details->beneficiary_name,
'bank_branch'=> $bank_details->bank_branch
];
}
}
I am using repository pattern design and using dingo for my REST framework.
I always get the response as
{
"user_bank_details": [
{
"user_bank_details_id": 1,
"bank_name": "jbjb",
"bank_account_number": "939393933939",
"bank_ifsc_code": "ABCD0000047",
"beneficiary_name": "Gaf",
"bank_account_type": "savings",
"bank_branch": "Mad(E)",
"created_at": "2015-12-23 17:05:39"
}
]
}
instead of the transformed json. I dont get any errors as well. Tried using
return $this->response->item($account, new UserBankAccountTransformer::class);
But still the response is not getting transformed. I tried whatever I could but I dont get it worked :(
Even though this question is over a year ago, however for the sake of anyone who would have similar issue, the suspect here is the fact that you used: item instead of collection.
If the $account contains a collection, then it means this answer is correct: that is, this:
return $this->response->item($account, new UserBankAccountTransformer::class);
Should be:
return $this->response->collection($account, new UserBankAccountTransformer::class);
PS: I also Encountered this on Laravel 5.2.* thats why I feel this might help.

Codeigniter URL Routing - Capture Everything for Tree Structure

OK, I think I am probably (hopefully) going to be told I am going about this in the wrong way.
Currently, if I go to root of CI web I call a function which reads a predefined location with map_directory(). I then iterate it out in a view as a simple directory listing.
I then want to click on one of these directories to see what's inside. When I do that I call a different controller function called browse.
So if I click on one link I go to
www.mysite.com/dir1
(which is Routed to www.mysite.com/controller/browse/$1 - where $1 in this instance = dir1).
Now I am presented with a dir listing of dir1. I have configured the links of the displayed listings to now go to:
www.mysite.com/dir1/subdir1 etc.
What I want to do and this might be the bit where I am cheating/going wrong, is capture everything after
www.mysite.com/
and pass it to
www.mysite.com/controller/browser/$1
So example:
www.mysite.com/dir1/subdir1/ => www.mysite.com/controller/browse/"dir1/subdir1"
I know I can't have '"' in there, but that's the bit I am trying to pass to the map_directory() function...so it goes /.$var (where $var would = $1 = "dir1/subdir1".
So far I have tried in CI Routes.php:
$route['(.+)$'] = 'controller/browse/$1';
$route['([a-zA-Z0-9\/]*)'] = 'controller/browse/$1';
$route['(:any)'] = 'controller/browse/$1';
...but they all only ever seem to capture "dir1" never anything beyond that.
I hope that makes sense to someone....
You can just use the special _remap() method in your controller to override the default routing behavior:
class Controller extends CI_Controller
{
protected function browse($path)
{
// ... do something with $path here
}
public function _remap($method, $params = array())
{
if ($method === 'browse')
{
$params = implode('/', $params);
return $this->browse($params);
}
elseif (method_exists($this, $method))
{
return call_user_func_array(array($this, $method), $params);
}
show_404();
}
}

Laravel - using controllers instead of routes for actions

I've literally downloaded Laravel today and like the looks of things but i'm struggeling on 2 things.
1) I like the controllers' actions method of analysing urls instead of using routes, it seems to keep everything together more cleanly, but lets say I want to go to
/account/account-year/
how can I write an action function for this? i.e.
function action_account-year()...
is obviously not valid syntax.
2) If i had
function action_account_year( $year, $month ) { ...
and visited
/account/account_year/
An error would be displayed about missing arguments, how do you go about making this user friendly/load diff page/display an error??
You would have to manually route the hyphenated version, e.g.
Route::get('account/account-year', 'account#account_year');
Regarding the parameters, it depends on how you are routing. You must accept the parameters in the route. If you are using full controller routing (e.g. Route::controller('account')) then the method will be passed parameters automatically.
If you are manually routing, you have to capture the params,
Route::get('account/account-year/(:num)/(:num)', 'account#account_year');
So visiting /account/account-year/1/2 would do ->account_year(1, 2)
Hope this helps.
You can think of the following possibility as well
class AccountController extends BaseController {
public function getIndex()
{
//
}
public function getAccountYear()
{
//
}
}
Now simply define a RESTful controller in your routes file in the following manner
Route::controller('account', 'AccountController');
Visiting 'account/account-year' will automatically route to the action getAccountYear
I thought I'd add this as an answer in case anyone else is looking for it:
1)
public function action_account_year($name = false, $place = false ) {
if( ... ) {
return View::make('page.error' );
}
}
2)
not a solid solutions yet:
laravel/routing/controller.php, method "response"
public function response($method, $parameters = array())
{
// The developer may mark the controller as being "RESTful" which
// indicates that the controller actions are prefixed with the
// HTTP verb they respond to rather than the word "action".
$method = preg_replace( "#\-+#", "_", $method );
if ($this->restful)
{
$action = strtolower(Request::method()).'_'.$method;
}
else
{
$action = "action_{$method}";
}
$response = call_user_func_array(array($this, $action), $parameters);
// If the controller has specified a layout view the response
// returned by the controller method will be bound to that
// view and the layout will be considered the response.
if (is_null($response) and ! is_null($this->layout))
{
$response = $this->layout;
}
return $response;
}

Creating canonical URLs with custom route-classes

I'm trying to implement canonical URLs and combine it with custom route-classes.
The URL-scheme is something like this:
/category-x/article/123
/category-y/article/123
I create a custom route-class extending Zend_Controller_Router_Route_Regex and checks that the article 123 exists and that the URL includes the correct category-name. If article 123 belongs in category-x and the user is accessing category-y I want to redirect to the correct URL.
But the routes does not have any obvious possibility to do this directly. What's the best practice approach here?
I often do this in my action controller. Something like this...
// assuming GET /category-y/article/123
// $article->url is generated, and contains /category-x/article/123
if (this->_request->getRequestUri() != $article->url) {
return $this->_helper->redirector->goToUrl($article->url);
}
In this example, $article->url would need to be generated from your database data. I often use this to verify a correct slug, when I also pull in the object id.
You could also potentially move this to your routing class, if you wanted to use a custom one instead of using Regex (you could subclass it).
I ended up with this solution:
The custom route-class creates the canonical URL in its match()-method like this:
public function match($path, $partial = false) {
$match = parent::match($path, $partial);
if (!empty($match)) {
$article = $this->backend->getArticle($match['articleId']);
if (!$article) {
throw new Zend_Controller_Router_Exception('Article does not exist', 404);
}
$match['canonicalUrl'] = $this->assemble(array(
'title' => $article->getTitle(),
'articleId' => $article->getId()
));
}
return $match;
}
$article is populated inside match() if the parent::match() returns array.
I've created a front controller plugin which hooks on the routeShutdown() like this:
public function routeShutdown(Zend_Controller_Request_Abstract $request) {
if ($request->has('canonicalUrl')){
$canonicalUrl = $request->getBaseUrl() . '/' . $request->get('canonicalUrl');
if ($canonicalUrl != $request->getRequestUri()) {
$this->getResponse()->setRedirect($canonicalUrl, 301);
}
}
}
It simply checks if the route(custom or native Zend) created a canonical URL and if the requested URL does not match, redirect to the correct canonical URL.

Categories