Symfony Controller magic method? - php

I am trying to use Symfony to replicate behavior in an existing Framework (zikula). This framework is extensible using modules which are basically extended symphony bundles. The old framework had urls like so
index.php?module=foo&type=bar&func=zip
which in symfony speak roughly translates to
index.php?bundle=foo&controller=bar&method=zip
The framework has an AbstractController which has a magic method like:
public function __call($method, $args)
{
$event = new \Zikula\Core\Event\GenericEvent($this, array('method' => $method, 'args' => $args));
$this->eventManager->dispatch('controller.method_not_found', $event);
if ($event->isPropagationStopped()) {
return $event->getData();
}
}
so, if you created a url with a method that didn't exist in the bundle, you could create a listener to capture it and send a response that looks like and behaves like it came from the specified bundle. We use this to call module services that are available to all modules and provided in a separate module but look like they are served by the 'host' module.
Now I am trying to replicates this using symfony and routing.
the first problem is generating a route that doesn't technically exist. Is this possible?
The second problem is capturing the RouteNotFoundException (which I know how to do, we already have listeners for other exceptions).
The last problem is making it appear that the bundle is serving up the response when it is actually being served by an event listener (or something else). This last part is important because other content in the response needs to come from the module/bundle.
I have tried changing the current listener to a controller, and also tried adding a method to our extension of symfony's AbstractController, but haven't yet achieved what I am hoping to achieve. I'm hoping for some suggestions on new ideas or methods to try.

I gave up trying to replicate the exact behavior as it seems impossible (it is also pretty difficult to describe). So I have resorted to a normal controller with standard route, but I found a way to make it appear to belong to the original 'host' module. Thanks to Gerry, ggioffreda and DerStoffel for offering ideas.

Related

Dealing with Views in Phalcon Controllers

I am working on a newly created Phalcon project, and I don't really know how to actually use multiples views.
What is the entry point? I don't really know when each method in the controller is called, under which conditions, etc.
Where is the control flow defined? is it based in the name of the view? or is there a place where you can register them?
Phalcon is a bit different than other well-known PHP frameworks, in that not much is pre-configured or pre-built by default. It's quite loosely-coupled. So you have to decide where and how your control flow will work. This means that you will need to dig deeper in the documentation and also that there could be different way to achieve the same thing.
I'm going to walk you through a simple example and provide references, so you can understand it more.
1) You would start by defining a bootstrap file (or files) that will define the routes, or entry points, and will setup and create the application. This bootstrap file could be called by an index.php file that is the default file served by the web server. Here is an example of how such bootstrap file will define the routes or entry points (note: these are just fragments and do not represent all the things that a bootstrap file should do):
use Phalcon\Di\FactoryDefault;
// initializes the dependency injector of Phalcon framework
$injector = new FactoryDefault();
// defines the routes
$injector->setShared('router', function () {
return require_once('some/path/routes.php');
});
Then it the routes.php file:
use Phalcon\Mvc\Router;
use Phalcon\Mvc\Router\Group as RouterGroup;
// instantiates the router
$router = new Router(false);
// defines routes for the 'users' controller
$user_routes = new RouterGroup(['controller' => 'users']);
$user_routes->setPrefix('/users');
$user_routes->addGet('/show/{id:[0-9]{1,9}}', ['action' => 'show']);
$router->mount($user_routes);
return $router;
Im defining routes in an alternate way, by defining routes groups. I find it to be more easy to organize routes by resource or controller.
2) When you enter the url example.com/users/show/123, the routes above will match this to the controller users and action show. This is specified by the chunks of code ['controller' => 'users'], setPrefix('/users') and '/show/{id:[0-9]{1,9}}', ['action' => 'show']
3) So now you create the controller. You create a file in, let's say, controllers/UsersController.php. And then you create its action; note the name that you used in the route (show) and the suffix of Action:
public function showAction(int $id) {
// ... do all you need to do...
// fetch data
$user = UserModel::findFirst(blah blah);
// pass data to view
$this->view->setVar('user', $user);
// Phalcon automatically calls the view; from the manual:
/*
Phalcon automatically passes the execution to the view component as soon as a particular
controller has completed its cycle. The view component will look in the views folder for
a folder named as the same name of the last controller executed and then for a file named
as the last action executed.
*/
// but in case you would need to specify a different one
$this->view->render('users', 'another_view');
}
There is much more stuff related to views; consult the manual.
Note that you will need to register such controller in the bootstrap file like (Im also including examples on how to register other things):
use Phalcon\Loader;
// registers namespaces and other classes
$loader = new Loader();
$loader->registerNamespaces([
'MyNameSpace\Controllers' => 'path/controllers/',
'MyNameSpace\Models' => 'path/models/',
'MyNameSpace\Views' => 'path/views/'
]);
$loader->register();
4) You will also need to register a few things for the views. In the bootstrap file
use Phalcon\Mvc\View;
$injector->setShared('view', function () {
$view = new View();
$view->setViewsDir('path/views/');
return $view;
});
And this, together with other things you will need to do, particularly in the bootstrap process, will get you started in sending requests to the controller and action/view defined in the routes.
Those were basic examples. There is much more that you will need to learn, because I only gave you a few pieces to get you started. So here are some links that can explain more. Remember, there are several different ways to achieve the same thing in Phalcon.
Bootstrapping:
https://docs.phalconphp.com/en/3.2/di
https://docs.phalconphp.com/en/3.2/loader
https://docs.phalconphp.com/en/3.2/dispatcher
Routing: https://docs.phalconphp.com/en/3.2/routing
Controllers: https://docs.phalconphp.com/en/3.2/controllers
More on Views (from registering to passing data to them, to templating and more): https://docs.phalconphp.com/en/3.2/views
And a simple tutorial to teach you some basic things: https://docs.phalconphp.com/en/3.2/tutorial-rest
The application begins with the routing stage. From there you grab the controller and action from the router, and feed it to the dispatcher. You set the view then call the execute the dispatcher so it access your controller's action. From there you create a new response object and set its contents equal to the view requests, and finally send the response to the client's browser -- both the content and the headers. It's a good idea to do this through Phalcon rather than echoing directly or using PHP's header(), so it's only done at the moment you call $response->send(); This is best practice because it allows you to create tests, such as in phpunit, so you can test for the existence of headers, or content, while moving off to the next response and header without actually sending anything so you can test stuff. Same idea with exit; in code, is best to avoid so you can write tests and move on to the next test without your tests aborting on the first test due to the existence of exit.
As far as how the Phalcon application works, and in what steps, it's much easier to follow the flow by looking at manual bootstrapping:
https://docs.phalconphp.com/en/3.2/application#manual-bootstrapping
At the heart of Phalcon is the DI, the Dependency Injection container. This allows you to create services, and store them on the DI so services can access each other. You can create your own services and store them under your own name on the DI, there's nothing special about the names used. However depending on the areas of Phalcon you used, certain services on the DI are assumed like "db" for interacting with your database. Note services can be set as either shared or not shared on the DI. Shared means it implements singleton and keeps the object alive for all calls afterwards. If you use getShared, it does a similar thing even if it wasn't initially a shared service. The getShared method is considered bad practice and the Phalcon team is talking about removing the method in future Phalcon versions. Please rely on setShared instead.
Regarding multiple views, you can start with $this->view->disable(); from within the controller. This allows you to disable a view so you don't get any content generated to begin with from within a controller so you can follow how views work from within controllers.
Phalcon assumes every controller has a matching view under /someController/someView followed by whatever extension you registered on the view, which defaults to .volt but can also be set to use .phtml or .php.
These two correspond to:
Phalcon\Mvc\View\Engine\Php and Phalcon\Mvc\View\Engine\Volt
Note that you DON'T specify the extension when looking for a template to render, Phalcon adds this for you
Phalcon also uses a root view template index.volt, if it exists, for all interactions with the view so you can use things like the same doctype for all responses, making your life easier.
Phalcon also offers you partials, so from within a view you can render a partial like breadcrumbs, or a header or footer which you'd otherwise be copy-pasting into each template. This allows you to manage all pages from the same template so you're not repeating yourself.
As far as which view class you use within Phalcon, there's two main choices:
Phalcon\Mvc\View and Phalcon\Mvc\View\Simple
While similar, Phalcon\Mvc\View gives you a multiple level hierarchy as described before with a main template, and a controller-action based template as well as some other fancy features. As far as Phalcon\Mvc\View\Simple, it's much more lightweight and is a single level.
You should be familiar with hierarchical rendering:
https://docs.phalconphp.com/en/3.2/views#hierarchical-rendering
The idea is with Phalcon\Mvc\View that you have a Main Layout (if this template exists) usually stored in /views/index.volt, which is used on every page so you can toss in your doctypes, the title (which you would set with a variable the view passed in), etc. You'd have a Controller Layout, which would be stored under /views/layouts.myController.volt and used for every action within a controller (if this template exists), finally you'd have the Action Layout which is used for the specific action of the controller in /views/myController/myAction.volt.
There are all types of ways you can break from Phalcon's default behavior. You can do the earlier stated $this->view->disable(); so you can do everything manually yourself so Phalcon doesn't assume anything about the view template. You can also use ->pick to pick which template to use if it's going to be different than the controller and action it's ran in.
You can also return a response object from within a controller and Phalcon will not try to render the templates and use the response object instead.
For example you might want to do:
return $this->response->redirect('index/index');
This would redirect the user's browser to said page. You could also do a forward instead which would be used internally within Phalcon to access a different controller and/or action.
You can config the directory the views are stored with setViewsDir. You can also do this from within the controller itself, or even within the view as late as you want, if you have some exceptions due to a goofy directory structure.
You can do things like use $this->view->setTemplateBefore('common') or $this->view->setTemplateAfter('common'); so you can have intermediate templates.
At the heart of the view hierarchy is <?php echo $this->getContent(); ?> or {{ content() }} if you're using Volt. Even if you're using Volt, it gets parsed by Phalcon and generates the PHP version with $this->getContent(), storing it in your /cache/ directory, before it is executed.
The idea with "template before" is that it's optional if you need another layer of hierarchy between your main template and your controller template. Same idea with "template after" etc. I would advise against using template before and after as they are confusing and partials are better suited for the task.
It all depends on how you want to organize your application structure.
Note you can also swap between your main template to another main template if you need to swap anything major. You could also just toss in an "if" statement into your main template to decide what to do based on some condition, etc.
With all that said, you should be able to read the documentation and make better sense of how to utilize it:
https://docs.phalconphp.com/en/3.2/api/Phalcon_Mvc_View

Creating a PHP Web app from scratch using Slim as Architecture

This is my long quest to develop web apps using core PHP and follow the best possible practices and not using a framework. I have achieved many things by structuring my project a better way. However ...getting a clean URL is often a problem for large apps.
Till now...I have used the Slim Framework only for creating RESTFUL services outside my web apps.
I am using Slim Framework to create APIs for a PHP project. Now, I have an install of Slim up and running fine. I have my routes talking to the database and doing what they're supposed to do, generally. My question has to do with modularizing the code. At the moment, all my routes are defined in my index.php file in the root directory. I would very much like to separate these out, say into a /controllers folder.
As I liked the way Slim makes pretty good URLs...I am wondering if it is possible to use Slim as my app architecture ...and let all my pages or APIs be accessible through the Slim index.php.
Yes pretty easily here are the steps I have taken on a recent project.
First lets say your have a HomeActionController
class HomeActionController {
//The below line I have moved into an abstract Controller class
public $view = null;
//This is using Slim Views PhpRenderer
//This allows for a controller to render views can be whatever you need
//I did not like the idea of passing the whole DC it seemed overkill
//The below method I have moved into an abstract Controller class
public function __construct(\Slim\Views\PhpRenderer $view = null){
if($view != null){
$this->view = $view;
}
}
//View could be any action method you want to call it.
public function view(Request $request, Response $response, array $args){
$data['user'] = "John Doe";
return $this->view->render($response, 'templates/home.php', $data);
}
}
Now you need to be able to call an instance of this controller from a route so you need to add the controllers you have to the DC
Where ever you are creating your instance of slim you will need to get the DC and add an instance of your controller:
$app = new \Slim\App($config['slim']);
// Get Dependency Container for Slim
$container = $app->getContainer();
$container['HomeActionController'] = new Controller\HomeActionController($container['view']); //Notice passing the view
As a note the above instantiations could have been a closures but I did not see the point at the time or making them. Also, there are ways to lazy load that I have not explored yet see here for more information.
Now the last thing you need to do is be able to call these on the routes which is not a huge challenge.
$app->get('/home', 'HomeActionController:view');
Granted you cannot have an action with parameters but I have not had an issue just passing them along in the request and then getting them from there.
If you want to create a app with no framework, then i would recommend looking through this small github repo:
https://github.com/PatrickLouys/no-framework-tutorial
It goes through with you settings everything up in terms of routing, plus would make everything go through the index.php in a public folder like your asking.

Implementating Dependency Injection for better routing in mvc or mvvm in php using DICE

I've trying to expand my knowledge on separation of concerns in php. I've been practicing for over a year I think and trying to write my own mvc framework for practice. Now I am stuck again in routing and on how to initiate MVC triad.
I have this uri that I want to map so I can identify which controller and which view to use
$uri = filter_var(rtrim(filter_input(INPUT_GET, 'url', FILTER_SANITIZE_STRING), '/'), FILTER_SANITIZE_URL);
lets say this piece of code resides in my bootstrap.php file that acts as the entry point.
While reading Tom Butler's Blog I realize many things, like views should have access to models, but not quite, using a viewmodel is better, or just a model.
I've come across his IOC or his Dependency Injection Container and fell interested on trying it.
What lacks in that article is the dispatching part, which I am very interested to learn, I've tried a couple things to make it work but with no avail.
I wanted to implement this because I want a single call of controller can have shared dependency across its views, something like
$route = $router->getRoutes(); // maybe something that return a Route object with Controller, and View object that has already shared dependecies.
I do not know if my understanding on the above paragraphs where correct and if really can be of use on my routing. Correct me if I am wrong.
The real question is how would the dispatcher looks like? and if I were to use the convention over configuration stuff of mr. tom, should I declare the routes individually in my bootstrap? Like these
$dice->addRule('$route_user/edit', $rule);
$dice->addRule('$route_user/delete', $rule);
...
I wonder if I could just do:
$controller->method($params)
After I have settled on what view and controller I needed.
I'm not sure that this answer will be what you want, but if I where you I'll do it this way, it will be more restFull, and more "convention over configuration":
Use http verbs for edit , delete...
Use a standard way to handle your urls: "article/", "article/2"
Where article is your controller name AND your view name
Use a simple array tree for simple routes:
$bootstrap=[
...
"routes"=>[
"myFirstCategory"=>[
"art"=>"article",
...
],
"mySecondCategory"=>[
...
This way, myFirstCategory/art can be redirected to article controller and view, with a recursive function that handle the routes tree
In this tree you can use a callback rule for complex rules (that callback have to be handeled by your recursive function for the routes tree:
... "art"=>function($myContainer){...return ['view'=>$view,'controller'=>$controller,'id'=>$id...];}...
It's just some ideas to make it more easy to use...

RESTful Server Design in Yii

Hi i have been trawling the net in search of as much info i could get my mits on about creating a restful server in the Yii framework.
All the examples i have found so far have dealt with writing a restful application that deals with one model (IE Topic Or Posts)
The crux of my question is this, How should i deal with writing a restful server that deals with alot of Model types, ie client, brand, project, task?
Each Model object will need CRUD operations to be implemented so for example
To create a new brand the system requires a Client Id as will the other CRUD operations.
Should each Model Object have its own RESTFUl server or should there be come kind of controller that routes the RESTFUL requests to the corresponding Rest controller for the model object?
Should there be one Server that dynamically decides what model to work with like a switch (not keen on this idea personally)
Any kind of advice of how to achieve this kind of rest architecture would be really great
I think its also worth noting that the server i am building will be working with sproutcore on the front end and will only be returning JSON so any kind of format detection will not be required.
I'm developing RESTful application in Yii. So far everything is ok.
Models. I have 2 kind of models:
Managed via REST interface (i.e. created/deleted and so on). I called them resources and put into separate directory, but technically they're models extending CActiveRecord.
Internal models used by application but not exposed to the world. Also, abstract model classes from which other models or resources are extended.
Resource instance can be constructed from XML representation sent by REST client and can be converted to XML representation. You could define a base class AbstractResource which will deal with this and override construction/conversion methods in child resources if necessary.
There is one controller for one resource. Controllers usually have four CRUD actions and possibly List action. The latter is used for search operations. It's a good idea to use class-based actions instead of inline actions, so you can reuse your actions in different controllers. For example, all my controllers use ListAction for searching.
I have base controller which sends everything as application/xml. Also it deals with HTTP status codes and handles all errors.
All XML input coming from clients is validated using Relax NG schemes. You'll need json validator.
TDD rocks! I've extensively tested nearly everything. It helps a lot when doing refactoring. Use TDD. Write unit-tests for all your models, resources and application components. Also, writing functional tests are easier, because you don't have HTML/CSS/etc. You just have to send HTTP request and check returned headers, code and content. I used php_curl extension and it works great. Since you will probably do PUT, DELETE and other exotic requests you have to manually compose each HTTP request. In my case, each request also needed to be signed (this involves calculating checksums, hashes and so on), so it's nearly impossible to test my application manually.
I also recommend this excellent book: RESTful Web Services.
Run through this tutorial. Just duplicate the controller actions. Edit the second set of actions to interact with a new model. For example, the tutorial calls for the following actions to be created in the controller:
public function actionList()
{
}
public function actionView()
{
}
public function actionCreate()
{
}
public function actionUpdate()
{
}
public function actionDelete()
{
}
You would create alternate actions for your second model. Something like:
public function actionListB()
{
}
public function actionViewB()
{
}
public function actionCreateB()
{
}
public function actionUpdateB()
{
}
public function actionDeleteB()
{
}
If you need more information or clarification please leave a comment. Good luck.
I am going through a similar exercise here with my project.
I am trying to decide the best way to make it all work in an elegant, scalable way.
At the moment that's what I have:
/controllers/apiController
/controllers/api/v1/resourceaController <extends apiController>
/controllers/api/v1/resourcebController <extends apiController>
/controllers/api/v1/resourcecController <extends apiController>
The solution I am building has the URL rules redirecting users straight to the appropriate CRUD function in apiController based on the method used:
// REST API routes
array('api/list', 'pattern' => 'api/v<version:\d+>/<resource:\w+>', 'verb'=>'GET'),
array('api/create', 'pattern' => 'api/v<version:\d+>/<resource:\w+>', 'verb'=>'POST'),
array('api/view', 'pattern' => 'api/v<version:\d+>/<resource:\w+>/<resource_id:\d+>', 'verb'=>'GET'),
array('api/update', 'pattern' => 'api/v<version:\d+>/<resource:\w+>/<resource_id:\d+>', 'verb'=>'PUT'),
array('api/delete', 'pattern' => 'api/v<version:\d+>/<resource:\w+>/<resource_id:\d+>', 'verb'=>'DELETE'),
array('api/list', 'pattern' => 'api/v<version:\d+>/<resource:\w+>/<resource_id:\d+>/<association:\w+>', 'verb'=>'GET'),
array('api/create', 'pattern' => 'api/v<version:\d+>/<resource:\w+>/<resource_id:\d+>/<association:\w+>', 'verb'=>'POST'),
array('api/view', 'pattern' => 'api/v<version:\d+>/<resource:\w+>/<resource_id:\d+>/<association:\w+>/<association_id:\d+>', 'verb'=>'GET'),
array('api/update', 'pattern' => 'api/v<version:\d+>/<resource:\w+>/<resource_id:\d+>/<association:\w+>/<association_id:\d+>', 'verb'=>'PUT'),
array('api/delete', 'pattern' => 'api/v<version:\d+>/<resource:\w+>/<resource_id:\d+>/<association:\w+>/<association_id:\d+>', 'verb'=>'DELETE'),
The function that receives the call will try to instantiate the resource controller based on $_GET['resource'] and call the function stored in $_GET['association'].
I think this makes it scale fairly well, as we can have new versions of the API coming out, and all we have to do it to create something like /api/v2, /api/v2 and put all the required resource controllers in there.

Create very simple Named Route in Zend Framework, using its MVC

I've just put together a very basic site using the Zend Framework and its MVC. (Actually, I'm not even using models at the moment, it's all just Controllers/Views for static info so far).
When I started toying with the Forms I realized that in the examples for Zend_Form they use something like this this set the form's action:
$form->setAction('/user/login')
Which contains the URL. I understand that Zend Framework has Routes and that they can be named, but I can't seem to grasp from the manual how to create a simple route for certain Controller/Actions so that I could do something like this:
$form->setAction($named_route)
// or
$form->setAction('named_route')
I hope my question is clear. I wasn't able to locate any duplicate questions, but if you spot one and point it out I won't mind.
Links to resources are as good as examples, so don't waste your time if you know of a decent blog post somewhere. Thanks!
References:
http://framework.zend.com/manual/en/zend.controller.router.html#zend.controller.router.routes.standard - Look for "12.5.7.1. Zend_Controller_Router_Route" for a clearer explanation.
This is not the best way to do it, but I have a working example. I welcome corrections.
After seeing Shorten Zend Framework Route Definitions, I agree that named Routes should go in their own config (I use Django, and named Views/URLs are generally separated) - but here I'm just going to define Routes in my Bootstrap.
So, in Bootstrap.php, inside the Bootstrap Class of course, I've created an function that will be automatically run, like so:
public function _initRoutes()
{
$frontController = Zend_Controller_Front::getInstance();
$route = new Zend_Controller_Router_Route(
'login/', // The URL, after the baseUrl, with no params.
array(
'controller' => 'login', // The controller to point to.
'action' => 'index' // The action to point to, in said Controller.
)
);
$frontController->getRouter()->addRoute('loginpage', $route);
}
In the above example, "loginpage" will be the Name of the "Named Route".
So, inside my LoginController, (in a function that builds the form) instead of doing
$form->setAction('/blah/login')
I retrieve the URL of the named Route and pass that in, like so:
$form_action_url = $this->view->Url(array(), 'loginpage', true);
// -- SNIP --
$form->setAction($form_action_url) // ...
This may be pointless and wrong, but it seems to work at the moment.
My reason for wanting a named URL, when Zend Framework handles URLs as /Controller/View(Action)/ automatically is because I'm anal about that kind of thing. I've been using Django for awhile, where the URLs are predefined, and I like it that way.
The Zend Framework MVC urls working out of the box is nice, tho.
Feel free to add notes and corrections to how this should work!

Categories