How to set regular expression parameter constraints for Route::group in Laravel 4? - regex

For simple routes I know I can user where statement. But what about parameters in Route::group() prefix
<?php
Route::get('user/{id}', 'UserController#profile')->where('id', '[0-9]+');
Route::group(['prefix' => 'foo/{bar}'], function() {
// ...
})->where('bar', '[0-9a-Z]+'); // this won't work

I'm using laravel 5.5. I had same problem and find this question in search.
I've tried to use the solution defined by #lukasgeiter and faced a problem:
The value of
$group->getRoutes()
was not only the routes of current group.
But I fixed my problem by specifying condition in route group definition.
Route::group([
'prefix' => 'foo/{bar}',
'where' => ['bar' => '[0-9a-Z]+']
],
function() {
// ...
});
And it worked for me :)

Out of the box the laravel router doesn't support this. You can use the Enhanced Router package from Jason Lewis or a fork that enables support for Laravel 4.2
Alternatively you can do it yourself. You could basically add the where condition to every route inside the group:
Route::group(['prefix' => 'foo/{bar}'], function() {
Route::get('/', function(){
// ...
})->where('bar', '[0-9a-Z]+');
});
Or do it a bit more dynamic and add this at the bottom of your route group:
Route::group(['prefix' => 'foo/{bar}'], function($group) {
// ...
foreach($group->getRoutes() as $route){
$route->where('bar', '[0-9a-Z]+');
}
});

One of possible not perfect solution in my view will be
// Route Model Binding
Route::model('user', 'User');
// Route Constraint Pattern
Route::pattern('user', '[0-9]+');
// Route Definition
Route::get('anything/{user}', 'UserController#anyFunction');
.
.
Route::resource('user', 'UsersController');

Related

How to exclude "/api" from "any"-route in Lumen router (per regex)?

I have a problem with the Lumen router (web.php):
My project includes vue.js with the vue router, so I want to point all routes to the router, what is indeed working fine.
$router->get('{path:.*}', function () {
return view('app');
});
My problem is: I also have a few api routes, handled by Lumen/controllers:
$router->group(['prefix' => 'api'], function ($router) {
$router->group(['prefix' => 'authors'], function ($router) {
$router->get('/', 'AuthorController#showAllAuthors');
$router->get('/id/{id}', 'AuthorController#showAuthorById');
});
});
Well, the route localhost/api/authors just works fine.
But localhost/api/authors/1 returns the app..
I was thinking of putting in an exception to the vue route:
$router->get('{path:^(?!api).*$}'
..but this will lead to a NotFoundHttpException.
Is something wrong with the regex?
It should exclude all routes that start with /api.
You're really close. Regex happens after the get/post statements in laravel's routes. Like so:
$router->get('/{catch?}', function () {
return view('app');
})->where('catch', '^(?!api).*$');
Here's the docs for reference:
https://laravel.com/docs/5.8/routing#parameters-regular-expression-constraints
Edit: Lumen specific should be solved in a group prefix.
$router->get('/{route:.*}/', function () {
return view('app');
});

Regex for route work incorrect and return error 404

I have custom regex for match correct username:
^(?=.{5,20}$)[a-zA-Z](?:[a-zA-Z0-9_]*[a-zA-Z0-9])?$
See demo
Here you can see my routes list:
Route::middleware(['userActivity'])->group(function () {
Route::group(['prefix' => '{nickname}','where' => ['nickname' => '^(?=.{5,20}$)[a-zA-Z](?:[a-zA-Z0-9_]*[a-zA-Z0-9])?$']], function ($nickname) {
Route::name('user.')->namespace('User')->group(function () {
Route::middleware(['auth', 'company'])->group(function () {
Route::namespace('Vacancy')->group( function () {
Route::prefix('vacancy')->name('vacancy.')->group( function () {
Route::get('/manage', "VacancyController#manage")->name('manage');
Route::post('/save', "VacancyController#save")->name('save');
});
});
});
});
});
});
In this case when I go to route user.vacancy.manage:
http://website.com/user_1544080981/vacancy/manage
Return error:
404 Page Not Found
When I change my regex to:
^(?=.{5,30}$)[a-zA-Z](?:[a-zA-Z0-9_]*[a-zA-Z0-9])?$
Note: Changed in regex only min and max length from {5,20} to {5,30}
Generaly when I see to part of url after domain name url length == 30
user_1544081143/vacancy/manage
But regex must work only for user nickname instead of to part url without domain name. Where I have any errors in my routes?
Regular expression, this is a better way then above in my opinion.
Route::any('{all}', function(){
return 'It Works';
})->where('all', '.*');
Using the Route::fallback method, you may define a route that will be executed when no other route matches the incoming request. Typically, unhandled requests will automatically render a "404" page via your application's exception handler. However, since you may define the fallback route within your routes/web.php file, all middleware in the web middleware group will apply to the route. Of course, you are free to add additional middleware to this route as needed:
Route::fallback(function () {
//
});

Angular RC5 - Overridden component template fails to find input property

I have two components:
IfNodeComponent:
#Component({
template: '<se-dynamic-condition (questionLinkClicked)="onQuestionClicked" [condition]="node.condition"></se-dynamic-condition>',
selector: 'se-node-if'
})
export class NodeIfComponent {
#Input() node: NodeProperties;
onQuestionClicked(event: IQuestionLinkClickEvent): void {
// do stuff
}
}
and DynamicConditionComponent:
#Component({
template: '<p>My original template</p>',
selector: 'se-dynamic-condition'
})
export class DynamicConditionComponent {
#Input() condition: Condition;
#Output() questionLinkClicked = new EventEmitter<IQuestionLinkClickEvent>();
}
I am writing a test to check that the [condition] binding is attached to the se-dynamic-condition component inside the if node template. To do this I am overriding the template of the DynamicConditionComponent to simply be {{condition | json}}. This then allows me to compare the JSON and assert that it is identical to the condition that should be passed in.
Before RC5 I used the OverridingTestComponentBuilder to achieve this. But since I've just upgraded to RC5, I am rewriting that test to use the TestBed instead. This is not going too well. Here is how it looks:
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [NodeIfComponent, DynamicConditionComponent]
});
TestBed.overrideComponent(DynamicConditionComponent, {
set: {
template: '{{condition | json}}'
}
});
fixture = TestBed.createComponent(NodeIfComponent);
component = fixture.componentInstance;
element = fixture.nativeElement;
component.node = {some:'data'};
fixture.detectChanges();
});
it('should display a dynamic condition component and pass the condition to it', () => {
let dc = element.querySelectorAll('se-dynamic-condition');
expect(dc.length).toEqual(1, 'Dynamic condition component is found');
expect(dc[0].innerHTML).toEqual(JSON.stringify({some:'data'}, null, 2));
});
However, running this test fails with the following error:
Can't bind to 'condition' since it isn't a known property of 'se-dynamic-condition'.
If I don't override the template for DynamicConditionComponent, then I don't get the error, but understandably my test fails. And if I remove the property binding from the IfNode template, then I don't get the error, but again, the test fails as expected. The error message points towards the se-dynamic-condition component not being registered in the same module. But it is, and the code works when I run it. It is just the test that is the problem, which doesn't use the module definition anyway. That's what the TestBed.configureTestingModule statement is for.
So it appears that overriding a template also loses the condition property associated with the component.
Am I doing something fundamentally wrong here? Examples I have seen elsewhere of overriding the template work fine, but I haven't seen any that override a template with an input property (and then try to assign a value to that property).
Any help would be greatly appreciated.
This is a bug in RC5 fixed in RC6 by https://github.com/angular/angular/pull/10767
For now as a workaround re-specify the inputs in the override statement.
use
TestBed.overrideComponent(DynamicConditionComponent, {
set: {
template: '{{condition|json}}',
inputs: ['condition'],
}
});

How do unit test with angular-translate

I have uses angular translate from here (http://pascalprecht.github.io/angular-translate/) and it's just work fine, but it break my controller's unit test whith Error:
Unexpected request: GET scripts/i18n/locale-en.json
I don't understant why?
I use yeoman and test with karma.
app.js:
'use strict';
(function() {
angular.module('wbApp', ['authService', 'authUserService', 'checkUserDirective', 'ui.bootstrap', 'pascalprecht.translate'])
.config(function($routeProvider) {
$routeProvider
.when('/', {
templateUrl: 'views/login.html',
controller: 'LoginCtrl',
access: {
isFree: true
}
})
.when('/main', {
templateUrl: 'views/main.html',
controller: 'MainCtrl',
access: {
isFree: false
}
})
.otherwise({
redirectTo: '/'
});
});
})();
configTranslate.js:
'use strict';
(function() {
angular.module('wbApp')
.config(['$translateProvider',
function($translateProvider) {
$translateProvider.useStaticFilesLoader({
prefix: 'scripts/i18n/locale-',
suffix: '.json'
});
$translateProvider.preferredLanguage('en');
}]);
})();
karma.conf.js:
files = [
...
'app/bower_components/angular-translate/angular-translate.js',
'app/bower_components/angular-translate-loader-static-files/angular-translate-loader-static-files.js',
...
];
controller test:
'use strict';
describe('Controller: LoginCtrl', function() {
// load the controller's module
beforeEach(module('wbApp'));
var LoginCtrl, scope, location, httpMock, authUser;
// Initialize the controller and a mock scope
beforeEach(inject(function($controller, $rootScope, $location, $httpBackend, AuthUser) {
authUser = AuthUser;
location = $location;
httpMock = $httpBackend;
scope = $rootScope.$new();
LoginCtrl = $controller('LoginCtrl', {
$scope: scope
});
httpMock.when('GET', 'scripts/i18n/locale-en.json').passThrough();
}));
it(...);
...
});
if i add this in test controller, product same error:
httpMock.when('GET', 'scripts/i18n/locale-en.json').respond(200);
httpMock.flush();
or
httpMock.when('GET', 'scripts/i18n/locale-en.json').passThrough();
httpMock.flush();
i find this post How do I test controllers with Angular Translate initialized in App Config? but not helped me :/
I extensively use $httpBackend in my tests and it works fine, but in this case it is ineffective. If I comment the line:
$translateProvider.preferredLanguage('en');
obviously an error, if I add on the runtime (in my controllers)
$translate.uses(local);
I end up with the same error?
So I turn to the translation configuration (configTranslate.js) or at runtime is the same result:
Unexpected request: GET scripts/i18n/locale-en.json
Here is the syntax that I tested, either in a "beforeEach(inject(function(...});"
or in a test "it('...', function() {...});"
httpMock.expectGET('scripts/i18n/locale-en.json');
httpMock.when('GET', 'scripts/i18n/locale-en.json').passThrough();
httpMock.when('GET', 'scripts/i18n/locale-en.json').respond(data);
with at end
httpMock.flush();
I also tried a $ apply
httpMock.expectGET('scripts/i18n/locale-fr.json');
scope.$apply(function(){
$translate.uses('fr');
});
httpMock.flush();
nothing happens, Still this error is driving me crazy ..
If you have any suggestion
it's a known issue, please follow the documentation here: unit testing angular
The solution
Unfortunately, this issue is caused by the design of
angular-translate. To get around these errors, all we can do is to
overwrite our module configuration in our test suite, that it doesn't
use asynchronous loader at all. When there's no asynchronous loader,
there's no XHR and therefore no error.
So how do we overwrite our module configuration at runtime for our
test suite? When instantiating an angular module, we can always apply
a inline function which is executed as configuration function. This
configuration function can be used to overwrite the modules
configuration since we have access to all providers.
Using the $provide provider, we can build a custom loader factory,
which should then be used instead of the static files loader.
beforeEach(module('myApp', function ($provide, $translateProvider) {
$provide.factory('customLoader', function () {
// loader logic goes here
});
$translateProvider.useLoader('customLoader');
}));
Please read more in the above link provided.
We took the approach of ignoring the translation loader in unit tests, rather than being forced to modify each of the spec files.
One way to do it could be by separating the loader configuration to a separate file and then exclude it in karma.
So for example you can create a file app-i18n-loader.js (all other module configurations takes place in a different file):
angular
.module('myApp')
.config(loaderConfig);
loaderConfig.$inject = ['$translateProvider', '$translatePartialLoaderProvider'];
function loaderConfig($translateProvider, $translatePartialLoaderProvider) {
$translateProvider.useLoader('$translatePartialLoader', {
urlTemplate: 'assets/i18n/{part}/{lang}.json'
});
$translatePartialLoaderProvider.addPart('myApp');
}
And in your karma.conf.js exclude the file:
files: [
'bower_components/angular/angular.js',
'bower_components/angular-mocks/angular-mocks.js',
//...
'bower_components/angular-translate/angular-translate.js',
'bower_components/angular-translate-loader-partial/angular-translate-loader-partial.js',
'app/**/*.mdl.js',
'app/**/*.js'
],
exclude: [
'app/app-i18n-loader.js'
],
(Note: Answer edited to a solution that does not require grunt/gulp).
I wanted a solution,
which was not too hacky
which didn't require me to change my actual application code,
which wouldn't interfere with the ability to load additional modules
and most importantly which wouldn't require me to change every
single test.
This is what I ended up with:
// you need to load the 3rd party module first
beforeEach(module('pascalprecht.translate'));
// overwrite useStaticFilesLoader to get rid of request to translation file
beforeEach(module(function ($translateProvider) {
$translateProvider.useStaticFilesLoader = function () {
};
}));
Assuming you don't need the actual translations for your unit tests, this works great. Just put the beforeEach on a global level, preferably in it's own file inside the test folder. It will be executed before every other test then.
I encountered this problem with protractor tests. My solution was to mock translations like this:
angular.module('app')
.config(function ($translateProvider) {
$translateProvider.translations('en', {});
$translateProvider.preferredLanguage('en');
})
Now no language files are downloaded, no strings get translated and I just test against the string keys in specifications:
expect(element(by.css('#title')).getText()).toEqual('TITLE_TEXT');
Try putting to test method:
it('should ...', function() {
httpMock.when('GET', 'scripts/i18n/locale-en.json').respond({});
httpMock.expectGET('scripts/i18n/locale-en.json');
scope.resetForm(); // Action which fires a http request
httpMock.flush(); // Flush must be called after the http request
}
See examples from Angular docs
Please have a look at https://github.com/PascalPrecht/angular-translate/blob/master/test/unit/service/loader-static-files.spec.js as a reference.
In general, I would recommend using a standard translation loader for unit tests (without the hassle of http loadings) which means you can provide the labels with $translateProvider.translations(). Why? Because you do not have to test the remote loading functionality which is part of angular-translate project.
None of the solutions worked for me but I came with these solutions:
1) If you need to use scope.$apply(), or should deal with states in your test (after the $apply() the 2nd approach won't work), override your app's translations with the $translateProvider.translations() method, using a plugin to load JSON files
beforeEach(module(function ($translateProvider) {
$translateProvider.translations('en', readJSON('scripts/i18n/locale-en.json'));
}));
2) If your tested controller depends on the $translate service you can use a plugin to load JSON files and combine it with $httpBackend to load your locale file when angular-translate requests it.
beforeEach(inject(function (_$httpBackend_) {
$httpBackend = _$httpBackend_;
$httpBackend.whenGET('scripts/i18n/locale-en.json').respond(readJSON('scripts/i18n/locale-en.json'));
$httpBackend.flush();
})));
Note this should be below your beforeEach(module('myApp')); or you will get an $injector error.
I made a simple mock service for $translate
$translate=function (translation) {
return {
then: function (callback) {
var translated={};
translation.map(function (transl) {
translated[transl]=transl;
});
return callback(translated);
}
}
};
Usage example here : https://gist.github.com/dam1/5858bdcabb89effca457
I use this pattern.
ApplicationModule set regular angular-translate config.
test code load 'testModule' instead of 'applicationModule'
// application module .js
(function() {
'use strict';
angular
.module('applicationModule', [
'ngAnimate',
'ngResource',
'ui.router',
'pascalprecht.translate'
])
.config(['$stateProvider', '$urlRouterProvider', '$translateProvider', '$translatePartialLoaderProvider', config]);
function config($stateProvider, $urlRouterProvider, $translateProvider, $translatePartialLoaderProvider) {
// set routing ...
$translateProvider.useStaticFilesLoader({
prefix: 'i18n/locale-',
suffix: '.json'
});
$translateProvider.useMessageFormatInterpolation();
$translateProvider.fallbackLanguage(['en']);
$translateProvider
.registerAvailableLanguageKeys(['en', 'ko'], {
'en_US': 'en',
'ko_KR': 'ko'
})
.determinePreferredLanguage(navigator.browserLanguage);
$translateProvider.addInterpolation('$translateMessageFormatInterpolation');
$translateProvider.useSanitizeValueStrategy('escaped');
}
})();
// test.module.js
(function() {
'use strict';
angular
.module('testModule', ['applicationModule'])
.config(['$translateProvider', '$translatePartialLoaderProvider', config])
.run(['$httpBackend', run]);
function config($translateProvider, $translatePartialLoaderProvider) {
$translateProvider.useLoader('$translatePartialLoader', {
urlTemplate: 'i18n/locale-en.json'
});
$translatePartialLoaderProvider.addPart('applicationModule');
}
function run($httpBackend) {
$httpBackend.when('GET', 'i18n/locale-en.json').respond(200);
}
})();
// someDirective.spec.js
describe("a3Dashboard", function() {
beforeEach(module("testModule"))
var element, $scope;
beforeEach(inject(function($compile, $rootScope) {
$scope = $rootScope;
element = angular.element("<div>{{2 + 2}}</div>");
$compile(element)($rootScope)
}))
it('should equal 4', function() {
$scope.$digest();
expect(element.html()).toBe("4");
})
})
Late to the table with this, but I got round this by specifying that Karma simply serve the files as per this entry in karma.conf.js:
files: [
...
{pattern: 'scripts/i18n/*.json', included: false, served: true},
...
]
The 2016 answer for this is to preprocess your json into your tests and properly test translations work on your directives.
I use karma-ng-json2js-preprocessor. Follow all the steps to setup your karma.conf then in your test file, prepend the relevant file as a module, then set that information in $translateProvider.
beforeEach(module('myApp', '/l10n/english-translation.json'));
// Mock translations for this template
beforeEach(module(function($translateProvider, englishTranslation) {
$translateProvider.translations('en_us', englishTranslation);
$translateProvider.useSanitizeValueStrategy(null);
$translateProvider.preferredLanguage('en_us');
}));
Note according to the plugin, it uses your filename to generate a camelcased module name. You can play with the function inside the module's /lib but basically it remove all dashes but KEEPS underscores in a camelCase. So en_us becomes En_us.
You'll also need to tell your test that it is expecting that file as a GEt.
$httpBackend.expect('GET', '/l10n/english-translation.json').respond(200);

Zend Route Regex: Match controller segment of Url to anything but "home"

I am working with Urls in the following format: "controller/action" where the slash is required.
I need to create a PCRE regex to match the controller and action names unless the controller name has the value of "home." For example:
"member/update" should produce "member", "update"
"admin/index" should produce "admin", "index"
"home/index" should produce <nomatch>
I've been playing around with look ahead, behind, and around and I have parts of it working but no totally correct solution. Any help appreciated.
** EDIT
Here's a little more background. I want to set up Zend routing so that
an url with just one part '/foo' dispatches to HomeController, i.e. '/home/foo'
any url with two parts '/controller/action' dispatches to that controller: e.g., '/member/update' dispatches to MemberController, '/member/update'; '/home/index' dispatches to HomeController, etc.
Right now I have routes set up as:
$router->addRoute('homeRoute',
new Zend_Controller_Router_Route(
':action',
array('controller' => 'home', 'action' => 'index')
)
);
$router->addRoute('memberRoute',
new Zend_Controller_Router_Route(
'member/:action',
array('controller' => 'member', 'action' => 'index')
)
);
// and so on, a route for each non-Home controller: adminRoute, etc
This is working the way I want but it's ugly and a maintenance problem. I'm want to replace all of the routes 'memberRoute', 'adminRoute', etc, with a single Regex route:
$router->addRoute('siteRoute',
new Zend_Controller_Router_Route_Regex(
'????/:action',
array('action' => 'index'),
array(?? => 'controller')
)
);
I have been running my regex experiments with a little php script like:
<?php
preg_match('/????/', 'home/index', $output);
echo '0=>'.$output[0].';';
echo '1=>'.$output[1].';';
?>
I don't think regular expressions are the solution here. I've never worked with the zend framework before but in PHP it would be probably something like this
$string = "controller/action";
$pieces = explode('/', $string);
if ($pieces[0] == 'home') {
// special code here
} else {
// other code here
}
When I hear, "..match everything but something", I think maybe it should be flipped around: how about matching just the something, or in this case, the 'home' controller?
// added last to be checked first
$router->addRoute(
'home',
new Zend_Controller_Router_Route(
'home/:action',
array('controller' => 'home')
)
);
(?:home.*)|([^/]*)(?:/(.*))?