Controller check requirements to process action - php

I'm trying to figure out if there is possibility to check requirements before processing controller action. In Nette there are methods like checkRequirements, onStartup, beforeRender where I can check this.
I have api resource album/{albumId}/song/ and I would like to check if album with given id exists every time any action on my SongController is processed and return 404 status code if not.
So far I have found this article in Symfony documentation where I found there are no methods like preExecute and postExecute. However I guess there is bundle or something like that to add those methods. I think it does not make sense to create new class to use it only in one controller.
Are there any other options to do it?

ParamConverter does that. It looks for a entity using the id supplied from the route and throws an exception, returning a 404 if it doesn't find anything.
// paramConverter requires that you type-hint a class, which is a best practice anyway :)
public function getArtist(\Appbundle\Entity\Song $song)
{
//...
}

Related

Which MVC thing I need here?

In general there are ActionController, Repositories, Models und Views in TYPO3 Flows domain driven MVC system. In our project we use a general File model that contains the Ressource.
Now we need a special "expert" php script like an action controller that doesn't listen to certain url actions. It should get such a File object, do something internal like logging stuff or manipulate the object after a special procedure and give back an information / return falue.
What mvc thing I need for that? An interface? A manager? How you call that and how do I initialise it in TYPO3 Flow? Or is the FileController (action controller) exact the thing I have to use for that?
This "expert" shouldn't listen to url actions but should be used like an action controller like
$expertyThing = new ../../Expertything();
$expertyThing->doCoolStuff($file);
and should can use thinks like the PersistenceManager (by injection or anyhow).
Thanks for any input for that.
I would say Service but I'm not sure if I understood you correctly.
I guess you have some FileController and you have createFileAction there, which creates new File model from uploaded resource, do some validation, transformations, renaming and save it using injected FileRepository.. And you want something in middle.
So I create FileService for that My/FileManager/Domain/Service/FileService.php - inject repository and other services there. And in action or command controllers I inject those services and they do "expert" stuff (and I don't have to duplicate code), like that:
// FileController
public function createFileAction(Resource $resource) {
try {
$file = $this->fileService->processAndSaveFile($resource);
} catch (\Exception $e) {
$this->addFlashMessage($e->getMessage(), '', Message::SEVERITY_ERROR);
$this->forwardToReferringRequest();
}
$this->addFlashMessage('File created');
$this->redirect('fileList');
}
So for me FileService do expert stuff for File - it creates new File model (maybe using FileFactory), do transformations using other services like ImageService, has repository and logger injected (but you can use Aspects for cases like logging).. and if something goes wrong it throws some FileException.
And of course FileService may implement some FileServiceInterface, and you can inject this interface to your controller and define in Objects.yaml which service should be used (it makes it more flexible, so someone else could implement it and replace your FileService not touching it).
This "Service" approach may be a little bit outdated, so maybe someone will suggest better solution.. If you want follow Flow rules, just check how they handle stuff like that in official packages.

DynamicRoute Plugin for CakePHP giving 404 errors

I'm using this plugin to create slug-based URL's on a CakePHP 2 web application: https://github.com/josegonzalez/cakephp-dynamic-route
The documentation suggests that you would call a Cake controller like so:
posts/view?id=45
My URL's currently work as Cake's default behaviour. So using the example above posts/view/45 works but posts/view?id=45 does not.
When I call URL's as per the example I get a 404 error.
My functions are written like so (e.g. in PostsController.php):
public function view($id) {
// logic to load post by ID
// ...
}
There is almost no documentaiton for the above plugin. Has anyone used it or know where I'm going wrong? It seems you cannot pass a GET variable such as 'id' to the 'view' function, without re-factoring the code inside it to accept passed parameters?
The solution appears to be that some of the controller functions needed to be re-written to accept GET style parameters.
In the documentation a "spec" field looks like this:
posts/view?id=45
In a regular CakePHP application the route for that would be like this: posts/view/45
The plugin simply doesn't work if you put the second style of route (posts/view/45) into the "spec" field.
So the answer is the "spec" fields must be like so:
posts/view?id=45 and then your controller functions have to be re-written, e.g.
public function view($id) {
if (isset($this->request->params['id'])) {
$id = $this->request->params['id'];
}
}
Doing this means that it will work with a parameter (view?id=45) or a standard Cake call (view/45).
Please note this has nothing to do with the "slug" aspect of the plugin - the "slug" can be anything, as per the documentation examples: /why-isnt-this-pup-asleep or /manchester/cakephp-developers-dance-to-beyonce. The original question was asking if there was a way to map a "spec" given in the documentation to a Cake controller function without having to modify it like I have above. The answer seems to be no, you have to modify them!

Controller as Service - How to pass and return values in an advanced case?

Using Symfony, I am displaying a table with some entries the user is able to select from. There is a little more complexity as this might include calling some further actions e. g. for filtering the table entries, sorting by different criteria, etc.
I have implemented the whole thing in an own bundle, let's say ChoiceTableBundle (with ChoiceTableController). Now I would like to be able to use this bundle from other bundles, sometimes with some more parametrization.
My desired workflow would then look like this:
User is currently working with Bundle OtherBundle and triggers chooseAction.
chooseAction forwards to ChoiceTableController (resp. its default entry action).
Within ChoiceTableBundle, the user is able to navigate, filter, sort, ... using the actions and routing supplied by this bundle.
When the user has made his choice, he triggers another action (like choiceFinishedAction) and the control flow returns to OtherBundle, handing over the results of the users choice.
Based on these results, OtherBundle can then continue working.
Additionally, OtherOtherBundle (and some more...) should also be able to use this workflow, possibly passing some configuration values to ChoiceTableBundle to make it behave a little different.
I have read about the "Controller as Service" pattern of Symfony 2 and IMHO it's the right approach here (if not, please tell me ;)). So I would make a service out of ChoiceTableController and use it from the other bundles. Anyway, with the workflow above in mind, I don't see a "good" way to achieve this:
How can I pass over configuration parameters to ChoiceTableBundle (resp. ChoiceTableController), if neccessary?
How can ChoiceTableBundle know from where it was called?
How can I return the results to this calling bundle?
Basic approaches could be to store the values in the session or to create an intermediate object being passed. Both do not seem particularly elegant to me. Can you please give me a shove in the right direction? Many thanks in advance!
The main question is if you really need to call your filtering / searching logic as a controller action. Do you really need to make a request?
I would say it could be also doable just by passing all the required data to a service you define.
This service you should create from the guts of your ChoiceTableBundleand let both you ChoiceTableBundle and your OtherBundle to use the extracted service.
service / library way
// register it in your service container
class FilteredDataProvider
{
/**
* #return customObjectInterface or scallar or whatever you like
*/
public function doFiltering($searchString, $order)
{
return $this->filterAndReturnData($searchString, $order)
}
}
...
class OtherBundleController extends Controller {
public function showStuffAction() {
$result = $this->container->get('filter_data_provider')
->doFiltering('text', 'ascending')
}
}
controller way
The whole thing can be accomplished with the same approach as lipp/imagine bundle uses.
Have a controller as service and call/send all the required information to that controller when you need some results, you can also send whole request.
class MyController extends Controller
{
public function indexAction()
{
// RedirectResponse object
$responeFromYourSearchFilterAction = $this->container
->get('my_search_filter_controller')
->filterSearchAction(
$this->request, // http request
'parameter1' // like search string
'parameterX' // like sorting direction
);
// do something with the response
// ..
}
}
A separate service class would be much more flexible. Also if you need other parameters or Request object you can always provide it.
Info how to declare controller as service is here:
http://symfony.com/doc/current/cookbook/controller/service.html
How liip uses it:
https://github.com/liip/LiipImagineBundle#using-the-controller-as-a-service

Symfony2 Service Response

i was trying to setup a general service which handles common function i use very often everywhere in my project. For example if a user wants to purchase something for virtual currency there would be a function which checks, if the user has enough virtual currency in his account.
If the user doesnt have enough virtual currency I want this function to make a JSOn Response, but of cource, only controllers are allowed to response. But this means i have to check in every action I use this function, whether the purchase is valid or not.
Here is the function call in my Controller:
$purchaes= $this->get('global_functions')->payVirtualCurrency($user_id, $currency_amount);
if($change instanceof JsonResponse){
return $change;
}
And the function:
public function payVirtualCurrency($user_id, $currency_amount){
$user = $this->dm->getRepository('LoginBundle:User')->findOneById($user_id);
if($user->getVirtualCurrency() < $currency_amount){
return new JsonResponse(array('error' => $this->trans->trans('Insufficient amount of virtual Currency')));
}
return true;
}
Is there a better way to do this? I really want to avoid doing the same thing in the controller over and over again.
Thanks in advance!
Two options come to my mind, both are quite elegant solutions but both require little work:
1. Create custom exception listener
Create custom exception, let's call it InsufficientMoneyException. Then, your sevice can be as it is, but instead of returning response it throws your custom exception (in case user does not have enough money). Then, you create custom exception listener which listenes to InsufficientMoneyException custom exception and returns your desired JsonResponse.
2. Create custom annotation
You can create custom annotation and flag a controller action with this annotation. It would look something like this
/**
* #MinimumMoneyRequired("50")
*/
public function buyAction()
{
(...)
}
This option is really nice and decoupled but it require quite a lot of configuration. This is nice blog post with detailed description how to create custom annotations

Get parameter passed in URL in Laravel view as variable directly?

Right now, if I have a parameter in a URL in one of my Laravel projects, I have to detect the route and grab the parameter:
Route::get('mission/{name}', 'MissionsController#show');
Pass the $name parameter as an argument to my controller:
class MissionsController extends BaseController {
public function show($missionName) {
...
}
}
Then pass it along to the view that is returned in my controller method:
return View::make('missions.mission', array(
'name' => $missionName
));
Before then using the $missionName variable in my view:
<p>{{ $missionName }}</p>
This is quite a roundabout way of doing so. Is there any way I can get the parameter from the URL directly in my view? I've tried accessing the $_GET superglobal, but it is empty. Surely there must be a better way of doing this.
Thoughts?
Use this code :
{{ Route::current()->getParameter('theParameterName') }}
EDIT: Above doesn't seem to be supported anymore in recent Laravel versions. You should use #lukasgeiter answer instead:
Route::input('name');
There is a nice shortcut for Route::current()->getParameter():
Route::input('name');
For small projects or simple examples, it may seem like this is a "roundabout" way, however this is the way it should be. In order to create more reusable, quality code, you need to have this separation of concerns. An over-simplified idea follows.
It is your route's job to figure out which controller needs to be called, and to make sure it is called with the correct parameters.
It is your controller's job to read the state of the application (input), communicate with the model (if needed), send the data into the view, and return the response. There's plenty opinion on whether or not this violates the Single Responsibility Principle, but no need to go into that here.
It is the view's job to use the data passed to it to build the response. The view shouldn't care where the data came from or how it was gotten, only that it now has it and can do what it needs. Your $missionName should be able to come from a URL segment, a URL request variable, a field on a model, or any other place you can think of, but the view shouldn't know any of that.

Categories