Using forceRootUrl() breaks some routing - laravel

I have my Laravel app running from a subdirectory, so in order to be able to use the URL helper like this: {{ url('users') }} instead of like this {{ url('subdirectory/users') }} I use the forceRouteUrl() method in my AppServiceProvider.php file. It looks like this:
public function boot()
{
URL::forceRootUrl(Config::get('app.url'));
...
And my .env: APP_URL=http://app.dev/subdirectory
So far so good, except when returning routes. For example I have a sorting method that does this:
function sort_schedule($column, $order)
{
return route('schedule', [
'sortBy' => $column,
'sortOrder' => $order,
]
);
}
But the link it generates is this: http://app.dev/subdirectory/subdirectory/...
The same thing happens when I use Kyslik's Column Sortable package.
How can I fix this?

It seems that while other helpers like url() or asset() don't have this issue, the route() helper tends to duplicate the subpath when laravel isn't accessible directly from the site root and forceRootUrl() is used.
I'm using laravel 5.4 but this problem is present on previous versions too.
To solve this issue i found three possible solutions:
Use url() instead of route() and manually create your urls
Everytime you use route() remember to pass false as the third parameter, it will use relative paths that don't have this issue
Override the default route() helper with a custom one
I opted for the third option and created a CustomHelper.php file that contains my new route() helper.
Here are the instructions on how to override default helpers:
https://laracasts.com/discuss/channels/general-discussion/override-functions-in-supporthelpersphp
Inside my CustomHelper.php file I added this
function route($name, $parameters = [], $absolute = true)
{
$appUrl = config('app.url'); // in your case: http://app.dev
$appUrlSuffix = config('app.url_suffix'); // in your case: subdirectory
// Additional check, do the workaround only when a suffix is present and only when urls are absolute
if ($appUrlSuffix && $absolute) {
// Add the relative path to the app root url
$relativePath = app('url')->route($name, $parameters, false);
$url = $appUrl.$relativePath;
} else {
// This is the default behavior of route() you can find in laravel\vendor\laravel\framework\src\Illuminate\Foundation\helpers.php
$url = app('url')->route($name, $parameters, $absolute);
}
return $url;
}
The app.url_suffix is a custom variable that i defined in config/app.php
'url_suffix' => env('APP_URL_SUFFIX', null),
and in the .env config file
APP_URL_SUFFIX=subdirectory

Related

Integrate Twig with CodeIgniter 4

I used Twig with Symfony and I really loved it. I now have a CodeIgniter project and I want to integrate Twig with it.
I installed the latest versions of CodeIgniter and Twig via Composer and and followed this tutorial but I believe the code in the tutorial is for CI v3.
Could anyone who has integrated Twig with CI v4 help me with the proper code please.
UPDATE
solution below!
Try this I hope it will help you
Install Composer and run the following command to get the latest version:
composer require "twig/twig:^3.0"
Then after installation add this line of code to the baseController initController method just after the parent::initController, just like the code below
namespace App\Controllers;
use CodeIgniter\Controller;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use Psr\Log\LoggerInterface;
class BaseController extends Controller
{
protected $helpers = [];
protected $twig;
// protected $helper = [];
public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger)
{
parent::initController($request, $response, $logger);
$appPaths = new \Config\Paths();
$appViewPaths = $appPaths->viewDirectory;
$loader = new \Twig\Loader\FilesystemLoader($appViewPaths);
$this->twig = new \Twig\Environment($loader, [
'cache' => WRITEPATH.'/cache/twig',
]);
}
}
So with this now you can call the view files in other controllers extends to parent controller BaseController
e.g
namespace App\Controllers;
class Home extends BaseController
{
public function index ()
{
// To load a template from a Twig environment, call the load() method which returns a \Twig\TemplateWrapper instance:
$template = $this->twig->load('index.html');
// To render the template with some variables, call the render() method:
return $template->render(['the' => 'variables', 'go' => 'here']);
// The display() method is a shortcut to output the rendered template.
// OR You can also load and render the template in one fell swoop:
return $this->twig->render('index.html', ['the' => 'variables', 'go' => 'here']);
// If a template defines blocks, they can be rendered individually via the renderBlock() call:
return $template->renderBlock('block_name', ['the' => 'variables', 'go' => 'here']);
// Note any of them above will work
}
}
If you still want to use view() with twig like codeigniter 4 default view function you can modify the Common.php file in app directory
by adding this block of code below.
if (!function_exists('view'))
{
function view($tpl, $data = []) {
$appPaths = new \Config\Paths();
$appViewPaths = $appPaths->viewDirectory;
$loader = new \Twig\Loader\FilesystemLoader($appViewPaths);
$twig = new \Twig\Environment($loader, [
'cache' => WRITEPATH.'/cache/twig',
]);
if (!stripos($tpl, '.twig')) {
$tpl = $tpl . '.twig';
}
return $twig->render($tpl, $data);
}
}
Then in controller call it like this
return view('index', ['name' => 'Chibueze Agwu'])
Then in view file index.twig
<!DOCTYPE html>
<html>
<head>
<title>My Webpage</title>
</head>
<body>
<h1>My Webpage</h1>
{{ name }}
</body>
</html>
This will output
My Webpage
Chibueze Agwu
I haven't test this code but I hope it will work. If not call my attentions.
In order to obey the the rule of DRY (DO NOT REPEAT YOURSELF), you can go ahead to improve the code I will do that later
I found the solution some time ago and I'm posting it in case some people stumble across the question.
First of all, all your controllers must extend BaseController; this controller is available by default when you install CodeIgniter 4.
Create a custom helper file and put in [project-name]/appstarter/app/Helpers.
IMPORTANT
the name of your helper must be [name]_helper.php or it will not work!
for example mine is called custom_helper.php
Create the following function in the custom helper you just created:
use Twig\Environment;
use Twig\Extension\DebugExtension;
use Twig\Loader\FilesystemLoader;
use Twig\TwigFilter;
if (!function_exists('twig_conf')) {
function twig_conf() {
// the follwing line of code is the only one needed to make Twig work
// the lines of code that follow are optional
$loader = new FilesystemLoader('Views', '../app/');
// to be able to use the 'dump' function in twig files
$twig = new Environment($loader, ['debug' => true]);
$twig->addExtension(new DebugExtension());
// twig lets you create custom filters
$filter = new TwigFilter('_base_url', function ($asset) {
return base_url() . '/' . $asset;
});
$twig->addFilter($filter);
return $twig;
}
}
NOTE
before creating any custom filter, make sure Twig doesn't already has one built-in.
Now in the BaseController you'll find an empty array called $helpers. You must put the name of your custom helper in it. Mine is called custom_helper.php; so the code looks like this for me:
protected $helpers = ['custom'];
Just below the array you'll find the constructor for BaseController and this is where the Twig library will be initialized; by calling the function you created in your custom helper:
public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger) {
parent::initController($request, $response, $logger);
$this->twig = twig_conf();
}
Now you are good to go! To render your twig files in any controller:
return $this->twig->render('twig_name', $dataArray);
Try this I hope it will help you.
Install Composer and run the following command to get the latest version:
composer require "twig/twig:^3.0"

When using a {domain} wildcard, is there a way to globally set the route( ['domain' => $domain] ) property (not at a function level)

Laravel 5.5
Using a group in RouteServiceProvider for {domain}
I want to be able to call Named Routes in blade without having to pass
public function pageName($domain){
return view('mypage', ['domain'=>$domain,'othervars'=>$domain)])
}
and avoid this mess in blade:
{{ route('nameOfRoute', ['domain'=>$domain]) }}
Instead i would love to simply in my route group set the route(['domain']) property to be $domain and be done with it.
No global way to set it, but instead, wrap the route('name') default helper function with my own helper function, and add the param to the array.
Example function:
function orgRoute($route, $params = [])
{
if (!is_array($params)){
$params = [$params];
}
// Set the domain value if not set, null, or jibberish
if (!isset($params['domain']) || $params['domain']=='{domain}' || $params['domain'] == '')
{
// Instance of App Domain is set in OrgBaseController __construct()
$domain = \App::make('current_domain');
$params['domain'] = $domain;
}
return route($route, $params);
}

Laravel 5.4: How to set a first and default parameter to route() function

I 'm passing a prefix parameter to all routes on my webiste:
Route::prefix("{param}")->group(function () {
# code...
});
Sometimes I need to call the route() function in the views. The problem is there, because I need to pass the $param as first parameter like:
resources\views\welcome.blade.php
About Us
The question is: I do not need to pass $param in route() function because its not necessary. How to avoid this and do just the following:
About Us
Is there a way to create a middleware and set a "global" configuration to route() function?
By using #William Correa approach, I've created a helper function to setting up a prefix parameter to default Laravel function helper route().
app\Helpers\functions.php
function routex($route, $params = [])
{
if (!is_array($params)) {
$params = [$params];
}
// Set the first parameter to App::getLocale()
array_unshift($params, App::getLocale());
return route($route, $params);
}
Now when I try to get a link to route() by name, just use routex('about-us') and the routex() function will put a prefix parameter like App::getLocale() or anything else you want.
In your Controller you can protect your route like this:
public function __construct()
{
$this->middleware('auth');
}
and than
Route::get('/about', 'yourcontroller#yourmethod')->name('name');
or in your web.php you can declare a group without a prefix like this
Route::namespace('name')->group(function () {
Route::get('/about', 'yourcontroller#yourmethod')->name('name');
});
Yeah, like this:
Route::get('about-us', 'AboutController#getAboutUs')->name('about-us');
Now if you call route('about-us') it will generate url to .../about-us.

How to create a common routing rule in laravel 5.1?

I am new in laravel. By doc, I got that i have write rule for every different url. Is it so? I just wanted a common routing rule which works for all urls something like
Route::get('/{Controller}/{method}', $Controller.'#'.$method);
I know this is wrong, I tried a lot but can't get proper sentence.
I simply want that first segment after Base Url become controller name and second segment become method name.
I suppouse You can - if You must - do sth like that:
Route::get('/{controller}/{method}', function($controller, $method) {
$name = "\App\Http\Controllers\\" . $controller . 'Controller';
$class = new $name();
return $class->{$method}();
});
or if You have static methods:
Route::get('/{controller}/{method}', function($controller, $method) {
return call_user_func(array("\App\Http\Controllers\\" . $controller . 'Controller', $method));
});
But I don't think this is a good idea.
This way You loose all 'power' of laravel routing (because this is just one route).
For example:
You can't refer to choosen method by route name
You can't attach middleware to specific routes etc.
It is always better to be more explicit.
At least You can use one of these:
Route::resource() or
Rotute::controller()
In both cases You will need to define routes for each controller, though.
Examples:
Route::resource('photo', 'PhotoController');
and then follow method name convention in Your controller (index, create etc.).
More here: http://laravel.com/docs/5.0/controllers#restful-resource-controllers
Route::controller('users', 'UserController');
and then prefix Your controller method by http method like: public function getIndex()
More here: http://laravel.com/docs/5.0/controllers#implicit-controllers
For the time I used this,
$controller = '';
$method = '';
$segments = $_SERVER['REQUEST_URI'];
$segments = str_replace('/cp/public/index.php/', '', $segments);
$arr_seg = explode('/',$segments);
if(count($arr_seg) > 1){
$controller = $arr_seg[0];
$method = $arr_seg[1];
}
Route::get('/{var1}/{var2}',$controller.'#'.$method);
And it's working for me.

Multiple routes to same Laravel resource controller action

I like to use resource controllers in Laravel, as it makes me think when it comes to data modelling. Up to now I’ve got by, but I’m now working on a website that has a public front-end and a protected back-end (administration area).
I’ve created a route group which adds an “admin” prefix, like so:
Route::group(array('before' => 'auth', 'prefix' => 'admin'), function()
{
Route::resource('article', 'ArticleController');
Route::resource('event', 'EventController');
Route::resource('user', 'UserController');
});
And I can access the methods using the default URL structure, i.e. http://example.com/admin/article/1/edit.
However, I wish to use a different URL structure on the front-end, that doesn’t fit into what resource controllers expect.
For example, to access an article, I’d like to use a URL like: http://example.com/news/2014/06/17/some-article-slug. If this article has an ID of 1, it should (under the hood) go to /article/1/show.
How can I achieve this in Laravel? In there some sort of pre-processing I can do on routes to match dates and slugs to an article ID, and then pass that as a parameter to my resource controller’s show() method?
Re-visiting this, I solved it by using route–model binding and a pattern:
$year = '[12][0-9]{3}';
$month = '0[1-9]|1[012]';
$day = '0[1-9]|[12][0-9]|3[01]';
$slug = '[a-z0-9\-]+';
// Pattern to match date and slug, including spaces
$date_slug = sprintf('(%04d)\/(%02d)\/(%02d)\/(%s)', $year, $month, $day, $slug);
Route::pattern('article_slug', $date_slug);
// Perform the route–model binding
Route::bind('article_slug', function ($slug) {
return Article::findByDateAndSlug($date_slug);
});
// The actual route
Route::get('news/{article_slug}', 'ArticleController#show');
This then injects an Article model instance into my controller action as desired.
One simple solution would be to create one more route for your requirement and do the processing there to link it to the main route. So, for example:
//routes.php
Route::get('/arical/{date}/indentifier/{slug}', array (
'uses' => 'ArticleController#findArticle'
));
//ArticleContoller
public function findArticle($date,$slug){
$article = Article::where('slug','=','something')->first(); //maybe some more processing;
$article_id = $article->id;
/*
Redirect to a new route or load the view accordingly
*/
}
Hope this is useful.
It seems like if Laravel 4 supports (:all) in routing, you would be able to do it with ease, but unfortunately (:all) is not supported in Laravel 4.
However, Laravel 4 allows detecting routes by regular expression, so we can use ->where('slug', '.*').
routes.php: (bottom of the file)
Route::get('{slug}', 'ArticleController#showBySlug')->where('slug', '.*');
Since Laravel will try to match the top most route in routes.php first, we can safely put our wildcard route at the bottom of routes.php so that it is checked only after all other criteria are already evaluated.
ArticleController.php:
class ArticleController extends BaseController
{
public function showBySlug($slug)
{
// Slug lookup. I'm assuming the slug is an attribute in the model.
$article_id = Article::where('slug', '=', $slug)->pluck('id');
// This is the last route, throw standard 404 if slug is not found.
if (!$article_id) {
App::abort(404);
}
// Call the controller's show() method with the found id.
return $this->show($article_id);
}
public function show($id)
{
// Your resource controller's show() code goes here.
}
}
The code above assumes that you store the whole URI as the slug. Of course, you can always tailor showBySlug() to support a more advanced slug checking.
Extra:
You could also do:
Route::get('{category}/{year}/{slug}', 'ArticleController#showBySlug')->where('slug', '.*');
And your showBySlug() would just have additional parameters:
public function showBySlug($category, $year, $slug)
{
// code
}
Obviously you can extend to month and day, or other adaptations.

Resources