My route cannot be found if I add a / at the end as you see below.
class QuizController extends Controller {
/**
* #Route("/quiz/{name}")
*/
public function showAction($name = '') {
$templating = $this->container->get('templating');
$html = $templating->render('quiz/show.html.twig', [
'name' => $name,
'title' => 'Hello World'
]);
return new Response($html);
}
}
The problem is:
myurl/quiz/whatever works
myurl/quiz works
myurl/quiz/ doesn't work
Whenever there is a slash but no value behind, I get the message
No route found for "GET /"
I'm new to Symfony. How can I fix this?
Using myurl/quiz indicates the {name} parameter is not used, but when you use myurl/quiz/ it needs the {name} parameter to be passed in.
Suggests you add defaults and a name for the route like so:
/**
* #Route("/quiz/{name}")
* defaults={"name" = 0},
* name="quiz")
*/
Then if you use myurl/quiz/ it will send myurl/quiz/0. This is an example
Add the ending slash in the route declaration, the route without / will be automatically redirected to the one with the /
class QuizController extends Controller
{
/**
* #Route("/quiz/{name}/")
*/
public function showAction($name = '')
{
$templating = $this->container->get('templating');
$html = $templating->render('quiz/show.html.twig', [
'name' => $name,
'title' => 'Hello World'
]);
return new Response($html);
}
}
Edit (via phone) After OP comments Below:
put 2 #Route declarations before controller action, one with slash one without
#Route("/quiz/")
#Route("/quiz/{name}")
Since OP wants the same route controller to handle both trailing slash and non-trailing slash, the solution is the allow Symfony to accept a slash as part of the variable (which is doesn't by default).
Docs Here : http://symfony.com/doc/current/routing/slash_in_parameter.html
So your route becomes
class QuizController extends Controller {
/**
* #Route("/quiz{name}", name="quiz_name", requirements={"name"=".+"})
*/
public function showAction($name = '') {
$templating = $this->container->get('templating');
$html = $templating->render('quiz/show.html.twig', [
'name' => $name,
'title' => 'Hello World'
]);
return new Response($html);
}
}
Please Note: While this answers the OP's question, it's a dangerour practice. For anyone reading this, it might suit the OP for his logic but note that it will also match any route
/quiz*
So while the OP wants this, it will also match
/quiz/name
/quiz/
/quiz
/quiz/name/edit
/quiz/name/delete
For OP, a work-around is to include edit, delete routes before this custom one as Symfony matches routes by first match wins basis.
Related
I am having a problem getting SilverStripe Fluent module to work with content/page controllers. Whenever a locale url segment is provided, the controller returns 404. For example, http://site.local/search works but http://site.local/en/search returns 404.
I tried using route config by pointing mi/search to the controller name. The template renders but the current locale is not correct.
To reproduce:
Set up a SilverStripe project using composer create-project silverstripe/installer test
Require the module composer require tractorcow/silverstripe-fluent
Setup 2 locale
English with url segment 'en'
Maori with url segment 'mi'
Create a simple controller called SearchController
Create a route.yml in the config folder
Create template file called Search.ss in the template folder
<?php
namespace App\Controllers;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\CMS\Controllers\ContentController;
class SearchController extends ContentController
{
private static $allowed_actions = [
'index',
];
public function index(HTTPRequest $request)
{
return $this->renderWith('Search');
}
}
---
Name: approutes
After: framework/_config/routes#coreroutes
---
SilverStripe\Control\Director:
rules:
'search//': 'App\Controllers\SearchController'
# 'mi/search//': 'App\Controllers\SearchController'
# 'en/search//': 'App\Controllers\SearchController'
<h1>Search</h1>
$CurrentLocale
Navigate to <baseurl>/mi/search, the template should render:
<h1>Search</h1>
mi_NZ
But error 404 is returned.
Concept
So Fluent supports localising via query string out of the box, but appending locales to the URL is reserved for pages driven by the CMS. With your example I'm able to see correct results via /search?l=en and /search?l=mi.
In order to allow locales in the URL for non-SiteTree routes, we can patch FluentDirectorExtension, which is the class responsible for injecting Fluent's routing rules, and add support for explicit configuration of routes that should also be localisable. This can be achieved by adding Director rules that essentially do the same thing as above, but masking the /search?l=en URL as /en/search in the background.
My example configuration is something like this:
TractorCow\Fluent\Extension\FluentDirectorExtension:
static_routes: # Routes that should also allow URL segment based localisation
- 'search//'
This should match the rule key in your Director.rules config.
We can then construct the new URL to allow support for, and tell Director to use the existing configured controller while also passing the l argument for the locale transparently. We need to do this for each locale, and the rules need to be inserted before Fluent's default rules are. An example of what you could do:
diff --git a/src/Extension/FluentDirectorExtension.php b/src/Extension/FluentDirectorExtension.php
index 6ebf1d6..0cdd80b 100644
--- a/src/Extension/FluentDirectorExtension.php
+++ b/src/Extension/FluentDirectorExtension.php
## -116,7 +116,10 ## class FluentDirectorExtension extends Extension
protected function getExplicitRoutes($originalRules)
{
$queryParam = static::config()->get('query_param');
+ $staticRoutes = static::config()->get('static_routes');
$rules = [];
+ $prependRules = []; // we push these into the $rules before default fluent rules
+
/** #var Locale $localeObj */
foreach (Locale::getCached() as $localeObj) {
$locale = $localeObj->getLocale();
## -138,8 +141,22 ## class FluentDirectorExtension extends Extension
'Controller' => $controller,
$queryParam => $locale,
];
+
+ // Include opt-in static routes
+ foreach ($staticRoutes as $staticRoute) {
+ // Check for a matching rule in the Director configuration
+ if (!isset($originalRules[$staticRoute])) {
+ continue;
+ }
+
+ $prependRules[$url . '/' . $staticRoute] = [
+ 'Controller' => $originalRules[$staticRoute],
+ $queryParam => $locale,
+ ];
+ }
}
- return $rules;
+
+ return array_merge($prependRules, $rules);
}
/**
If you debug $rules at the end of the updateRules() method, you'll see that Fluent has now injected a new rule for that route in each locale:
'en/search//' =>
array (size=2)
'Controller' => string 'App\Controllers\SearchController' (length=42)
'l' => string 'en_NZ' (length=5)
'mi/search//' =>
array (size=2)
'Controller' => string 'App\Controllers\SearchController' (length=42)
'l' => string 'mi_NZ' (length=5)
Implementation
I'm going to formulate a pull request to the module for this change once I can back it up with some unit tests, but in the meantime, you can implement this by using an Injector override in your project code, and extend the protected getExplicitRoutes method to implement the changes above:
SilverStripe\Core\Injector\Injector:
TractorCow\Fluent\Extension\FluentDirectorExtension:
class: MyFluentDirectorExtension
class MyFluentDirectorExtension extends FluentDirectorExtension
{
protected function getExplicitRoutes($originalRules)
{
$rules = parent::getExplicitRoutes($originalRules);
$staticRoutes = static::config()->get('static_routes');
$queryParam = static::config()->get('query_param');
$prependRules = [];
// Include opt-in static routes
foreach (Locale::getCached() as $localeObj) {
foreach ($staticRoutes as $staticRoute) {
$locale = $localeObj->getLocale();
$url = urlencode($localeObj->getURLSegment());
// Check for a matching rule in the Director configuration
if (!isset($originalRules[$staticRoute])) {
continue;
}
$prependRules[$url . '/' . $staticRoute] = [
'Controller' => $originalRules[$staticRoute],
$queryParam => $locale,
];
}
}
return array_merge($prependRules, $rules);
}
}
I recently dove into the world of laravel (version 5.4). While initially confused, the concept of MVC makes a lot of sense in writing large applications. Applications that you want to be easily understood by outside developers.
Using laravel for this has greatly simplified coding in PHP and has made the language fun again. However, beyond dividing code into its respective models, views, and controllers, what happens if we need to divide controllers to prevent them from growing too large?
A solution that I have found to this is to define one controller each folder and then fill that controller with traits that further add functionalities to the controller. (All-caps = folder):
CONTROLLER
HOME
Controller.php
TRAITS
additionalFunctionality1.php
additionalFunctionality2.php
additionalFunctionality3.php
...
ADMIN
Controller.php
TRAITS
additionalFunctionality1.php
additionalFunctionality2.php
additionalFunctionality3.php
...
Within routes/web.php I woud initialize everything as so:
Route::namespace('Home')->group(function () {
Route::get('home', 'Controller.php#loadPage');
Route::post('test', 'Controller.php#fun1');
Route::post('test2', 'Controller.php#fun2');
Route::post('test3', 'Controller.php#fun3');
});
Route::namespace('Admin')->group(function () {
Route::get('Admin', 'Controller.php#loadPage');
Route::post('test', 'Controller.php#fun1');
Route::post('test2', 'Controller.php#fun2');
Route::post('test3', 'Controller.php#fun3');
});
With me being new to laravel, this seems like a simple and elegant way to organize my logic. It is however something I do not see while researching laravel controller organization.
The Question
Is there an issue, both in the short-run and in the long-run, of organizing my data like this? What is a better alternative?
Example Controller:
<?php
namespace App\Http\Controllers\Message;
use DB;
use Auth;
use Request;
use FileHelper;
use App\Http\Controllers\Message\Traits\MessageTypes;
use App\Http\Controllers\Controller;
class MessageController extends Controller
{
// Traits that are used within the message controller
use FileHelper, MessageTypes;
/**
* #var array $data Everything about the message is stored here
*/
protected $data = []; // everything about the message
/**
* #var booloean/array $sendableData Additional data that is registered through the send function
*/
protected $sendableData = false;
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('auth');
$this->middleware('access');
}
/**
* Enable sendableData by passing data to the variable
*
* #param array $data Addition data that needs to registrered
* #return MessageController
*/
protected function send ($data = []) {
// enable sendableData by passing data to the variable
$this->sendableData = $data;
return $this;
}
/**
* Enable sendableData by passing data to the variable
*
* #param string $type The type of message that we will serve to the view
* #return MessageController
*/
protected function serve ($type = "message") {
$this->ss();
$this->setData(array_merge($this->sendableData, $this->status[$type]));
$this->data->id = DB::table('messages')->insertGetId((array) $this->data);
}
/**
* Set the data of the message to be used to send or construct a message
* Note that this function turns "(array) $data" into "(object) $data"
*
* #param array $extend Override default settings
* #return MessageController
*/
protected function setData(array $extend = []) {
$defaults = [
"lobby" => Request::get('lobbyid'),
"type" => "text",
"subtype" => null,
"body" => null,
"time" => date("g:ia"),
"user" => Auth::User()->username,
"userid" => Auth::User()->id,
"day" => date("j"),
"month" => date("M"),
"timestamp" => time(),
"private" => Request::get('isPrivate') ? "1" : "0",
"name" => Request::get('displayname'),
"kicker" => null
];
$this->data = (object) array_merge($defaults, $extend);
// because a closure can not be saved in the database we will remove it after we need it
unset($this->data->message);
return $this;
}
/**
* Send out a response for PHP
*
* #return string
*/
public function build() {
if($this->data->type == "file") {
$filesize = #filesize("uploads/" . $this->data->lobby . "/" . $this->data->body);
$this->data->filesize = $this->human_filesize($filesize, 2);
}
// do not send unneccessary data
unset($this->data->body, $this->data->time, $this->data->kicker, $this->data->name, $this->data->timestamp);
return $this->data;
}
/**
* Send out a usable response for an AJAX request
*
* #return object
*/
public function json() {
return json_encode($this->build());
}
}
?>
Laravel architecture is simple enough for any size of the application.
Laravel provides several mechanisms for developers to tackle the fatty controllers in your Application.
Use Middlewares for authentications.
Use Requests for validations and manipulating data.
Use Policy for your aplication roles.
Use Repository for writing your database queries.
Use Transformers for your APIs to transform data.
It depends on your application. if it is too large and have different Modules or functionalities then you should use a modular approach.
A nice package is available for making independent modules here
Hope this helps.
I think you should do a little differently ! First you should use your traits at the same levels as the controllers since traits are not controllers, your tree should look more like :
Http
Controller
Controller.php
Home
YourControllers
Admin
Your admin controllers
Traits
Your Traits
Next your routes need to be more like that :
Route::group(['prefix' => 'home'], function()
{
Route::get('/', 'Home\YourController#index')->name('home.index');
}
Route::group(['prefix' => 'admin', 'middleware' => ['admin']], function()
{
Route::get('/', 'Admin\DashboardController#index')->name('dashboard.index');
}
You can use many kink or routes like :
Route::post('/action', 'yourControllers#store')->name('controller.store');
Route::patch('/action', 'yourControllers#update')->name('controller.update');
Route::resource('/action', 'yourController');
The Resource route creates automatically the most used your, like post, patch, edit, index.. You just need to write the action and the controller called with its action. You can check out your toutes with this command : php artisan route:list
Laravel also has many automated features, like the creation of a controller with this command : php artisan make:controller YourController.
For the routes the prefix creates portions of url, for example all the routes inside the route group with the prefix 'admin' will lool like : www.yourwebsite.com/admin/theroute, and can also be blocked for some users with a middleware.
To get familiar with laravel i suggest you follow the laravel 5.4 tutorial from scratch by Jeffrey Way on Laracasts, he's awesome at explaining and showing how laravel works. Here is a link : https://laracasts.com/series/laravel-from-scratch-2017
Hope it helps, ask me if you want to know anything else or have some precisions, i'll try to answer you !
Yii is giving me 404 Error if I declare an action like this:
SiteController.php
public function actionRegisterUser()
This is how I call it in the main.php
['label' => 'Register User', 'url' => ['/site/RegisterUser']],
I tried several different combinations. The only combination that will work is this naming convention in both places:
public function actionRegisteruser
'url' => ['/site/registeruser']
I used to work on another Yii project (Yii 1.0) and I could name my actions in camel case and call them without any problem. Do I need to turn on some sort of setting to do this?
I also tried playing with the rules of the Controller but that didn't solve anything.
In some cases you need camelcase link. For example, for SEO purposes (keep inbound links). You could create rewrite rule on web server side or add inline rule to URL manager on app side. Example:
'urlManager' => [
'rules' => [
'<controller:RegisterUser>/<action:\w+>'=>'register-user/<action>',
],
],
Also it's possible to write custom URL rule. Example:
namespace app\components;
use yii\web\UrlRuleInterface;
use yii\base\Object;
class CarUrlRule extends Object implements UrlRuleInterface
{
public function createUrl($manager, $route, $params)
{
if ($route === 'car/index') {
if (isset($params['manufacturer'], $params['model'])) {
return $params['manufacturer'] . '/' . $params['model'];
} elseif (isset($params['manufacturer'])) {
return $params['manufacturer'];
}
}
return false; // this rule does not apply
}
public function parseRequest($manager, $request)
{
$pathInfo = $request->getPathInfo();
if (preg_match('%^(\w+)(/(\w+))?$%', $pathInfo, $matches)) {
// check $matches[1] and $matches[3] to see
// if they match a manufacturer and a model in the database
// If so, set $params['manufacturer'] and/or $params['model']
// and return ['car/index', $params]
}
return false; // this rule does not apply
}
}
And use the new rule class in the [[yii\web\UrlManager::rules]] configuration:
[
// ...other rules...
[
'class' => 'app\components\CarUrlRule',
// ...configure other properties...
],
]
You need to specify your action like this ['/site/register-user']. As documentation says about Inline Actions:
index becomes actionIndex, and hello-world becomes actionHelloWorld
I am currently trying to create a link on the index page that'll allow users to create an item. My routes.php looks like
Route::controller('items', 'ItemController');
and my ItemController looks like
class ItemController extends BaseController
{
// create variable
protected $item;
// create constructor
public function __construct(Item $item)
{
$this->item = $item;
}
public function getIndex()
{
// return all the items
$items = $this->item->all();
return View::make('items.index', compact('items'));
}
public function getCreate()
{
return View::make('items.create');
}
public function postStore()
{
$input = Input::all();
// checks the input with the validator rules from the Item model
$v = Validator::make($input, Item::$rules);
if ($v->passes())
{
$this->items->create($input);
return Redirect::route('items.index');
}
return Redirect::route('items.create');
}
}
I have tried changing the getIndex() to just index() but then I get a controller method not found. So, that is why I am using getIndex().
I think I have set up my create controllers correctly but when I go to the items/create url I get a
Unable to generate a URL for the named route "items.store" as such route does not exist.
error. I have tried using just store() and getStore() instead of postStore() but I keep getting the same error.
Anybody know what the problem might be? I don't understand why the URL isn't being generated.
You are using Route::controller() which does generate route names as far as I know.
i.e. you are referring to "items.store" - that is a route name.
You should either;
Define all routes specifically (probably best - see this blog here)
Use Route::resource('items', 'ItemController'); see docs here
If you use Route::resource - then you'll need to change your controller names
The error tells you, that the route name is not defined:
Unable to generate a URL for the named route "items.store" as such route does not exist.
Have a look in the Laravel 4 Docs in the Named Routes section. There are several examples that'll make you clear how to use these kind of routes.
Also have a look at the RESTful Controllers section.
Here's an example for your question:
Route::get('items', array(
'as' => 'items.store',
'uses' => 'ItemController#getIndex',
));
As The Shift Exchange said, Route::controller() doesn't generate names, but you can do it using a third parameter:
Route::controller( 'items',
'ItemController',
[
'getIndex' => 'items.index',
'getCreate' => 'items.create',
'postStore' => 'items.store',
...
]
);
Using Symfony2 and PdfBundle to generate dynamically PDF files, I don't get to generate the files indeed.
Following documentation instructions, I have set up all the bundle thing:
autoload.php:
'Ps' => __DIR__.'/../vendor/bundles',
'PHPPdf' => __DIR__.'/../vendor/PHPPdf/lib',
'Imagine' => array(__DIR__.'/../vendor/PHPPdf/lib', __DIR__.'/../vendor/PHPPdf/lib/vendor/Imagine/lib'),
'Zend' => __DIR__.'/../vendor/PHPPdf/lib/vendor/Zend/library',
'ZendPdf' => __DIR__.'/../vendor/PHPPdf/lib/vendor/ZendPdf/library',
AppKernel.php:
...
new Ps\PdfBundle\PsPdfBundle(),
...
I guess all the setting up is correctly configured, as I am not getting any "library not found" nor anything on that way...
So, after all that, I am doing this in the controller:
...
use Ps\PdfBundle\Annotation\Pdf;
...
/**
* #Pdf()
* #Route ("/pdf", name="_pdf")
* #Template()
*/
public function generateInvoicePDFAction($name = 'Pedro')
{
return $this->render('AcmeStoreBundle:Shop:generateInvoice.pdf.twig', array(
'name' => $name,
));
}
And having this twig file:
<pdf>
<dynamic-page>
Hello {{ name }}!
</dynamic-page>
</pdf>
Well. Somehow, what I just get in my page is just the normal html generated as if it was a normal Response rendering.
The Pdf() annotation is supposed to give the "special" behavior of creating the PDF file instead of rendering normal HTML.
So, having the above code, when I request the route http://www.mysite.com/*...*/pdf, all what I get is the following HTML rendered:
<pdf>
<dynamic-page>
Hello Pedro!
</dynamic-page>
</pdf>
(so a blank HTML page with just the words Hello Pedro! on it.
Any clue? Am I doing anything wrong? Is it mandatory to have the alternative *.html.twig apart from the *.pdf.twig version? I don't think so... :(
Ok I got it.
For some reason, the example that comes in the bundle documentation didn't work for me. Nevertheless, there is this class in de bundle: http://github.com/psliwa/PdfBundle/blob/master/Controller/ExampleController.php, where I could find an example that did work for me. This is the code that I finally used:
/**
* #Route ("/generateInvoice", name="_generate_invoice")
*/
public function generateInvoiceAction($name = 'Pedro')
{
$facade = $this->get('ps_pdf.facade');
$response = new Response();
$this->render('AcmeStoreBundle:Shop:generateInvoiceAction.pdf.twig', array("name" => $name), $response);
$xml = $response->getContent();
$content = $facade->render($xml);
return new Response($content, 200, array('content-type' => 'application/pdf'));
}
Next challenge: store that PDF into disk.
It's because you've missed the "_format" option in the URL.
$this->render() shouldn't be used with the #Template annotation. The #Template will serve the correct template's format depending of the _format parameter.
...
use Ps\PdfBundle\Annotation\Pdf;
...
/**
* #Pdf()
* #Route ("/pdf.{_format}", name="_pdf")
* #Template()
*/
public function generateInvoicePDFAction($name = 'Pedro')
{
return array('name' => $name);
}
Should work fine.