Dynamic route url change is not reflecting in laravel package - laravel

I am creating a package which gives a config file to customize the route url which it will add, I can see config file values in the controller, but same config('app_settings.url') is coming as null in
pakacge/src/routes/web.php
Route::get(config('app_settings.url'), 'SomeController')
my tests are also giving 404 and app_settings config change is not getting picked by route.
function it_can_change_route_url_by_config() {
// this should be default url
$this->get('settings')
->assertStatus(200);
// change the route url
config()->set('app_settings.url', '/app_settings');
$this->get('app_settings')
->assertStatus(200);
$this->get('settings')
->assertStatus(400);
}
app_setting.php
return [
'url' => 'settings',
'middleware' => []
];
It works when I use this package, but tests fail.
Please help How I can give the option to change the route url from config.

To be honest I think it's impossible to make such test. I've tried using some "hacky" solutions but also failed.
The problem is, when you start such test, all routes are already loaded, so changing value in config doesn't affect current routes.
EDIT
As alternative solution, to make it a bit testable, in config I would use:
<?php
return [
'url' => env('APP_SETTING_URL', 'settings'),
'middleware' => []
];
Then in phpunit.xml you can set:
<env name="APP_SETTING_URL" value="dummy-url"/>
As you see I set here completely dummy url to make sure this custom url will be later used and then test could look like this:
/** #test */
function it_works_fine_with_custom_url()
{
$this->get('dummy-url')
->assertStatus(200);
$this->get('settings')
->assertStatus(404);
}
Probably it doesn't test everything but it's hard to believe that someone would use dummy-url in routing, and using custom env in phpunit.xml give you some sort of confidence only custom url is working fine;

Related

Laravel Route Controller issue

I am trying to add a new route to my application and can't seem to get it to work. I keep getting a 404 error. It looks like the physical path is looking at the wrong directory. Currently looking at D:\Web\FormMapper\blog\public\forms but should be looking at D:\Web\FormMapper\blog\resources\view\layout\pages\forms.blade.php
My request URL:
http://localhost/FormMapper/ /works fine
http://localhost/FormMapper/forms /doesn't work
http://localhost/FormMapper/forms.php /No input file specified.
my FormsController:
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class FormsController extends Controller
{
public function index()
{
return view('layouts.pages.forms');
}
}
My web.php:
Route::get('/', function () {
return view('layouts/pages/login');
});
Route::get('/forms', 'FormsController#index');
My folder structure looks like this:
My config/view.php
return [
'paths' => [
resource_path('views'),
],
'compiled' => env(
'VIEW_COMPILED_PATH',
realpath(storage_path('framework/views'))
),
];
you must use dot for this. In your controller change to this:
return view('layouts.pages.forms');
If your route only needs to return a view, you may use the Route::view method. Like the redirect method, this method provides a simple shortcut so that you do not have to define a full route or controller. The view method accepts a URI as its first argument and a view name as its second argument. In addition, you may provide an array of data to pass to the view as an optional third argument:
Route::view('/', 'layouts.pages.login');
Route::view('/forms', 'layouts.pages.forms', ['foo' => 'bar']);
Check docs
After tracking digging deeper I determined that the issue was that IIS requires URL rewrite rules in place for Laravel to work properly. The index.php and '/' route would work b/c it was the default page but any other pages wouldn't. To test this I used the
php artisan serve
approach to it. and everything worked properly. Unfortunately I am unable to do this in production so I needed to get it to work with IIS.

How to do unit testing with Laravel Localization?

I'm using mcamara/laravel-localization package and I can't figure out how to make it work with my unit tests. Both of the following fail with red:
// 1. This one results in "Redirecting to http://myapp.dev/en"
$this->get('/')->assertSee('My App Homepage');
// 2. This one results in 404
$this->get('/en')->assertSee('My App Homepage');
In the browser, http://myapp.dev returns 302 with a redirect to http://myapp.dev/en, fair enough. However, http://myapp.dev/en returns 200. So both cases work 100% fine on the front-end, but not with unit tests.
I do have some customization however, which once again, works like charm in the browser.
// in web.php
Route::group([
'prefix' => app('PREFIX'), // instead of LaravelLocalization::setLocale()
'middleware' => ['localeSessionRedirect', 'localizationRedirect']],
function() {
Route::get('/', function() {
return view('home');
});
}
]);
// in AppServiceProvider.php
public function boot()
{
// This, unlike LaravelLocalization::setLocale(), will determine the
// language based on URL, rather than cookie, session or other
$prefix = request()->segment(1); // expects 'en' or 'fr'
$this->app->singleton('PREFIX', function($app) use ($prefix) {
return in_array($prefix, ['en', 'fr']) ? $prefix : null;
});
}
Hopefully this code makes sense to you. Thanks!
UPDATE
I addressed this problem with the package in a GitHub issue #435.
UPDATE 2
Insofar as I could figure it out, it seems that you can safely test your localized routes as long as you specify the locale in the base URL in your phpunit XML file:
<env name="APP_URL" value="http://myapp.dev/en"/>
However, this would work for your localized GET endpoints (which start with a locale prefix, e.g. 'en'), but not for non-localized POST, PUT, etc. (which don't have any prefix). Hence, you can't really test both kinds of endpoints at the same time, unless you use Dusk (which I don't, as it's an overkill and much slower, almost the same as doing it manually).
I found that if you dump the request URL during testing, it is always http://myapp.dev no matter what endpoint you're accessing. So both LaravelLocalization::setLocale() and my custom app('PREFIX') return null, meaning that not a single route is ever localized during testing. You are screwed either way because if you try to access a route without a locale prefix, you get a 302, but if you do specify the locale, the framework can't find a definition for that route.
One article helped me discover a temporary solution: you need to hideDefaultLocaleInURL to true in laravellocalization.php. This way, the routes matching your default locale won't have any prefix, so you can test them as if they were non-localized.
However, the problem still persists, because how are you supposed to test your application when it is localized? (For ex., when you have language-specific routes that need to be tested). This poses the question whether this package is even compatible with unit testing per se...
The problem
Using mcamara / laravel-localization when I test a show route I get a 404 error.
For instance, testing this route returns me a 404:
Route::get('/posts/{post:slug}', [PostController::class, 'show'])->name('posts.show');
The test:
/** #test */
public function itShouldDisplayThePostsShowViewToGuestUser()
{
$response = $this->get("/posts/{$this->post1->slug}");
$response->assertStatus(200);
$response->assertViewIs('posts.show');
}
The solution
I solved hiding the locale from the URL while testing.
Creating this env variable at the end of phpunit.xml.
...
<env name="LOCALIZATION_HIDE_DEFAULT_LOCALE" value="true"/>
</php>
</phpunit>
And in config/laravellocalization.php setting hideDefaultLocaleInURL like this:
'hideDefaultLocaleInURL' => env('LOCALIZATION_HIDE_DEFAULT_LOCALE', false)
This solution was inspired by this this post:
https://github.com/mcamara/laravel-localization/issues/161#issuecomment-381367191

Laravel: Change base URL?

When I use secure_url() or asset(), it links to my site's domain without "www", i.e. "example.com".
How can I change it to link to "www.example.com"?
First change your application URL in the file config/app.php (or the APP_URL value of your .env file):
'url' => 'http://www.example.com',
Then, make the URL generator use it. Add thoses lines of code to the file app/Providers/AppServiceProvider.php in the boot method:
\URL::forceRootUrl(\Config::get('app.url'));
// And this if you wanna handle https URL scheme
// It's not usefull for http://www.example.com, it's just to make it more independant from the constant value
if (\Str::contains(\Config::get('app.url'), 'https://')) {
\URL::forceScheme('https');
//use \URL:forceSchema('https') if you use laravel < 5.4
}
That's all folks.
.env file change in
APP_URL='http://www.example.com'
config/app.php :
'url' => env('APP_URL', 'http://www.example.com')
In controller or View call with config method
$url = config('app.url');
print_r($url);

Laravel 5: Sessions not working the way they should

On top of every controller and routes.php I used:
use Illuminate\Support\Facades\Session;
In routes.php I set the session using:
Session::put('key', 'value');
In a controller I want to call the session value of key using:
echo Session::get('key');
But once I set a new value to key in routes.php and call it in a controller, I still get the first value and not the new one. If I echo the the session using Session::all() in routes.php after setting it, I see the new value, but in a controller it flips back to the first value. I even tried using below in routes.php before setting the new value, but without success.
Session::forget('key');
Am I forgetting something here?
Using regular PHP $_SESSION my routes.php looks like this:
$slug = $_SERVER['REQUEST_URI'];
$slug = explode('/', $slug[0]);
if(in_array($slug[1], Language::all()->lists('iso'))) {
$_SESSION['language'] = $slug[1];
if(!$slug[2]) {
$_SESSION['slug'] = 'home';
Route::any('/{slug}', ['as' => 'pages.page', 'uses' => 'PagesController#page']);
} else {
if($slug[2] != 'dashboard' && $slug[2] != 'migrate' && $slug[2] != 'form-send') {
if (in_array($slug[2], ElementValue::where('element_field_id', 2)->lists('value_char')) && !isset($slug[3])) {
$_SESSION['slug'] = $slug[2];
Route::any('/{slug}', ['as' => 'pages.page', 'uses' => 'PagesController#page']);
} else {
$_SESSION['slug'] = 'home';
Route::any('/{slug}', ['as' => 'pages.page', 'uses' => 'PagesController#page']);
}
}
}
}
Where in routes.php are you setting the session value? It sounds like you're doing something like this:
Session::put('key', 'value');
Route::get('my-route', 'MyController#doSomething');
and then doing this:
class MyController {
public function doSomething()
{
Session::get('key');
}
}
Is that correct? If so, read on...
I'm no expert on the Laravel request lifecycle (for more, see the documentation), but it doesn't surprise me that this doesn't work. The way I think about it is this: the routes.php file is loaded and executed early in the life cycle - probably first - since it tells the application what code to execute next (ie. what do when a particular request is received). And when I say "early in the life cycle", I mean early - like before sessions are initialized. I believe that the Session::put call is simply being ignored, since at the time when you're setting the value, the session does not exist.
You may want expand your question with a little more detail about what you're trying to accomplish - there has got to be a better way to do it.
EDIT - in response to the comments below...
I am not saying you should touch the $_SESSION superglobal - that's a bad idea because I'm not even sure that Laravel uses the native PHP session facility and you have no guarantee that whatever you do will continue to work in the future.
It's not clear what you're trying to do, but to me this sounds like a value that does not belong in the session.
By placing the Session::put in the routes.php file, it sounds like you have some value that's important and should be set for every session and every request
If that's the case, and it's a static value, then it's not a session value, it's a configuration value.
If, instead, it's a dynamic value and/or it changes depending on which user is associated with a session, then you can set it in one of several places:
if you're using controller-based routing, you could set this in the controller constructor, although I wouldn't recommend it, because you will probably have to do it for several controllers, leading to code duplication
if you're using closures in your routes, set it there. E.g.
Route::get('some/route', function () {
Session::put('key', 'value');
// this works, because the closure isn't executed until after
// the application is initialized
});
you could also do it in middleware
or in a service provider (although I'm not certain that sessions would be available when the service providers are executed).
The best option is probably middleware - this would allow you to set (or calculate) the session value in one place in your code and also associate it with particular routes, if you don't need it for all routes.
Don't use $_SESSION in laravel. Uses the laravel Session class. See the following post How to access the globals $_SESSION and $_COOKIE from a laravel app?
Also, all your if logic should not be living in routes.php. You should add that to middleware to filter your routes.
Also, you are really making this hard for yourself. Laravel provides most of what you need in convenient helper classes e.g. Request::url(), Request::getHost(), Request::getLocale(). Have a read through the docs and get familiar with "The Laravel Way" it will be much easier and things will then work as you expect.
I moved the logic to the controller and now my routes are this simple:
Route::pattern('slug', '[a-zA-Z0-9\-_\/]+');
$slug = Request::path();
if(isset($slug)) {
Route::any('/{slug}', 'PagesController#index')->where('slug', '[a-zA-Z0-9\-_\/]+');
}
The session is stored in the PagesController and used further in the application. Thanks for your help guys.

Why aren't my controllers actions accessible with unit tests in Laravel

I have set up my route as such:
Route::controller('clients', 'Controllers\ClientsController');
Through this method I can easily access all the controller functions via post and get. However I cannot test them as easily.
public function testCantDeleteOtherAccountsClient()
{
Route::enableFilters();
$user = Models\User::find(1);
$this->be($user);
$response = $this->action('GET', 'ClientsController#getDelete');
$this->assertRedirectedToAction('ClientsController#getIndex');
}
This test results in the message
InvalidArgumentException: Route [ClientsController#getDelete] not defined.
The method accessible via url though. What am I missing?
Just tried this out myself (a route specified via the controller) your issue is that using action requires a named route. Controller routes do not currently support this as far as I'm aware of.
If you create a test route:
Route::get('test', array(
'as' => 'testName',
'uses' => 'ClientsController#getDelete'
));
And try
$this->action('GET', 'testName');
The test should pass, you can view all the routes with names via php artisan routes.
You may want to use $this->client->request() instead. You can check if a redirect occurred with:
$this->assertRedirectedTo("some\url");
Note that $this->call() is just an alias to $this->client->request().
I found changing it to use call instead of action worked for me:
$response = $this->call('GET', 'clients/delete/1');

Resources