is there a way in Zend to call one controller from another?
I have seen the action stack but that does not seem to work for me and i have read that alot of people think it to be EVIL!
What i am trying to acchive is as follows:
Reports Controller scans through all the modules in the system, it then checks to see if a route has been registered for that module called MODULENAME-reports-run
The controller then reuns that registered route to generate all the reports from all the modules.
The idea is that i can create modules for my application that clients can simple drag and drop into place and the system picks up on the reports.
Your controller should not do any of these things. Your controller should only accept any input from the User Interface and then decide to delegate this to the appropriate classes in your Model.
If you have a ReportController, have it accept any input and forward it to a ReportsService or something else in the Model responsible for generating Reports. It's not the controllers responsibility to generate them.
It should look something like this:
public function generateReportAction()
{
try {
$service = new Model_ReportService;
$service->setReportToGenerate($this->getRequest()->getParam('reportId'));
$this->view->report = $service->generateReport();
} catch (ReportException $e) {
// do something with $e
}
}
If your ReportService needs to generate multiple reports, change the ReportService so it knows how to do that. You could do something like
$service = new Model_ReportService;
$service->setModulesDirectory('something');
$this->view->reports = $service->generateReportsForModules();
Personally I dont think a ReportService should need to know about a Module Directory, so you will want to give the public interface of that Service some more thought. But in general, this is the way to go.
Whatever you do, dont do it in the controller. Controllers ought to be slim.
Related
Goal: understand whether this implemented order/logic of operation indeed belongs to the C part of MVC.
Situation: I have nearly finished a simple note-taking site with markdown files. No database is used except for authentication. The lack of database, however, makes it difficult to know if I am neglecting the M of MVC.
Because I did not want to have the .md extension as part of the pretty url, I am heavily relying on the PageController to settle the order of operation.
From the Controller, PageController inherits the constructed filesystem/Flysystem(fs), twig, and the "$app" that processes any of the three scenarios.
It first checks to see if a param.md exists and then whether param is a directory. If none of the above, then it's a new note. In each case, it uses a corresponding method set by the Application ($app) that processes and returns an array (title, breadcrumbs, content/directory listing, new template etc).
<?php
namespace App\Controller;
class PageController extends Controller {
public function Page($param){
$file=$param.'.md';
if ($this->fs->has($file)) {
$data=$this->app->setNote($file);
return $this->app->render('note.twig',$data)
}
elseif ($this->fs->has($param)) {
$data=$this->app->setFolder($param);
return $this->app->render('folder.twig',$data)
}
else {
$data=$this->app->setNew($param);
return $this->app->render('new.twig',$data)
}
}
}
Per "PHP the Right Way":
Controllers handle the request, process the data returned from models and load views to send in the response.
Although my code works and gets the job done, it does not appear to the be right way because the main App is doing the processing. I guess I could just move the Application.php in the Models folder, but would that make it adhere to "right way"? Should I use a Middleware before the PageController gets to work?
It may seem silly to ask about a code that just works, but my goal is to better understand/learn the current wisdom's ways when dealing with flat-files.
Regardless of whether you are 'database-less', the data is being stored / accessed in the .md files.
Access to them should be abstracted to a Model. You should create a File.find object + method, and/or a File.find_or_create. Then
$file = File.find_or_create($param);
$render_type = $file.type . '.twig';
return $this->app->render($render_type, $file.data);
Put all your if logic in the Model.
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.
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
i'm trying to implement Respect/Rest in my existing CMS.
The Problem:
I would like to create a single magic route to works like: /admin/*/save, calls the * controller...
I would like to make something like this:
$r->any('/admin/*/save/*/', function($controller, $id = null) use ($r) {
return $r->dispatchClass($controller,array($id));
});
Note that i don't know which HTTP method user is using.
Actually I "solved" this problem with something like:
$r->any('/admin/*/save/*/', function($controller, $id = null) use ($tcn) {
$r = new Router;
$r->any('/admin/*/save/*/', $tcn($controller . '_save'), array($id));
return $r->run();
});
$tcn is a named function that returns the full namespace of the controller.
I know it's not a good approach.
EDIT:
This project wants to be Open Source, but it's still being created.
We're trying to transport an old project made on functional paradigm to OOP.
We are trying to learn about OOP while making an useful project.
Actuall state of the files can be found at: https://github.com/dindigital/skeleton
Alganet: The bootstrap for admin routes can be found at: https://github.com/dindigital/skeleton/blob/master/admin_routes.php
A simple controller sample: https://github.com/dindigital/skeleton/blob/master/src/app/admin/controllers/TagController.php
https://github.com/dindigital/skeleton/blob/master/src/app/admin/controllers/TagSaveController.php
I liked the Forwards and also the Factory approach... I could not decide yet.
Tricky question! That depends a lot of why are you making these routes dynamic. Can you show us some sample structure for your controllers so I can improve the answer?
Meanwhile, two native features that can help:
Forwards
You can treat the problem as an internal forward (does not make redirects). It's normally to redirect to other static routes, but you can redirect to a new one as well:
$r->any(
'/admin/*/save/*/',
function ($controller, $id) use ($tcn, $r) {
return $r->any(
"/admin/$controller/save",
$tcn($controller . '_save'),
array($id)
);
}
);
Factory Routes
Also, Respect\Rest implements factory routes. It is experimental but stable in tests:
$r->factoryRoute(
'ANY',
'/admin/*/save/*/',
'MyAbstractController',
function ($method, array $params) use ($tcn) {
return new $tcn($params[0] . '_save');
}
);
In a MVC pattern, what's the best way to handle when a single view could have multiple actions of the same type (e.g POST)?
Say for instance in a TODO list application. You might allow a user to create multiple lists. Each list could have multiple items. So a user navigates to site.com/list/1 which shows them all the items on the 1st list (1 is a GET parameter). There are then 2 forms (POST) on this page to allow a user to:
Create a new item
Delete an existing item
Should the bootstrap create a "listcontroller", inspect the POST variables and then call the appropriate method similar to :
$lc = new ListController();
if(strtolower($request->verb) === 'post'):
if(isset($_POST['title'])) :
$data = $lc->newItem($_POST);
$load->view('newitem.php', $data);
else if(isset($_POST['delete']) && isset($_POST['id'])):
$data = $lc->deleteItem($_POST);
$load-view('deleteitem.php', $data);
endif;// End if post title
else:
//GET request here so show view for single list
endif; //
Or is it better to just do something like
$lc = new ListController();
if(isset($_POST)):
//controller handles logic about what function to call
$data = $lc->PostAction($_POST);
// $data could also potentially hold correct view name based on post
$load->view();
else:
//again just show single list
endif;
I'm just struggling how best to have a controller potentially handle multiple different actions, as there's potentially quite a few nested if/else or case statements to handle different scenarios. I know these would have to sit somewhere, but where is cleanest?
I know that there are many frameworks out there, but I'm going through the whole "want to understand best practice" behind it phase. Or is this totally the wrong way to do it? Should the controllers actually be structured differently?
To begin with, I actually really like, how you are dealing with implementation of MVC. None of that rails-like parody, where view is managed inside the controller.
Here is what I think is the root of your problem: you are still using a "dumb view" approach.
View is not supposed to be a synonym for "template". Instead it should be a full object, which has knowledge-of and ability-to deal with multiple templates. Also, in most of MVC-inspired design patterns, the view instances are able to request information from model layer.
In your code the issue can be traced back to view's factory ( the $load->view() method ), which only gets what controller sends it. Instead controller should only change the name of the view, and maybe send something that would change the state of view.
The best solution for you would be to create full-blown view implementation. Such that view itself could request data from model layer and , based on data it received, decide which template(s) to use and whether to require additional information from model layer.
I think you're somewhat on the right track with the latter approach. However, you should not hard code the calling of actions in your bootstrap. The bootstrap should interpret the URL and call the action methods dynamically through the use of a function like call_user_func_array.
Also, I would suggest that you leave the rendering of views up to the action code so the action logic is self sufficient and flexible. That would allow the action to analyse the input for correctness and render errors or views appropriately. Also, you've got the method 'deleteItem' on your controller, but that should really be the work of a model. Perhaps you should read up some more on MVC and try to work with an existing framework to understand the concepts better before you try to implement your own framework (I would suggest the Yii framework for that).
Here's an example of how I think your logic should be implemented in a good MVC framework.
class ListController extends BaseController
{
public function CreateAction($title){
if(ctype_alnum($title))
{
$list = new List();
$list->Title = $title;
if($list->insert())
{
$this->render_view('list/create_successful');
}
else
{
$this->render_view('list/create_failed');
}
}
else
{
$this->render_view('list/invalid_title');
}
}
public function DeleteAction($id){
$list = List::model()->getById($id);
if($list == null)
{
$this->render_view('list/errors/list_not_found');
}
elseif($list->delete())
{
$this->render_view('list/delete_successful');
}
else
{
$this->render_view('list/delete_failed');
}
}
}
here is a great tutorial on how to write your own MVC framework