I try to use plugin to dispatch bootstraps for different modules. However, for some reason, I can not trigger controller for each module, and the error is "EXCEPTION_NO_CONTROLLER". Any one could give some advice about it?
// Plugin Code:
class Plugin_AccessCheck extends Zend_Controller_Plugin_Abstract {
public function __construct() {
}
public function dispatchLoopStartup(Zend_Controller_Request_Abstract $request) {
if ('admin' == $request->getModuleName()) {
require_once APPLICATION_PATH .'/modules/admin/Bootstrap.php';
$moduleBootstrap = new Admin_Bootstrap();
$moduleBootstrap->bootstrap();
} else if('site' == $request->getModuleName()) {
}
}
}
// Module Bootstrap:
class Admin_Bootstrap extends Zend_Application_Module_Bootstrap{
public function Admin_Bootstrap() {
}
protected function _initAutoload() {
define("localhost", "adrian");
}
}
All module bootstraps are run on every request.
If there is some processing you wish to perform when the request is routed to single module, then register a plugin - either in application bootstrap or in module bootstrap; as noted above, they will all run - that exits early if the request is not targeted at his module.
See this post by MWOP for further discussion:
http://www.mwop.net/blog/234-Module-Bootstraps-in-Zend-Framework-Dos-and-Donts.html
Im not sure..if i unserstand your question..you can try
public function dispatchLoopStartup(Zend_Controller_Request_Abstract $request)
{
$request = Zend_Controller_Front::getInstance()->getRequest();
if ('admin' == $request->getModuleName()) {
require_once APPLICATION_PATH.'/modules/admin/Bootstrap.php';
$moduleBootstrap = new Admin_Bootstrap();
$moduleBootstrap->bootstrap();
}
else if('site' == $request->getModuleName()){
$request->setModuleName('othermodule');
$request->setControllerName('othercontroller');
$request->setActionName('otherindex');
}
}
Related
I'm building application in Phalcon PHP where I have database with access from website and API.
In normal website I would create MVC like here:
- app
-- controllers
-- models
-- views
- public
but I have problem with duplicate code for API and Web.
Sample code:
class Users extends Model {
// ...
protected $id;
protected $username;
protected $email;
// setters and getters, validation
}
class UserController extends ControllerBase {
// ...
public function loginAction() {
if ($this->request->isPost()) {
// ... get post
// check login is correct
// create session
// redirect
}
$this->view->var = $var;
}
}
class ApiController extends ControllerBase {
// ...
public function loginAction() {
if ($this->request->isPost() //or put) {
$json = $this->request->getJsonRawBody();
// ... get json
// check login is correct
// create session
}
$response->setStatusCode('2xx/4xx', 'msg');
$response->setJsonContent([
'status' => 'OK / ERROR',
'message' => '$msg / $ex->getMessage()'
]);
}
}
Now I would create class with logic for check is user data correct.
I think about class like this:
class MyClass extends ParentClass {
public function login($username, $password) {
$user = Users::findFirstByEmail($email);
if ($user->password === hash($password)) {
$successLogin = new UserSuccessLogins();
$successLogin ->setId('id');
$successLogin ->setIpAddress('ip');
$successLogin ->save();
} else {
$failedLogin = new UserFailedLogins();
$failedLogin->setId('id');
$failedLogin->setIpAddress('ip');
$failedLogin->save();
}
}
}
And now I could use it in controllers like here:
class UserController extends ControllerBase {
public function loginAction() {
if ($this->request->isPost()) {
$c = new MyClass();
if ($c->login($username, $password)) {
// redirect
}
}
$this->view->var = $var;
}
}
class ApiController extends ControllerBase {
public function loginAction() {
if ($this->request->isPost() //or put) {
$c = new MyClass();
if ($c->login($username, $password)) {
// send json OK
} else {
// send json Error
}
}
}
}
What is best way for this? I don't want logic in model class.
I have read about Plugin and Component, but I don't know how create good self commented code.
You might be looking for Phalcon Multimodule, have a look at this example. Besides "Front-End" and "Back-End" modules, you can add "API" module.
OK, I'm going to extend my project with components like here:
-app
--components
--controllers
--models
--views
-public
Now, my code may looks like below:
use Phalcon\Mvc\Model;
class Users extends Model {
// ...
protected $id;
protected $username;
protected $email;
// setters and getters, validation
}
use Phalcon\Mvc\User\Component;
class UserComponent extends Component {
// class with access to dependecy injector
public login ($email, $password) {
$user = Users::findFirstByEmail($email);
// logic with setting session in $di
}
}
class UserController extends ControllerBase {
public function loginAction() {
if ($this->request->isPost()) {
$userComponent = new UserComponent();
if ($userComponent ->login($username, $password)) {
return $this->response->redirect($this->url->getBaseUri(), false, 301);
} else {
$this->flash->error('message');
}
}
// setting view variables if not post or login filed
$this->view->var = $var;
}
}
class ApiController extends ControllerBase {
public function loginAction() {
if ($this->request->isPost()) {
$userComponent = new UserComponent();
if ($userComponent ->login($username, $password)) {
//json OK
} else {
//json Error
}
}
}
}
If no one have better proposition I'll close this topic as solved in few days.
Your suggestion is a good option, however if you want to decouple and segregate responsibilities in a better way, you can try to use a service layer like in this example https://github.com/phalcon/mvc/tree/master/multiple-service-layer-model. Where you will have:
entities ( the models generated by phalcon)
repositories ( all the operations that requires fetching, updating or persisting data)
services (where the business logic is).
Whit this the call graph can be summarised as follow:
controllers -> services -> repositories -> entities
Note that the dependencies go in a single direction, nonetheless for simple tasks you can use a repo inside the controller directly o a entity inside the service, is up to you how hard or flexible your architecture will be.
I hope It is clear regards.
In our CakePHP 3 application we found a different behaviour. We're sure that it worked well in CakePHP 2, so I suppose something changed in new version.
When user visits this url: /b2controller/myMethod, these methods run:
AppController::beforeFilter()
BController::beforeFilter()
B2Controller::beforeFilter()
B2Controller::myMethod()
B2Controller::myMethod2()
then user is redirected to this url /ccontroller/myMethod10/
But we need this:
When user visits
/b2controller/myMethod and $isOk condition is true, then redirect user to /ccontroller/myMethod10/, without running BController::beforeFilter(), B2Controller::beforeFilter(), B2Controller::myMethod() and BController::MyMethod2().
Our minimal code is like this:
class AppController {
function beforeFilter(Event $event) {
// set $isOk variable
if ($isOk == TRUE) {
return $this->redirect('/ccontroller/myMethod10/');
}
$aa=1;
$ab=2;
}
}
class BController extends AppController {
function beforeFilter(Event $event) {
parent::beforeFilter($event);
$a=1;
$b=2;
}
function myOtherMethod() {
myOtherMethod2();
}
function myOtherMethod2() {
...
...
}
}
class B2Controller extends BController {
function beforeFilter(Event $event) {
parent::beforeFilter($event);
$m1=1;
$m2=2;
}
function myMethod() {
myMethod2();
}
function myMethod2() {
...
...
}
}
class CController extends AppController {
function beforeFilter(Event $event) {
parent::beforeFilter($event);
}
function myMethod10() {
...
...
...
}
}
How can I make user to redirect to another controller action, from the beforeFilter of main class ? Note that redirect occurs. But user is redirected after calling myMethod() and myMethod2().
Also note that there is other controllers like CController that uses beforeFilter redirect behaviour.
Here are 3 methods that works:
Method 1 - Override startupProcess in your controller(s)
Override the startupProcess method of AppController:
// In your AppController
public function startupProcess() {
// Compute $isOk
if ($isOk) {
return $this->redirect('/c/myMethod10');
}
return parent::startupProcess();
}
This is a short and quite clean method, so I would go for this one if you can. If this does not fit your needs, see below.
Note: If you use this method, your components may not be initialized when you compute $isOk since the initialization is done by parent::startupProcess.
Method 2 - Send the response from AppController:
One easy but not really clean way may be to send the response from AppController::beforeFilter:
public function beforeFilter(\Cake\Event\Event $event) {
// Compute $isOk
if ($isOk) {
$this->response = $this->redirect('/c/myMethod10');
$this->response->send();
die();
}
}
Method 3 - Use Dispatcher Filters
A more "clean" way would be to use Dispatcher Filters:
In src/Routing/Filter/RedirectFilter.php:
<?php
namespace App\Routing\Filter;
use Cake\Event\Event;
use Cake\Routing\DispatcherFilter;
class RedirectFilter extends DispatcherFilter {
public function beforeDispatch(Event $event) {
// Compute $isOk
if ($isOk) {
$response = $event->data['response'];
// The code bellow mainly comes from the source of Controller.php
$response->statusCode(302);
$response->location(\Cake\Routing\Router::url('/c/myMethod10', true));
return $response;
}
}
}
In config/bootstrap.php:
DispatcherFactory::add('Redirect');
And you can remove the redirection in your AppController. This may be the cleanest way if you are able to compute $isOk from the DispatcherFilter.
Note that if you have beforeRedirect event, these will not be triggered with this method.
Edit: This was my previous answer which does not work very well if you have multiple B-like controllers.
You need to return the Response object returned by $this->redirect(). One way of achieving this is by doing the following:
class BController extends AppController {
public function beforeFilter(\Cake\Event\Event $event) {
$result = parent::beforeFilter($event);
if ($result instanceof \Cake\Network\Response) {
return $result;
}
// Your stuff
}
}
The code bellow the if is executed only if there was no redirection (parent::beforeFilter($event) did not return a Response object).
Note: I do not know how you compute isOk, but be careful of infinite redirection loop if you call $this->redirect() when calling /ccontroller/mymethod10.
This is my controller
class CarsController extends Controller {
public function actionIndex() {
echo 1; exit();
}
}
this is the module file:
<?php
class CarsModule extends CWebModule {
public $defaultController = "cars";
public function init() {
// this method is called when the module is being created
// you may place code here to customize the module or the application
// import the module-level models and components
$this->setImport(array(
'cars.models.*',
'cars.components.*',
));
}
public function beforeControllerAction($controller, $action) {
if (parent::beforeControllerAction($controller, $action)) {
// this method is called before any module controller action is performed
// you may place customized code here
return true;
}
else
return false;
}
}
my problem is if I access my project like: localhost/cars it works. If I access localhost/cars/index I am getting this message: Unable to resolve the request . If I create a new function and I access like this: localhost/cars/myfunction, still the same. I am working on Windows. Can someone help me with this ?
Typically the default url rule for modules is module/controller/actin, so for access actionIndex inside CarsController inside CarsModule, your url should be localhost/cars/cars/index, not localhost/cars/index. If you don't like the url be like localhost/cars/cars/index, you can write one url manager rule to map localhost/cars/cars/index into localhost/cars/index. Something like this:
'cars/index' => 'cars/cars/index'
Assuming I have an application using a lot of AJAX requests.
Is there a way to edit Symfony behavior and autommatically call indexAjaxAction instead of indexAction when my request is AJAX made ?
I already know that I can test if a request is Ajax with the Request::isXmlHttpRequest() method but I want it to be autommatic (i.e without testing in each controllerAction).
Does a service/bundle already makes it ?
Example :
<?php
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class FooController extends Controller
{
public function indexAction($vars)
{
$request = $this->getRequest();
if($request->isXmlHttpRequest()) {
return $this->indexAjaxAction($vars);
}
// Do Stuff
}
public function indexAjaxAction($vars){ /* Do AJAX stuff */ }
}
becomes
<?php
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class FooController extends Controller
{
public function indexAction($vars) { }
public function indexAjaxAction($vars) { }
// Other functions
}
One way would be to use a slightly modified controller resolver that would be used instead of the current controller resolver in the regular KttpKernel::handleRaw process.
Please note that I may be wrong in my thinking here and it is untested.
The controller resolver class has the id controller_resolver.class which you could overwrite with your custom one in your config using
In your app/config/config.yml...
.. config stuff ..
parameters:
controller_resolver.class: Acme\SomeBundle\Controller\ControllerResolver
And then in your new ControllerResolver...
namespace Acme\SomeBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\ControllerResolver
as BaseControllerResolver;
class ControllerResolver extends BaseControllerResolver
{
/**
* {#inheritdoc
*/
public function getArguments(Request $request, $controller)
{
if (is_array($controller) && $request->isXmlHttpRequest()) {
$action = preg_replace(
'/^(.*?)Action$/',
'$1AjaxAction',
$controller[1]
);
try {
$r = new \ReflectionMethod($controller[0], $action);
return $this->doGetArguments(
$request,
$controller,
$r->getParameters()
);
} catch( \Exception $e) {
// Do nothing
}
}
return parent::getArguments($request, $controller);
}
}
This class just extends the current controller resolver and will attempt to use the youractionAjaxAction if it exists in the controller and then falls back to the regular resolver if it gets an error (method not found);
Alternatively you could just use...
if (is_array($controller) && $request->isXmlHttpRequest()) {
$controller[1] = preg_replace(
'/^(?P<action>.*?)Action$/',
'$1AjaxAction',
$controller[1]
);
}
return parent::getArguments($request, $controller);
.. which would just update the called action and then send it through to the regular resolver with no fall back, meaning that every action that could be called using an XmlHttpRequest would require a corresponding AjaxAction.
You may want to look into FOSRestBundle for Symfony, it can be very useful if you have 1 action that can either return json data or rendered html template depending on the request method
public function init(){
$this->view->user = Zend_Auth::getInstance()->getIdentity();
$this->view->siteName = Zend_Registry::get('config')->site->name;
$this->view->menu = $this->_helper->generateMenu(Zend_Auth::getInstance()->getIdentity());
$this->view->slogan = Zend_Registry::get('config')->site->slogan;
}
This is the init file in all of my controllers across all modules, is there a place I can put this code so it executes every request irregardless of the module/controller being called?
I'd rather advise you to write a plugin by extending Zend_Controller_Plugin_Abstract, it is its purpose.
By this way, you will have no need to do anything anywhere in your controller.
Then you can use the registry to access to your data...
class My_Controller_Plugin_Acl extends Zend_Controller_Plugin_Abstract
{
protected $_auth = null;
protected $_acl = null;
public function __construct (Zend_Auth $auth, Zend_Acl $acl)
{
$this->_auth = $auth;
$this->_acl = $acl;
}
public function preDispatch(Zend_Controller_Request_Abstract $request)
{
//some code
}
}
And then in your bootstrap.php
$this->_front->registerPlugin(new My_Controller_Plugin_Layout());
http://framework.zend.com/manual/en/zend.controller.plugins.html
To share code across controllers, create an Action Helper which was designed primarily to solve the problem you have.
They can be run "on demand":
$myHelper = $this->_helper->MyHelper;
$myHelper->someFunction();
and also have a set of hooks that the dispatch process will call automatically. To use the hooks, you need to register the action helper with the broker:
$helper = new App_Controller_Action_Helper();
Zend_Controller_Action_HelperBroker::addHelper($helper);
The available hooks are:
init()
preDispatch()
postDispatch()
For more info,the manual page can be found at http://framework.zend.com/manual/en/zend.controller.actionhelpers.html and I have written a couple of articles about them: http://akrabat.com/2008/10/31/using-action-helpers-in-zend-framework/ and http://akrabat.com/2008/11/05/hooks-in-action-helpers/
You can extend Zend_Controller_Action:
public class My_Controller_Action extends Zend_Controller_Action
{
public function init()
{
$this->view->user = Zend_Auth::getInstance()->getIdentity();
$this->view->siteName = Zend_Registry::get('config')->site->name;
$this->view->menu = $this->_helper->generateMenu(Zend_Auth::getInstance()->getIdentity());
$this->view->slogan = Zend_Registry::get('config')->site->slogan;
}
}
Then you just change your controllers to extend My_Controller_Action rather than Zend_Controller_Action. Just keep in mind that if you need to add additional code to the init method of a controller, you'll have to invoke parent::init() as well:
public class FooController extends My_Controller_Action
{
public function init()
{
parent::init();
// Do something.
}
public function IndexAction()
{
// ...
}
}