Laravel 4 : Route to localhost/controller/action - laravel

I'm more or less new to Laravel 4. I've never used routes before but normally what I'm used to is url/controller/action and then the backend routing for me. I've read the documentation for routes and controllers a few times as well as read through some tutorials and so, I'm trying to figure out how to get this to work without writing a route for every controller and action.
I tried something like
Route::get('{controller}/{action}', function($controller, $action = 'index'){
return $controller."#".$action;
});
Now then, I know this is wrong since it doesn't work, but what am I missing? On most tutorials and stuff I'm seeing an route for more or less every controller and action like:
Route::get('/controller/action' , 'ControllerName#Action');
Which seems silly and like a waste of time to me.
Is there anyway to achieve what I want?

If you are looking for a more automated routing, this would be the Laravel 4 way:
Route:
Route::controller('users', 'UsersController');
Controller (in this case UsersController.php):
public function getIndex()
{
// routed from GET request to /users
}
public function getProfile()
{
// routed from GET request to /users/profile
}
public function postProfile()
{
// routed from POST request to /users/profile
}
public function getPosts($id)
{
// routed from GET request to: /users/posts/42
}
As The Shift Exchange mentioned, there are some benefits to doing it the verbose way. In addition to the excellent article he linked, you can create a name for each route, for example:
Route::get("users", array(
"as"=>"dashboard",
"uses"=>"UsersController#getIndex"
));
Then when creating urls in your application, use a helper to generate a link to a named route:
$url = URL::route('dashboard');
Links are then future proofed from changes to controllers/actions.
You can also generate links directly to actions which would still work with automatic routing.
$url = URL::action('UsersController#getIndex');

app\
controllers\
Admin\
AdminController.php
IndexController.php
Route::get('/admin/{controller?}/{action?}', function($controller='Index', $action='index'){
$controller = ucfirst($controller);
$action = $action . 'Action';
return App::make("Admin\\{$controller}Controller")->$action();
});
Route::get('/{controller?}/{action?}', function($controller='Index', $action='index'){
$controller = ucfirst($controller);
$action = $action . 'Action';
return App::make("{$controller}Controller")->$action();
});

I come from .Net world and routing is typically done:
/{Controller}/{action}/{id}
Which looks like:
/Products/Show/1 OR /Products/Show/Beverages
In Laravel I accomplish this routing like so:
Route::get('/{controller?}/{action?}/{id?}', function ($controller='Home', $action='index', $id = null) {
$controller = ucfirst($controller);
return APP::make("{$controller}Controller")->$action($id);
});
The controller would look roughly like so:
class ProductsController extends BaseController {
public function Show($id) {
$products = array( 1 => array("Price" => "$600","Item" => "iPhone 6"),
2 => array("Price" => "$700", "Item" => "iPhone 6 Plus") );
if ($id == null) {
echo $products[1]["Item"];
} else {
echo $products[$id]["Item"];
}
}
}

Related

Can we use multiple second-level domains with multitenancy?

I have implemented the simplest example using the Spatie docs for multitenancy, that is working perfectly fine. Now, I intend to use multiple second-level domains for each tenant I have.
For example; I have 2 tenants company-a and company-b and they are being served at company-a.localhost and company-b.localhost, now what I want is that when I visit company-a.admin.localhost, it should tell me COMPANY-A ADMIN and If I visit company-a.employee.localhost, it should tell me COMPANY-A EMPLOYEE.
I have tried using subdomain on routes in RouteServiceProvider like the following:
Route::middleware('web')
->group(base_path('routes/security.php'));
Route::domain($this->baseDomain('admin'))
->middleware('web')
->name('admin.')
->group(base_path('routes/admin.php'));
Route::domain($this->baseDomain('employee'))
->middleware('web')
->name('employee.')
->group(base_path('routes/employee.php'));
private function baseDomain(string $subdomain = ''): string
{
if (strlen($subdomain) > 0) {
$subdomain = "{$subdomain}.";
}
return $subdomain . config('app.base_domain');
}
Without subdomain, it works fine, but the routes with second-level domain, it falls to base level domain route and does not get the current tenant.
What am I missing here? Is this even possible to implement.
Thankyou.
Take, for example, the route:
Route::domain('{subdomain}.example.com')
->get('/foo/{param1}/{param2}',function(Router $router) {
// do something with it
});
The binding fields would be ['subdomain', 'param1', 'param2'], and the compiled route would have it's regexes declared as
regex => "{^/foo/(?P<param1>[^/]++)/(?P<param2>[^/]++)$}sDu",
hostRegex => "{^(?P<subdomain>[^\.]++)\.example\.com$}sDiu"
Where ^(?P<subdomain>[^\.]++)\. will explicitly stop capturing when finding a dot, in this case the delimiter between groups.
However, these regexes are overridable by using the where method. You could declare the above route as
Route::domain('{subdomain}.example.com')
->get('/foo/{param1}/{param2}',function(Router $router) {
// do something with it
})->where('subdomain', '(.*)');
In the compiled route , the hostRegex would be now
hostRegex => "{^(?P<subdomain>(?:.*))\.example\.com$}sDiu"
Meaning it will capture anything preceding .example.com. If you requested company-a.admin.example.com, $subdomain would be company-a.admin.
You could also declare a route with two binding fields in its domain:
Route::domain('{subsubdomain}.{subdomain}.example.com')
->get('/foo/{param1}/{param2}',function(Router $router) {
// do something with it
});
Which might be more useful if you wanted subsubdomains to imply a hierarchy.
I have achieved this by using some checks, in RouteServiceProvider, I have not used the actual domain function on Route like we do normally i.e. Route::domain('foo.bar'). The reason was that, the Spatie package use a kind of middleware Spatie\Multitenancy\TenantFinder\DomainTenantFinder::class which runs whenever we hit the domain with tenant comapny-a.localhost. And it gets the tenant from hostname i.e comapny-a.localhost.
public function findForRequest(Request $request):?Tenant
{
$host = $request->getHost();
return $this->getTenantModel()::whereDomain($host)->first();
}
In my RouteServiceProvide:
$this->routes(function () {
$class = 'security';
$middleware = 'web';
if (Str::contains(request()->getHost(), 'admin')) {
$class = 'admin';
} elseif (Str::contains(request()->getHost(), 'employee')) {
$class = 'employee';
} elseif (Str::contains(request()->getHost(), 'api')) {
$class = 'api';
$middleware = 'api';
}
Route::middleware($middleware)
->name("$class.")
->group(base_path("routes/${class}.php"));
});
As In my scenario, I had only these 2 kind of second-level domains and so, I just checked if this particular keyword exists in the hostname and choosing the file and middleware accordingly.
I also overrided the DomainTenantFinder class and in multitenancy.php config file:
public function findForRequest(Request $request): ?Tenant
{
$host = $request->getHost();
$host = str_replace('admin.', '', $host);
$host = str_replace('employee.', '', $host);
$host = str_replace('api.', '', $host);
$tenant = $this->getTenantModel()::whereDomain($host)->first();
if (empty($tenant)) {
abort(404);
}
return $tenant;
}
I have acheived the desired outcome, however, I have a security concern, specially in RouteServiceProvider logic. Thought??

Laravel 6.2 - Dynamically Call a Controller action

I have used a code from Internet to call controller action dynamically. Here is the code for that, and is used in web.php. But I dont fully understand what it does.
Route::match(['get', 'post'], '{controller}/{action?}/{params1?}/{params2?}', function ($controller, $action = 'index', $params1 = '',$params2 = '') {
$params = explode('/', $params1);
$params[1] = $params2;
$app = app();
$controller = $app->make("\App\Http\Controllers\\" . ucwords($controller) . 'Controller');
return $controller->callAction($action, $params);
})->middleware('supadminauth');
Can someone explain?
Route::match(['get', 'post'], '{controller}/{action?}/{params1?}/{params2?}', function ($controller, $action = 'index', $params1 = '',$params2 = '') {
The first line looks at the request to see whether it is a get or post request, if it is some other types of request that means it does not match and will not proceed further. Then the url are separated into 4 parts corresponding by their name and passed into variables with the same name i.e. $controller, $action, $param1 and $params2 where the last 3 variables do not need to be present (with ? at the end of the name).
$params = explode('/', $params1);
$params[1] = $params2;
I believe this is a crude way to create an array of parameters as $params where the following would be more appropriate.
$params = [$params1, $params2];
.
$app = app();
$controller = $app->make("\App\Http\Controllers\\" . ucwords($controller) . 'Controller');
Then load the relevant controller.
return $controller->callAction($action, $params);
And run the corresponding action and passing all the parameters with it.
Hope this makes sense.
This is example of use it:
If you have controller like bellow:
class AdminController extends Controller {
public function index(){ //sample 0, sample 1
...
}
public function view($param1){ //sample2 , sample3
...
}
}
There is some sample route for calling them
sample0: yoursite.com/admin
sample1: yoursite.com/admin/index
sample2: yoursite.com/admin/view
sample3: yoursite.com/admin/view/5
Notice in your question ? in {action?} means it can either have value or not. Other things is simple and clear. Do you need more explaination?

Laravel Ajax controller with single route

Just wondering if this is a good way to write ajax code to interact with Laravel routes?
Example my application's require to list all customer data and also list all country through ajax. I have 3 controller ApiController, CustomerController, CountryController.
So in my routes.php I have this routes
Route::get('api/v1/ajax/json/{class}/{function}', 'Api\v1\ApiController#ajaxreturnjson');
In the ApiController.php, I have below function to call other controller function to return the data I need.
class ApiController extends Controller
{
public function ajaxreturnjson(Request $request, $controller, $function){
$input = $request->input();
if($request->input('namespace') != ''){
$namespace = $request->input('namespace');
unset($input['namespace']);
}else{
$namespace = 'App\Http\Controllers';
}
$data = array();
try {
$app = app();
$controller = $app->make($namespace.'\\'.$controller);
$data = $controller->callAction($function, array($request)+$input);
} catch(\ReflectionException $e){
$data['error'] = $e->getMessage();
}
return response()->json($data);
}
}
So example to use the ajax, I just need to pass the class name, namespace and also the function name to the ajax url.
Example to retrieve all customer info.
$.ajax({
dataType:"json",
url:"api/v1/ajax/json/CustomerController/getList",
data:"namespace=\\App\\Http\\Controllers\\",
success:function(data){
}
})
So in this way, I don't have to create so many routes for different ajax request.
But I am not sure if this will cause any security issue or is this a bad design?
Personally, I would not do it this way. Sure, you could do it this way, but it's not very semantic and debugging it could be a pain.
Also, if someone else begins working on the project, when they look at your routes file, they won't have any idea how your app is structured or where to go to find things.
I think it's better to have a controller for each Thing.

Obtain CodeIgniter links that consider routes.php

How can I link pages in my site considering routes.php?
Example:
$route['login'] = 'user/login';
The code above allows me to see "user/login" visiting just "login". But how can I link to that page using the internal route (user/login) and get as a result the "external route" "login".
I think it's important because I could change my URLs just modifiying "routes.php" and linking everything with internal routes.
From a Drupal perspective I can have my internal route "node/1" and the external url could be "about-us". So if I use "l('node/1')" this will return "about-us". Is there a function like "drupal_get_path_alias"?
Right now I can't find anything in the CI docs that point me to the right direction.
Thanks for your help.
You could have a look at using something like
http://osvaldas.info/smart-database-driven-routing-in-codeigniter
This would allow you to have the routes configured in the database. Then if you want to dynamically create you links through a model like this:
class AppRoutesModel extends CI_Model
{
public function getUrl($controller)
{
$this->db->select('slug');
$this->db->from('app_routes');
$this->db->where('controller', $controller);
$query = $this->db->result();
$data = $query->row();
$this->load->library('url');
return base_url($data->slug);
}
public function getController($slug)
{
$this->db->select('controller');
$this->db->from('app_routes');
$this->db->where('slug', $slug);
$query = $this->db->result();
$data = $query->row();
return $data->controller;
}
}
These have not been fully tested but will hopefully give you the general idea.
I hope this helps you :)
Edit------------------------------
You can create a routes_helper.php and add a function like
//application/helpers/routes_helper.php
function get_route($path)
{
require __DIR__ . '/../config/routes.php';
foreach ($route as $key => $controller) {
if ($path == $controller) {
return $key;
}
}
return false;
}
$this->load->helper('routes');
echo get_route('controller/method');
This does roughly what you want although this method does not support the $1 $2 etc vars that can be added to reflect the :num or :any wildcard that exist. You can edit the function to add that functionality but this will point you in the right direction :D
You can do that with .htaccess file:
Redirect 301 /user/login http://www.example.com/login

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