Routing issues with subcontroller structure in Phalcon - php

I've implemented a router, securityplugin (for ACL) and notfoundplugin.
I want my site to be set up with a subcontroller structure: Link
The website is set up in the following main parts:
Index
Admin
Tickets
Account
The issues I have:
When calling: domain/admin/actionofadmin
Returns custom 404: "Page doesn't exist" instead of the normal action result
When calling: domain/admin/actionofadmin. (<-- mind the dot at the end)
Returns index action of index controller instead of the normal action result
Why does the router return theses results? How can they be fixed?
Extra questions:
How does Phalcon know where to find a view and link it to the correct controller? Example: A dashboardController resides in a folder "admin" inside the folder "controllers".
How does Phalcon know that in the SecurityPlugin ACL it needs to search for the correct controller while it doesn't get a namespace? Example: When I want to allow controller Tickets of namespace app\controllers\admin to be only viewed by admin users.
Extra information:
Phalcon 3.0.3
PHP 5.6
Let me know if you need any more information / files or if I should post it somewhere else for ease of reading.
Files:
/app/controllers/AdminController.php
<?php
namespace Ontrack\Controllers;
class AdminController extends ControllerBase
{
public function indexAction(){
}
public function testAction(){
echo "test";
}
}
/app/config/services.php Excerpt
//This makes sure the routes are correctly handled for authorized/unauthorized
/**
* MVC dispatcher
*/
$di->set("dispatcher", function () use ($di) {
// Create an events manager
$eventsManager = $di->getShared('eventsManager');
/**
*Check if the user is allowed to access certain action using the SecurityPlugin
*Listen for events produced in the dispatcher using the Security plugin
*/
$eventsManager->attach("dispatch:beforeDispatch", new SecurityPlugin());
// Handle exceptions and not-found exceptions using NotFoundPlugin
$eventsManager->attach("dispatch:beforeException", new NotFoundPlugin());
$dispatcher = new Dispatcher();
$dispatcher->setDefaultNamespace('Ontrack\Controllers');
// Assign the events manager to the dispatcher
$dispatcher->setEventsManager($eventsManager);
return $dispatcher;
}
);
/app/config/loader.php
<?php
$loader = new \Phalcon\Loader();
/**
* We're a registering a set of directories taken from the configuration file
*/
$loader->registerDirs(
[
$config->application->controllersDir,
$config->application->modelsDir,
$config->application->pluginsDir
]
)->register();
$loader->registerNamespaces(
[
'Ontrack\Controllers' => APP_PATH . '/controllers/',
'Ontrack\Controllers\Admin' => APP_PATH . '/controllers/admin',
'Ontrack\Models' => APP_PATH . '/models/'
]
)->register();
/app/config/routes.php
<?php
$router = new Phalcon\Mvc\Router(false);
$router->setDefaults(
[
"controller" => "index",
"action" => "index",
]
);
$router->add('/:controller/:action/:params', [
'namespace' => 'Ontrack\Controllers',
'controller' => 1,
'action' => 2,
'params' => 3,
]);
$router->add('/:controller/:action', [
'namespace' => 'Ontrack\Controllers',
'controller' => 1,
'action' => 2,
]);
$router->add('/:controller', [
'namespace' => 'Ontrack\Controllers',
'controller' => 1,
]);
$router->add('/admin/:controller/:action/:params', [
'namespace' => 'Ontrack\Controllers\Admin',
'controller' => 1,
'action' => 2,
'params' => 3,
]);
$router->add('/admin/:controller/:action', [
'namespace' => 'Ontrack\Controllers\Admin',
'controller' => 1,
'action' => 2,
]);
$router->add('/admin/:controller', [
'namespace' => 'Ontrack\Controllers\Admin',
'controller' => 1,
]);
$router->removeExtraSlashes(true);
return $router;
/app/plugins/SecurityPlugin.php
<?php
use Phalcon\Acl;
use Phalcon\Acl\Role;
use Phalcon\Acl\Adapter\Memory as AclList;
use Phalcon\Acl\Resource;
use Phalcon\Events\Event;
use Phalcon\Mvc\User\Plugin;
use Phalcon\Mvc\Dispatcher;
class SecurityPlugin extends Plugin
{
/**
* Returns an existing or new access control list
*
* #returns AclList
*/
public function getAcl()
{
if (!isset($this->persistent->acl)) {
$acl = new AclList();
$acl->setDefaultAction(Acl::DENY);
// Register roles
$roles = [
'admins' => new Role(
'admins',
'Website administrators'
),
'users' => new Role(
'users',
'Member privileges, granted after sign in.'
),
'guests' => new Role(
'guests',
'Anyone browsing the site who is not signed in is considered to be a "Guest".'
)
];
foreach ($roles as $role) {
$acl->addRole($role);
}
//Private area resources
$privateResources = array(
'account' => array('*')
);
$privateResourcesAdmin = array(
'admin' => array('*'),
'tickets' => array('*')
);
//Public area resources
$publicResources = array(
'index' => array('*'),
'register' => array('*'),
'errors' => array('show401', 'show404', 'show500'),
'register' => array('*'),
'login' => array('*'),
'logout' => array('*')
);
foreach ($privateResources as $resource => $actions) {
$acl->addResource(new Resource($resource), $actions);
}
foreach ($privateResourcesAdmin as $resource => $actions) {
$acl->addResource(new Resource($resource), $actions);
}
foreach ($publicResources as $resource => $actions) {
$acl->addResource(new Resource($resource), $actions);
}
//Grant access to public areas to users, admins and guests
foreach ($roles as $role) {
foreach ($publicResources as $resource => $actions) {
foreach ($actions as $action){
$acl->allow($role->getName(), $resource, $action);
}
}
}
//Grant access to private area to role Users
foreach ($privateResources as $resource => $actions) {
foreach ($actions as $action){
$acl->allow('users', $resource, $action);
}
}
foreach ($privateResourcesAdmin as $resource => $actions) {
foreach ($actions as $action){
$acl->allow('admins', $resource, $action);
}
}
//The acl is stored in session, APC would be useful here too
$this->persistent->acl = $acl;
}
return $this->persistent->acl;
}
/**
* This action is executed before execute any action in the application
*
* #param Event $event
* #param Dispatcher $dispatcher
* #return bool
*/
public function beforeDispatch(Event $event, Dispatcher $dispatcher){
$auth = $this->session->has('auth');
if (!$auth){
$role = 'guests';
} else {
$authSession = $this->session->get("auth");
if($authSession['account_type'] == 99){
$role = 'admins';
} else {
$role = 'users';
}
}
$controller = $dispatcher->getControllerName();
$action = $dispatcher->getActionName();
$acl = $this->getAcl();
if (!$acl->isResource($controller)) {
$dispatcher->forward([
'controller' => 'errors',
'action' => 'show404'
]);
return false;
}
$allowed = $acl->isAllowed($role, $controller, $action);
if (!$allowed) {
if($controller === 'admin'){
$dispatcher->forward(array(
'controller' => 'errors',
'action' => 'show404'
));
} else {
$dispatcher->forward(array(
'controller' => 'errors',
'action' => 'show401'
));
}
return false;
}
}
}
/app/plugins/NotFoundPlugin.php
<?php
use Phalcon\Events\Event;
use Phalcon\Mvc\User\Plugin;
use Phalcon\Dispatcher;
use Phalcon\Mvc\Dispatcher\Exception as DispatcherException;
use Phalcon\Mvc\Dispatcher as MvcDispatcher;
/**
* NotFoundPlugin
*
* Handles not-found controller/actions
*/
class NotFoundPlugin extends Plugin
{
/**
* This action is executed before execute any action in the application
*
* #param Event $event
* #param MvcDispatcher $dispatcher
* #param Exception $exception
* #return boolean
*/
public function beforeException(Event $event, MvcDispatcher $dispatcher, Exception $exception)
{
error_log($exception->getMessage() . PHP_EOL . $exception->getTraceAsString());
if ($exception instanceof DispatcherException) {
switch ($exception->getCode()) {
case Dispatcher::EXCEPTION_HANDLER_NOT_FOUND:
case Dispatcher::EXCEPTION_ACTION_NOT_FOUND:
$dispatcher->forward(array(
'controller' => 'errors',
'action' => 'show404'
));
return false;
}
}
$dispatcher->forward(array(
'controller' => 'errors',
'action' => 'show500'
));
return false;
}
}

When calling: domain/admin/actionofadmin(as i understand domain is for example www.google.pl) phalcon is expecting ActionofadminController according to your routes. Are you sure there is such controller? Don't sure why with with dot it's hitting index controller and index action though.
How does Phalcon know where to find a view and link it to the correct controller? Example: A dashboardController resides in a folder "admin" inside the folder "controllers".
It's getting this info from dispatcher. Mvc application if you don't render view your self is implicit automatically rendering. Check this source: https://github.com/phalcon/cphalcon/blob/master/phalcon/mvc/application.zep#L348 And other view classes.
About Acl Plugin - it doesn't check for namespace at all. So if you have two controllers with same name but other namespace this wil cause obviously a problem. You just need to change security plugin to your needs.

Related

Costum ReST post route function view not found cakephp 3

i try to build a custom POST route for ReST API in cakephp 3, but when i want to connect to the url i got result:
The view for CalculatedpricesController::getCosts() was not found.
My url to connect to ReST like this :
http://localhost/test/api/calculatedprices/getCosts
here's the route code:
Router::scope('/', function (RouteBuilder $routes) {
Router::prefix('api', function ($routes) {
$routes->extensions(['json', 'xml']);
$routes->resources('Calculatedprices', [
'map' => [
'getCosts' => [
'action' => 'getCosts',
'method' => 'POST'
]
]
]);
});
$routes->fallbacks(DashedRoute::class);
});
here's the controller code:
namespace App\Controller\Api;
use App\Controller\Api\AppController;
/**
* Calculatedprices Controller
*
* #property \App\Model\Table\CalculatedpricesTable $Calculatedprices
*/
class CalculatedpricesController extends AppController
{
public function getCosts(){
$originIdCity = $this->request->query('originCity');
$originIdSub = $this->request->query('originSub');
$courierId = $this->request->query('courierId');
$serviceId = $this->request->query('serviceId');
$conditions = array('origin_city_id' => $originIdCity,
'courier_id' => $courierId,
'service_id' => $serviceId
);
if($originIdSub == ''){
$condition = 'origin_subdistrict_id IS NULL';
array_push($conditions,$condition);
} else{
$conditions['origin_subdistrict_id'] = $originIdSub;
}
$calculatedprices = $this->Calculatedprices->find('all', array(
'conditions' => $conditions
));
$this->set([
'calculatedprices' => $calculatedprices,
'_serialize' => ['calculatedprices']
]);
}
}

Route parameter only works for one code Zend Framework 2

I'm trying to have a verification process after registration (by a randomly generated verification code), but after I verify one code, it will not verify another one even though I am using the code that is stored in the database upon registration. For instance:
verify/c42557235936ed755d3305e2f7305aa3
...works fine, but when I try and use another code (like /verify/3bc056ff48fec352702652cfa4850ac4), it generates the default layout for the application and does nothing. I don't know what is causing it.
Here is the code I have for this:
VerifyController -
namespace Application\Controller;
use Zend\Mvc\Controller\AbstractActionController;
class VerifyController extends AbstractActionController
{
public $verify;
public function indexAction()
{
$code = $this->params()->fromRoute('code');
if ($this->getVerifyInstance()->authenticateCode($code) !== false) {
$this->flashMessenger()->addSuccessMessage("Verification Successful, you can now login.");
return $this->redirect()->toRoute('verify', array('action' => 'success'));
} else {
$this->flashMessenger()->addErrorMessage("Oops! Something went wrong while attempting to verify your account, please try again.");
return $this->redirect()->toRoute('verify', array('action' => 'failure'));
}
}
public function successAction()
{
}
public function failureAction()
{
}
public function getVerifyInstance()
{
if (!$this->verify) {
$sm = $this->getServiceLocator();
$this->verify = $sm->get('Application\Model\VerifyModel');
}
return $this->verify;
}
}
VerifyModel -
namespace Application\Model;
use Zend\Db\TableGateway\TableGateway;
use Zend\Db\Sql\Sql;
use Zend\Db\Sql\Insert;
use Zend\Db\Adapter\Adapter;
class VerifyModel
{
/**
* #var TableGateway
*/
protected $table_gateway;
/**
* #var mixed
*/
protected $code;
/**
* Constructor method for VerifyModel class
* #param TableGateway $gateway
*/
public function __construct(TableGateway $gateway)
{
// check if $gateway was passed an instance of TableGateway
// if so, assign $this->table_gateway the value of $gateway
// if not, make it null
$gateway instanceof TableGateway ? $this->table_gateway = $gateway : $this->table_gateway = null;
}
public function authenticateCode($code)
{
// authenticate the verification code in the url against the one in the pending_users table
$this->code = !empty($code) ? $code : null;
$select = $this->table_gateway->select(array('pending_code' => $this->code));
$row = $select->current();
if (!$row) {
throw new \RuntimeException(sprintf('Invalid registration code %s', $this->code));
} else {
// verification code was found
// proceed to remove the user from the pending_users table
// and insert into the members table
$data = array(
'username' => $row['username'],
'password' => $row['password'],
);
$sql = new Sql($this->table_gateway->getAdapter());
$adapter = $this->table_gateway->getAdapter();
$insert = new Insert('members');
$insert->columns(array(
'username',
'password'
))->values(array(
'username' => $data['username'],
'password' => $data['password'],
));
$execute = $adapter->query(
$sql->buildSqlString($insert),
Adapter::QUERY_MODE_EXECUTE
);
if (count($execute) > 0) {
// remove the entry now
$delete = $this->table_gateway->delete(array('pending_code' => $this->code));
if ($delete > 0) {
return true;
}
}
}
}
}
the route:
'verify' => array(
'type' => 'Segment',
'options' => array(
'route' => 'verify/:code',
'constraints' => array(
'code' => '[a-zA-Z][a-zA-Z0-9_-]*',
),
'defaults' => array(
'controller' => 'Application\Controller\Verify',
'action' => 'index',
),
),
),
and the layout configurer in Module.php:
public function init(ModuleManager $manager)
{
$events = $manager->getEventManager();
$shared_events = $events->getSharedManager();
$shared_events->attach(__NAMESPACE__, 'dispatch', function ($e) {
$controller = $e->getTarget();
if (get_class($controller) == 'Application\Controller\SetupController') {
$controller->layout('layout/setup');
} else if (get_class($controller) == 'Application\Controller\MemberLoginController' || get_class($controller) == 'Application\Controller\AdminLoginController') {
$controller->layout('layout/login');
} else if (get_class($controller) == 'Application\Controller\RegisterController') {
$controller->layout('layout/register');
} else if (get_class($controller) == 'Application\Controller\VerifyController') {
$controller->layout('layout/verify');
}
}, 100);
}
Your route is defined
'options' => array(
'route' => 'verify/:code',
'constraints' => array(
'code' => '[a-zA-Z][a-zA-Z0-9_-]*',
),
So, it should start with a letter (upper or lower case), and be followed by any (even none) number of characters (letters, numbers, underscores, and dashes).
So, valid routes:
verify/c42557235936ed755d3305e2f7305aa3 (the one you where trying)
verify/abcde
verify/N123-123
verify/Z
verify/X-1
etc.
Any of those should work. But the other code you provide in your question:
/verify/3bc056ff48fec352702652cfa4850ac4
starts with a number, so it wont be caught by your router. You need to either change how you generate your codes so they match your route, or change your route so it matches your codes. E.g.:
'options' => array(
'route' => 'verify/:code',
'constraints' => array(
'code' => '[a-zA-Z0-9][a-zA-Z0-9_-]{28,32}',
),

Phalcon - Module exception if does not exist

I have set the Route:
$router->add('/:module/:controller/:action/:params', [
'module' => 1,
'controller' => 2,
'action' => 3,
'params' => 4
]);
When I enter URL to the browser, for example: auth/login/index and module under this URL does not exist, so it throws an exception:
Phalcon\Mvc\Application\Exception: Module 'auth' isn't registered in the application container
How can I catch this exception?
SOLUTION:
$router->add('/:module/:controller/:action/:params', [
'module' => 1,
'controller' => 2,
'action' => 3,
'params' => 4
])->beforeMatch(function($uri) use ($application) {
$modules = $application->getModules();
$moduleName = array_filter(explode('/', $uri))[1];
if(!isset($modules[$moduleName]))
return false;
return true;
});
In beforeMatch method I check If module exist.
For second param you can use closure and check via
if ($di->has('modulename'))
Update1
As I can see https://github.com/phalcon/cphalcon/blob/master/phalcon/mvc/application.zep#L232
you can use event manager and return false from beforeStartModule if module not found in DI
if typeof eventsManager == "object" {
if eventsManager->fire("application:beforeStartModule", this, moduleName) === false {
return false;
}
}
Update2
Also you can use dispatcher setting:
// Initialize the Dispatcher
$di->setShared('dispatcher', function() use ($eventsManager) {
$dispatcher = new \Phalcon\Mvc\Dispatcher;
// Attach a listener for type "dispatch:beforeException"
$eventsManager->attach('dispatch:beforeException', function($event, $dispatcher, $exception) {
/**
* #var \Phalcon\Mvc\Dispatcher\Exception $exception
* #var \Phalcon\Mvc\Dispatcher $dispatcher
*/
switch ($exception->getCode()) {
case \Phalcon\Mvc\Dispatcher::EXCEPTION_HANDLER_NOT_FOUND:
case \Phalcon\Mvc\Dispatcher::EXCEPTION_ACTION_NOT_FOUND:
case ANY OTHER CODE HERE:
$dispatcher->forward([
'controller' => 'error',
'action' => 'show404'
]);
return false;
}
});
// Setting up the Dispatcher component
$dispatcher->setDefaultNamespace('your_default_namespace_here');
// Obtain the Events Manager from the DI and bind the eventsManager to the module dispatcher
$dispatcher->setEventsManager($eventsManager);
return $dispatcher;
});

How to select and return data using FKs in Yii2 RESTful API?

I managed to write the REST API code and it works for the standard actions.
Now, if I want to send more attributes, like url_to_api_action?a=b&c=d&e=f, this does not match any of the standard actions.
I need to search by attributes, using a RESTful API in Yii2.
any ideas?
<?php
namespace api\modules\v1\controllers;
use yii\rest\ActiveController;
class UneController extends ActiveController {
public $modelClass = 'common\models\Une';
}
I'm elaborating the answer
add search action mentioned in this link to the controller
Yii2 REST query
<?php
namespace api\modules\v1\controllers;
use yii\rest\ActiveController;
use yii\data\ActiveDataProvider;
/**
* Country Controller API
*
* #author Budi Irawan <deerawan#gmail.com>
*/
class CountryController extends ActiveController
{
public $modelClass = 'api\modules\v1\models\Country';
public $serializer = [
'class' => 'yii\rest\Serializer',
'collectionEnvelope' => 'items',
];
public function actionSearch()
{
if (!empty($_GET)) {
$model = new $this->modelClass;
foreach ($_GET as $key => $value) {
if (!$model->hasAttribute($key)) {
throw new \yii\web\HttpException(404, 'Invalid attribute:' . $key);
}
}
try {
$provider = new ActiveDataProvider([
'query' => $model->find()->where($_GET),
'pagination' => false
]);
} catch (Exception $ex) {
throw new \yii\web\HttpException(500, 'Internal server error');
}
if ($provider->getCount() <= 0) {
throw new \yii\web\HttpException(404, 'No entries found with this query string');
}
else {
return $provider;
}
}
else {
throw new \yii\web\HttpException(400, 'There are no query string');
}
}
}
And add urlManager like below in the config/main.php
Cant use tockens and extrapattern together for REST services in Yii2
'urlManager' => [
'enablePrettyUrl' => true,
'enableStrictParsing' => true,
'showScriptName' => false,
'rules' => [
[
'class' => 'yii\rest\UrlRule',
'controller' => 'v1/country',
'extraPatterns' => [
'GET search' => 'search'
],
],
[
'class' => 'yii\rest\UrlRule',
'controller' => 'v1/country',
'tokens' => [
'{id}' => '<id:\\w+>'
]
],
],
]
therefor we can use both default actions of the activecontroller and our custom actions together
You can create your own actions inside the controller, and you just need to return the result from Active Record and it will take care of formatting the data.
public function actionSearch($keyword)
{
$result = YourModel::find()
->where(['keyword' => $keyword])
->all();
return $result;
}
More details here: http://www.yiiframework.com/doc-2.0/guide-rest.html#creating-controllers-and-actions
public function actionSearch($keyword)
{
$result = YourModel::find()
->with('model relation')
->where(['keyword' => $keyword])
->all();
return $result;
}

A multi module MVC structure in phalconphp

Hi I'm trying to implement a Multi Module MVC for frontend and backend like what is in phalconphp documentations. But I can't make it work. it's about an hour but I really can not understand where is the problem.
Could anyone guide me how can I make a skeleton for a multi module mvc for frontend and backend.
what should I put in Moudle.php for frontend and backend
And also what should I put in bootstrap file located in public/index.php
And any extra file or information I need.
The code in the phalcon/mvc repository on GitHub will help. You can find it here:
https://github.com/phalcon/mvc/tree/master/multiple
More specifically, you'll be interested in:
https://github.com/phalcon/mvc/blob/master/multiple/public/index.php
https://github.com/phalcon/mvc/blob/master/multiple/apps/backend/Module.php
I tend to use this in my index.php:
$application = new \Phalcon\Mvc\Application($di);
// Register the installed modules
$application->registerModules(
array(
'web' => array(
'className' => 'Apps\Web\Module',
'path' => '../apps/web/Module.php',
)
)
);
echo $application->handle()->getContent();
And in my Module.php:
<?php
namespace Apps\Web;
use Phalcon\Loader;
use Phalcon\Mvc\Dispatcher;
use Phalcon\Mvc\View;
use Phalcon\Mvc\ModuleDefinitionInterface;
class Module implements ModuleDefinitionInterface
{
/**
* Register a specific autoloader for the module
*/
public function registerAutoloaders()
{
$loader = new Loader();
$loader->registerNamespaces(
array(
'Apps\Web\Controllers' => '../apps/web/controllers/',
)
);
$loader->register();
}
/**
* Register specific services for the module
* #param \Phalcon\DI\FactoryDefault $di
*/
public function registerServices($di)
{
//Registering a dispatcher
$di->set(
'dispatcher',
function() use ($di) {
$eventsManager = $di->getShared('eventsManager');
$dispatcher = new Dispatcher();
$dispatcher->setDefaultNamespace('Apps\Web\Controllers');
$eventsManager->attach(
'dispatch:beforeException',
function($event, $dispatcher, $exception) use ($di) {
/* #var $dispatcher \Phalcon\Mvc\Dispatcher */
switch ($exception->getCode()) {
case Dispatcher::EXCEPTION_HANDLER_NOT_FOUND:
case Dispatcher::EXCEPTION_ACTION_NOT_FOUND:
$di->set('lastException', $exception);
$dispatcher->forward(
array(
'module' => 'web',
'controller' => 'error',
'action' => 'notFound',
)
);
return false;
default:
$di->set('lastException', $exception);
$dispatcher->forward(
array(
'module' => 'web',
'controller' => 'error',
'action' => 'uncaughtException',
)
);
return false;
}
}
);
$dispatcher->setEventsManager($eventsManager);
return $dispatcher;
}
);
}
}
Hopefully this helps!

Categories