How to create dynamic URLs at the base URL of a CMS? - php

I'm displaying user profiles on a PHP website using usernames as part of the URL that links to the given user profile.
I can achieve this through a controller, the ProfileController, but the URL will look like this thewebsite.com/profile/show_profile/ANYUSERNAMEHERE
What i want is something similar to Facebook, where the username is appended just after the base URL:
https://www.facebook.com/zuck
I tried passing a variable to the Index function (Index()) of the home page controller (IndexController), but the URL becomes thewebsite.com/index/ANYUSERNAMEHERE and the base url thewebsite.com throws an error:
Too few arguments to function IndexController::index(), 0 passed and exactly 1 expected.
The home page controller:
<?php
class IndexController extends Controller
{
public function __construct()
{
parent::__construct();
}
// IF LEFT, THE VARIABLE $profile THROWS AN ERROR AT THE BASE URL
public function index($profile)
{
/** AFTER REMOVING THE $profile VARIABLE ABOVE AND THE 'if'
* STATEMENT BELOW, THE ERROR THROWN AT THE BASE URL VANISHES AND
* THE WEBSITE GOES BACK TO IT'S NORMAL STATE. THIS CODE WAS USED
* TRYING TO RENDER THE URL thewebsite.com/ANYUSERNAMEHERE BUT IT
* ONLY WORKS WITH thewebsite.com/index/ANYUSERNAMEHERE
*/
if (isset($profile)) {
$this->View->render('profiles/show_profile', array(
'profiles' => ProfileModel::getSelectedProfile($profile))
);
} else {
$this->View->render('index/index', array(
'profiles' => ProfileModel::getAllProfiles()));
}
}
The profile controller:
<?php
class ProfileController extends Controller
{
public function __construct()
{
parent::__construct();
Auth::checkAuthentication();
}
public function index()
{
$this->View->render('profiles/index', array(
'profiles' => ProfileModel::getAllProfiles())
);
}
public function show_profile($profile)
{
if (isset($profile)) {
$this->View->render('profiles/show_profile', array(
'profiles' => ProfileModel::getSelectedProfile($profile))
);
} else {
Redirect::home();
}
}
}
I was expecting the base URL to pass the argument (the username) to the IndexController's Index($profile) function, but the webpage throws an error and the expected result is being displayed from the wrong URL: thewebsite.com/index/ANYUSERNAMEHERE

You would need to use a router based on regular expressions, like FastRoute, or Aura.Router.
For example, with FastRoute you'd define and add a route to the so-called route collector ($r) like this:
$dispatcher = FastRoute\simpleDispatcher(function(FastRoute\RouteCollector $r) {
// The /{profile} suffix is optional
$r->addRoute('GET', '[/{profile}]', 'handler');
});
where handler is just a generic name for a customizable route handler in form of a callable. For example, if you'd additionally use the PHP-DI/Invoker library, the route handler ('handler') could look like one of the following callables (at least):
[ProfileController::class, 'show_profile']
'ProfileController::show_profile'
So the complete route definition would be like:
$r->addRoute('GET', '[/{profile}]', [ProfileController::class, 'show_profile']);
$r->addRoute('GET', '[/{profile}]', 'ProfileController::show_profile');
The placeholder name (profile) corresponds to the name of the parameter of the method ProfileController::show_profile:
class ProfileController extends Controller {
public function show_profile($profile) {
...
}
}
Even though the URL would look like you want it, e.g. thewebsite.com/zuck, I imagine that the placeholder {profile} of the above route definition would come in conflict with the fixed pattern parts defined in other route definitions, like /books in:
$r->addRoute('GET', '[/books/{bookName}]', 'handler');
So I suggest to maintain a URL of the form thewebsite.com/profiles/zuck, with the route definition:
$r->addRoute('GET', '/profiles/{profile}', 'handler');
I also suggest to read and apply the PHP Standards Recommendations in your code. Especially PSR-1, PSR-4 and PSR-12.

Related

Get current route inside Controller Laravel

I have several routes in web.php:
Route::get('/','PagesController#index');
Route::get('/contact','PagesController#contact');
and so on...
I need to get in my PagesController the current 'module' (index, contact or smth else).
The Controller's code:
class PagesController extends Controller
{
public function index()
{
$menu = new Menu();
$links = $menu->getMenu();
//$header=url()->current(); // returns the full url, e.g. http://test.com/
//$header=Route::current(); // error: Class Route not found
return view("index",['links'=>$links,'header'=>$header]);
}
}
For example, $header should be equal to "/" inside PagesController#index and $header = "contact" inside PagesController#contact.
I need the universal solution for all the modules I'll have in future.
Thanks a lot!
I have not had a chance to test this, however you should be able to achieve this with the following:
Route::get('/{page?}', function($page = 'index') {
return app('App\Http\Controllers\PageController')->{$page}($page);
});
Basically, this is setting a route to have an optional $page variable. If no page name is passed (e.g. contact), we default to index.
We finally use the app() helper to call the PageController and then use the ->{$page}() syntax to call a dynamic controller method (In the default case index).
Hope this helps.

Silverstripe: Not able to access Page fields in controller with custom route

I'm using SilverStripe 3.3.1 and have a custom route set up to handle urls with many parameters. That works.
However, the routing rule causes Page fields and functions to be inaccessible in the Page_Controller and templates. Any ideas how to fix this?
//MyPage class
class MyPage extends Page {
//Not accessible if route to controller specified in config.yml
private static $db = array(
'MyPageVar' => 'Int',
);
//Not accessible if route to controller specified in config.yml
public function getMySpecialVar() {
return $this->MyPageVar;
}
}
//MyPage_Controller class
class MyPage_Controller extends Page_Controller {
private static $allowed_actions = array(
'index',
'detailsearch',
);
private static $url_handlers = array (
'detailsearch/$Key1/$Value1/$Key2/$Value2/$Key3/$Value3/$Key4/$Value4/$Key5/$Value5' => 'detailsearch',
);
/**
* UseMyPageVar()
*
* #return Boolean
*/
public function UseMyPageVar() {
//Empty if route to controller specified in config.yml
Debug::show($this->MyPageVar);
Debug::show($this->Title);
Debug::show($this->Content);
//Error if route to controller specified in config.yml
Debug::show($this->getMySpecialVar());
return true;
}
}
MyPage.ss
<!-- This work as expected if no route is specified. -->
<!-- But all vars are empty if route is specified in config.yml -->
<p>MyVar: $MyPageVar</p>
<p>Title: $Title</p>
<p>Content: $Content</p>
Routing rule in config.yml
Director:
rules:
'mypage': 'MyPage_Controller'
This question is also posted on the Silverstripe forum:
http://www.silverstripe.org/community/forums/general-questions/editpost/413506
It's not pretty, but for now I've solved the problem by using a private var in the Controller class to hold a reference to the page.
//MyPage_Controller class
class MyPage_Controller extends Page_Controller {
private $_page; //reference to page that's lost with custom routing
//ContentController uses route, which has been changed to
// 'MyPage_Controller' by routing rule, to initialize
// page reference. Can't find anything so reference
// not set. (set to -1)
public function init() {
parent::init();
//Initialize using default route overwritten in routing rule
// This will break if URL segment changed in CMS
$route = array_search($this->URLSegment,
Config::inst()->get('Director', 'rules'));
$link = str_replace($this->URLSegment, $route, $this->Link());
$this->_page = $this->Page($link);
}
//Use private var to access page fields
public function MyPageVar() {
Debug::show($this->_page->MyPageVar);
}
//expose $Content to templates
public function Content() {
return $this->_page->Content;
}
//Can't use Title() so expose Page Title as $PageTitle
public function PageTitle() {
return $this->_page->Title;
}
}
Three things spring to mind when I look at your code:
That "mypage" in config.yml should be the name of a public method on MyPage_Controller. As it is, SilverStripe cannot find a matching method called mypage and will default to calling index() instead.
Routes should really go in a separate routes.yml file so you can "namespace" it to be invoked before or after SilverStripe's own core routes. If you don't do this, then it may result in the weird behaviour you're experiencing.
Did you know that you can debug your routes using the ?debug_request=1 URL param? See: https://docs.silverstripe.org/en/3.3/developer_guides/debugging/url_variable_tools#general-testing

ZF2 - pass variables to zend build-in helper

I would like to pass variable (that would be service menager) to a build-in helper of zend. Is it possible? To be more clearly:
There is a zend helper called Url, which constructs url's
In this helper I would like to get some data from database, so I need to pass there connection or model (doesn't matter really)
Depends on data get in point 2. I would like to construct my custom link
Well, the thing looks like this: I'm trying to make own custom routing. So in database I have controller, action and it's alias. For example:
Home\Controller\Home | index | myalias
Routing works fine, that means that if I type url:
example.com/myalias
Then Zend will open Home controller and index action. But on whole page I have url's made by Zend build-in Url helper, which looks like this:
$this->url('home', array('action' => 'index'));
So link looks:
example.com/home/index
I would like to change link to
example.com/myalias
without changing links generated by Url helper on whole page. So before helper return url, should check if that url have alias, and if so then should return that alias exept regular url.
In Module.php of the module where you have he helper class file, write the following -
//use statements
class Module {
//public function getAutoloaderConfig() { [...] }
//public function getConfig() { [...] }
public function getViewHelperConfig() {
return array(
'factories' => array(
'Url' => function ($sm) {
$locator = $sm->getServiceLocator();
$viewHelper = new View\Helper\Url;
//passing ServiceLocator to Url.php
$viewHelper->setServiceLocator($locator); //service locator is passed.
return $viewHelper;
},
),
);
}
}
Now in the Url.php, we need a function setServiceLocator() and getServiceLocator().
//use statements
class Url {
public $serviceLocator;
public function getServiceLocator() {
return $this->serviceLocator;
}
public function setServiceLocator($serviceLocator) {
if($this->serviceLocator == null)
$this->serviceLocator = $serviceLocator;
return;
}
}
I hope it helps.

Controller routing not working as expected in silverstripe 3.1

I'm setting up routing to a controller and I keep getting either a 404, or the 'getting started with the silverstripe framework' page.
In routes.yaml I have:
---
Name: nzoaroutes
After: framework/routes#coreroutes
---
Director:
rules:
'view-meetings/$Action/$type': 'ViewMeeting_Controller'
My controller looks like this:
class ViewMeeting_Controller extends Controller {
public static $allowed_actions = array('HospitalMeetings');
public static $url_handlers = array(
'view-meetings/$Action/$ID' => 'HospitalMeetings'
);
public function init() {
parent::init();
if(!Member::currentUser()) {
return $this->httpError(403);
}
}
/* View a list of Hospital meetings of a specified type for this user */
public function HospitalMeetings(SS_HTTPRequest $request) {
print_r($arguments, 1);
}
}
And I've created a template (ViewMeeting.ss) that simply outputs $Content, but when I flush the site cache and visit /view-meetings/HospitalMeetings/6?flush=1
I get the default 'getting started with the Silverstripe framework' page
I know the routing in routes.yaml is working, because if I change the route there and visit the old URL I get a 404, but the request doesn't seem to fire my $Action...
You had 2 different rules in your YAML and controller ($type vs $ID). Also, I don't think you need to define the route in both YAML and the Controller.
Try this, the YAML tell SS to send everything that starts with 'view-meetings' to your Controller, then $url_handlers tell the Controller what to do with the request depending on everything after 'view-meetings' in the URL.
routes.yaml
---
Name: nzoaroutes
After: framework/routes#coreroutes
---
Director:
rules:
'view-meetings': 'ViewMeeting_Controller'
ViewMeeting_Controller.php
class ViewMeeting_Controller extends Controller {
private static $allowed_actions = array('HospitalMeetings');
public static $url_handlers = array(
'$Action/$type' => 'HospitalMeetings'
);
public function init() {
parent::init();
if(!Member::currentUser()) {
return $this->httpError(403);
}
}
public function HospitalMeetings(SS_HTTPRequest $request) {
}
}
The Silverstripe documentation on routing isn't at all clear on this point, but for $Action to be correctly interpreted you should use a double slash before it in the routes.yml file:
view-meetings//$Action/$type
According to the documentation, this sets something called the 'shift point'. Exactly what this means isn't described very well either in the documentation or in the source code which matches URLs against rules.
I'm doing some guessing here, but what if you drop the
public static $url_handlers = array(
'view-meetings/$Action/$ID' => 'HospitalMeetings'
);
part and change the Action method to:
// View a list of Hospital meetings of a specified type for this
public function HospitalMeetings(SS_HTTPRequest $request) {
// Should print 8 if url is /view-meetings/HospitalMeetings/6
print_r($request->param('type');
}

Zend Framework _forward to other action inside same controller

How can i forward to other action inside the same controller avoiding repeat all dispatch proccess ?
Example:
If i point to User Controller the default action is indexAction() inside this funciton i use _forwad('list') ... but all dispatch proccess are repeated.. and i dont that
Whats is the right way ?
Usually, you will install routes to redirect your users to the proper (default) action, instead of the index action (read how to redirect from a given route using Zend_Router). But you can do everything manually if you really want to (however this is called "writing hacker code to achieve something dirty") directly from the controller.
Change your "view script" to be rendered, then call your action method....
// inside your controller...
public function indexAction() {
$this->_helper->viewRenderer('foo'); // the name of the action to render instead
$this->fooAction(); // call foo action now
}
If you tend on using this "trick" often, perhaps you may write a base controller that you extend in your application, which can simply have a method like :
abstract class My_Controller_Action extends Zend_Controller_Action {
protected function _doAction($action) {
$method = $action . 'Action';
$this->_helper->viewRenderer($action);
return $this->$method(); // yes, this is valid PHP
}
}
Then call the method from your action...
class Default_Controller extends My_Controller_Action
public function indexAction() {
if ($someCondition) {
return $this->_doAction('foo');
}
// execute normal code here for index action
}
public function fooAction() {
// foo action goes here (you may even call _doAction() again...)
}
}
NOTE : this is not the official way to do it, but it is a solution.
We Can Also use this Helper To redirect
$this->_helper->redirector->gotoSimple($action, $controller, $module, $params);
$this->_helper->redirector->gotoSimple('edit'); // Example 1
$this->_helper->redirector->gotoSimple('edit', null, null, ['id'=>1]); // Example 2 With Params
If you don't want to re-dispatch there is no reason you can't simply call the action - it's just a function.
class Default_Controller extends My_Controller_Action
{
public function indexAction()
{
return $this->realAction();
}
public function realAction()
{
// ...
}
}
You could also create a route. For example I have in my /application/config/routes.ini a section:
; rss
routes.rss.route = rss
routes.rss.defaults.controller = rss
routes.rss.defaults.action = index
routes.rssfeed.route = rss/feed
routes.rssfeed.defaults.controller = rss
routes.rssfeed.defaults.action = index
Now you only need one action and that is index action but the requess rss/feed also goes there.
public function indexAction()
{
...
}

Categories