Phalconphp micro mvc routing issue - phalcon

I have just started working on a simple restful service.
The folder structure I have is like so:
root
- /api
--/api/customers.php
So for example in the browser I intend to call http://domain/api/customers/fetchall
However, I only ever get the notFound handler called. The code in customers.php is:
<?php
use Phalcon\DI\FactoryDefault,
Phalcon\Mvc\Micro,
Phalcon\Config\Adapter\Ini;
$di = new FactoryDefault();
$di->set('config', function() {
return new Ini("config.ini");
});
$app = new Micro($di);
/**
* Create new customer
*/
$app->post('/create', function(){});
/**
* Retrieve all customers
*/
$app->get('/fetchall', function() use ($app) {
$data = array();
$data[] = array(
'id' => '123456',
'name' => 'customerName',
);
echo json_encode($data);
});
/**
* Find customer by name
*/
$app->get('/search/{name}', function($name){});
/**
* Find customer by email
*/
$app->get('/search/{email}', function($email){});
/**
* Find customer by postcode
*/
$app->get('/search/{postcode}', function($postcode){});
/**
* Move a customer
*/
$app->put('/move/{oldpostcode}/{newpostcode}', function($oldpostcode, $newpostcode){});
/**
* Delete customer
*/
$app->delete('/delete/{id:[0-9]+}', function($id) use ($app) {
$response = new Phalcon\Http\Response();
$response->setJsonContent(array('status' => 'OK'));
return $response;
});
$app->notFound(function () use ($app) {
$app->response->setStatusCode(404, "Not Found")->sendHeaders();
echo 'This is crazy, but this page was not found!';
});
//echo $_SERVER['REQUEST_URI'];
$app->handle();
If i instead use / instead of /fetchall then in works but it also matches any url as well which is no good.
Appreciate the help in advance.
Thanks
Adam

Look like a bug, but you need to specify the URL you want your application handles:
$app->handle(filter_input(INPUT_SERVER, 'REQUEST_URI'));
Replace REQUEST_URI by what you need.

In case you are still looking for an answer:
If you don't have .htacess please create one and add the following: as per tutorial
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ api/customers.php?_url=/$1 [QSA,L]
</IfModule>
Then in your code you have to edit paths that are to be matched from say:
$app->get('/fetchall', function() use ($app) {
to:
$app->get('/api/customers/fetchall', function() use ($app) {
That's all there was to it.

You guy may forgot to add the /api/customers prefix to your route. Try with this:
$app->get('/api/customers/fetchall', function() use ($app) {
$data = array();
$data[] = array(
'id' => '123456',
'name' => 'customerName',
);
echo json_encode($data);
});

Related

Vite proxy server rewrite

/**
* Generate proxy
* #param list
*/
export function createProxy(list: ProxyList = []) {
const ret: ProxyTargetList = {};
for (const [prefix, target] of list) {
const isHttps = httpsRE.test(target);
// https://github.com/http-party/node-http-proxy#options
ret[prefix] = {
target: target,
changeOrigin: true,
ws: true,
rewrite: (path) => path.replace(new RegExp(`^${prefix}`), ''),
// https is require secure=false
...(isHttps ? { secure: false } : {}),
};
}
console.log('proxy list');
console.log(ret);
return ret;
}
I have above method to create a list of proxy based on different prefix which defined in .env.development.
VITE_PROXY = [["/basic-api","http://127.0.0.1:3100"],["/api","http://127.0.0.1:8080"],["/upload","http://localhost:3300/upload"]]
But when I request url http://127.0.0.1/api/xxx, it is not rewrite to http://127.0.0.1:8080 as expected.
then I just add return path in rewrite function like this:
rewrite: (path) =>
{path.replace(new RegExp(`^${prefix}`), '');return path},
Then it works.
I am confused now. What's the difference there? I googled a lot about vite proxy rewrite, normally, no need to add return statement like I just did.
Thanks!
Hint: rewrite: (path) => {path.replace(new RegExp(^${prefix}), '');return path},
will return you the original, untransformed path, since replace returns a new string. Please see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace.
So probably the regex or underlying logic is not correct.

Symfony3.4 controller called but nothing happens after redirection

Here is the first question I published. I hope that you'll be able to help me.
My problem is described in the title but lets be more accurate:
I'm using PHP7.2 and Symfony3.4
Ive got a standard form whose redirect action is the following:
/**
* #Route("/giveMetiers/{newAgent}", name="giveMetiers")
* #param $newAgent
* #return Response
*/
public function giveMetiersAction($newAgent)
{
$agent = $this->getProfil();
$response = new Response();
$response->setContent(
$this->render(
'AgentBundle:Templates:blank.html.twig',
[
"agent" => $agent,
"url" => $this->generateUrl(
'giveMetiersCorpus',
["newAgent" => $newAgent]
),
"title" => "Attribuer des métiers"
]
)
->getContent()
);
return $response;
}
The function giveMetierCorpus is the following:
/**
* #Route("/giveMetiersCorpus{newAgent}", name="giveMetiersCorpus")
* #param $newAgent
* #return Response
*/
public function giveMetiersCorpusAction($newAgent)
{
//Some business logic..
$template = $this->render(
'AgentBundle:SuperAgent:giveMetiers.html.twig',
[
"poles" => $poles,
"newAgent" => $newagent,
"metiers" => $metiers,
"constMetier" => $constMetier,
"constCompetence" => $constCompetence
]
)
->getContent();
$json = json_encode($template);
$response = new Response($json, 200);
$response->headers->set('Content-Type', 'application/json');
return $response;
}
Well, everything works, my view got called properly and a new form is then displayed. Here comes trouble. When i submit this new form, the executed action is:
/**
* #Route("/updateMetiersAgent", name="updateMetiersAgent")
* #param Request $request
* #return Response
*/
public function updateMetiersAgent(Request $request)
{
//Business logics..
return $this->redirect(
$this->generateUrl(
'giveCompetences',
["newAgent" => $agent->getId()]
)
);
}
Which calls giveCompetences..:
/**
* #Route("/giveCompetences/{newAgent}", name="giveCompetences")
* #param $newAgent
* #return Response
*/
public function giveCompetencesAction($newAgent)
{
$agent = $this->getProfil();
$response = new Response();
$response->setContent(
$this->render(
'AgentBundle:Templates:blank.html.twig',
[
"agent" => $agent,
"url" => $this->generateUrl(
'giveCompetencesCorpus',
["newAgent" => $newAgent]
),
"title" => "Attribuer des competences"
]
)
->getContent()
);
return $response;
}
Here, the fonction giveCompetencesCorpus isnt called... I give you this function:
/**
* #Route("/giveCompetencesCorpus{newAgent}", name="giveCompetencesCorpus")
* #param $newAgent
* #return Response
*/
public function giveCompetencesCorpusAction($newAgent)
{
//Business Logic
$template = $this->render(
'AgentBundle:SuperAgent:giveCompetences.html.twig',
[
"poles" => $poles,
"newAgent" => $newagent,
"technos" => $listeTechnos,
"constMetier" => $constMetier,
"constCompetence" => $constCompetence
]
)
->getContent();
$json = json_encode($template);
$response = new Response($json, 200);
$response->headers->set('Content-Type', 'application/json');
return $response;
}
I really dont understand why this isn't call.
I even tried to directly call this last function directly as an action from my form, i see that the function is called but nothing happens, nothing got render to the page.
Does anyone got the solution of this problem please?
Best Regards,
Maxime
You seem to have forgotten the slash before the placeholder in your route
#Route("/giveCompetencesCorpus{newAgent}", name="giveCompetencesCorpus")
Should probably be something like
#Route("/giveCompetencesCorpus/{newAgent}", name="giveCompetencesCorpus")
Thanks for your answer but that was not the problem (I've already tried this way).
I finally figured out the problem (but still I don't know why..)
The first form was built with real TAG and a submit button while the second was submitted using a Javascript FormData (the code was originally not written by me).
That was apparently the problem, when I changed the behavior of this form using a "normal form" everything was then going right.
However, if someone know the real underlying reason of this "bug" I'm interested in :)
Maxime

Make an ajax request from a Prestashop module

I am making a module and I need to make an ajax request, with JSON response if possible, how can i do this ?
I don't understand really well the structure of Prestashop 1.7 on this.
Thanks !
This is pretty simple, you just have to make the controller with Prestashop's standards then link it to your frontend Javascript.
Name a php file like this : ./modules/modulename/controllers/front/ajax.php
Then put inside :
<?php
// Edit name and class according to your files, keep camelcase for class name.
require_once _PS_MODULE_DIR_.'modulename/modulename.php';
class ModuleNameAjaxModuleFrontController extends ModuleFrontController
{
public function initContent()
{
$module = new ModuleName;
// You may should do some security work here, like checking an hash from your module
if (Tools::isSubmit('action')) {
// Usefull vars derivated from getContext
$context = Context::getContext();
$cart = $context->cart;
$cookie = $context->cookie;
$customer = $context->customer;
$id_lang = $cookie->id_lang;
// Default response with translation from the module
$response = array('status' => false, "message" => $module->l('Nothing here.'));
switch (Tools::getValue('action')) {
case 'action_name':
// Edit default response and do some work here
$response = array('status' => true, "message" => $module->l('It works !'));
break;
default:
break;
}
}
// Classic json response
$json = Tools::jsonEncode($response);
echo $json;
die;
// For displaying like any other use this method to assign and display your template placed in modules/modulename/views/template/front/...
// Just put some vars in your template
// $this->context->smarty->assign(array('var1'=>'value1'));
// $this->setTemplate('template.tpl');
// For sending a template in ajax use this method
// $this->context->smarty->fetch('template.tpl');
}
}
?>
In your Module Hooks, you need to bring access to the route in JS, so we basicaly make a variable :
// In your module PHP
public function hookFooter($params)
{
// Create a link with the good path
$link = new Link;
$parameters = array("action" => "action_name");
$ajax_link = $link->getModuleLink('modulename','controller', $parameters);
Media::addJsDef(array(
"ajax_link" => $ajax_link
));
}
On the frontend side, you just call it like this in a JS file (with jQuery here) :
// ajax_link has been set in hookfooter, this is the best way to do it
$(document).ready(function(){
$.getJSON(ajax_link, {parameter1 : "value"}, function(data) {
if(typeof data.status !== "undefined") {
// Use your new datas here
console.log(data);
}
});
});
And voila, you have your ajax ready to use controller

Authentication with 2 different tables

I need to create a new "auth" config with another table and users. I have one table for the "admin" users and another table for the normal users.
But how can I create another instance of Auth with a different configuration?
While trying to solve this problem myself, I found a much simpler way. I basically created a custom ServiceProvider to replace the default Auth one, which serves as a factory class for Auth, and allows you to have multiple instances for multiple login types. I also stuck it all in a package which can be found here: https://github.com/ollieread/multiauth
It's pretty easy to use really, just replace the AuthServiceProvider in app/config/app.php with Ollieread\Multiauth\MultiauthServiceProvider, then change app/config/auth.php to look something like this:
return array(
'multi' => array(
'account' => array(
'driver' => 'eloquent',
'model' => 'Account'
),
'user' => array(
'driver' => 'database',
'table' => 'users'
)
),
'reminder' => array(
'email' => 'emails.auth.reminder',
'table' => 'password_reminders',
'expire' => 60,
),
);
Now you can just use Auth the same way as before, but with one slight difference:
Auth::account()->attempt(array(
'email' => $attributes['email'],
'password' => $attributes['password'],
));
Auth::user()->attempt(array(
'email' => $attributes['email'],
'password' => $attributes['password'],
));
Auth::account()->check();
Auth::user()->check();
It also allows you to be logged in as multiple user types simultaneously which was a requirement for a project I was working on. Hope it helps someone other than me.
UPDATE - 27/02/2014
For those of you that are just coming across this answer, I've just recently added support for reminders, which can be accessed in the same factory style way.
You can "emulate" a new Auth class.
Laravel Auth component is basically the Illuminate\Auth\Guard class, and this class have some dependencies.
So, basically you have to create a new Guard class and some facades...
<?php
use Illuminate\Auth\Guard as AuthGuard;
class CilentGuard extends AuthGuard
{
public function getName()
{
return 'login_' . md5('ClientAuth');
}
public function getRecallerName()
{
return 'remember_' . md5('ClientAuth');
}
}
... add a ServiceProvider to initialize this class, passing it's dependencies.
<?php
use Illuminate\Support\ServiceProvider;
use Illuminate\Auth\EloquentUserProvider;
use Illuminate\Hashing\BcryptHasher;
use Illuminate\Auth\Reminders\PasswordBroker;
use Illuminate\Auth\Reminders\DatabaseReminderRepository;
use ClientGuard;
use ClientAuth;
class ClientServiceProvider extends ServiceProvider
{
public function register()
{
$this->registerAuth();
$this->registerReminders();
}
protected function registerAuth()
{
$this->registerClientCrypt();
$this->registerClientProvider();
$this->registerClientGuard();
}
protected function registerClientCrypt()
{
$this->app['client.auth.crypt'] = $this->app->share(function($app)
{
return new BcryptHasher;
});
}
protected function registerClientProvider()
{
$this->app['client.auth.provider'] = $this->app->share(function($app)
{
return new EloquentUserProvider(
$app['client.auth.crypt'],
'Client'
);
});
}
protected function registerClientGuard()
{
$this->app['client.auth'] = $this->app->share(function($app)
{
$guard = new Guard(
$app['client.auth.provider'],
$app['session.store']
);
$guard->setCookieJar($app['cookie']);
return $guard;
});
}
protected function registerReminders()
{
# DatabaseReminderRepository
$this->registerReminderDatabaseRepository();
# PasswordBroker
$this->app['client.reminder'] = $this->app->share(function($app)
{
return new PasswordBroker(
$app['client.reminder.repository'],
$app['client.auth.provider'],
$app['redirect'],
$app['mailer'],
'emails.client.reminder' // email template for the reminder
);
});
}
protected function registerReminderDatabaseRepository()
{
$this->app['client.reminder.repository'] = $this->app->share(function($app)
{
$connection = $app['db']->connection();
$table = 'client_reminders';
$key = $app['config']['app.key'];
return new DatabaseReminderRepository($connection, $table, $key);
});
}
public function provides()
{
return array(
'client.auth',
'client.auth.provider',
'client.auth.crypt',
'client.reminder.repository',
'client.reminder',
);
}
}
In this Service Provider, I put some example of how to create a 'new' password reminder component to.
Now you need to create two new facades, one for authentication and one for password reminders.
<?php
use Illuminate\Support\Facades\Facade;
class ClientAuth extends Facade
{
protected static function getFacadeAccessor()
{
return 'client.auth';
}
}
and...
<?php
use Illuminate\Support\Facades\Facade;
class ClientPassword extends Facade
{
protected static function getFacadeAccessor()
{
return 'client.reminder';
}
}
Of course, for password reminders, you need to create the table in database, in order to work. In this example, the table name should be client_reminders, as you can see in the registerReminderDatabaseRepository method in the Service Provider. The table structure is the same as the original reminders table.
After that, you can use your ClientAuth the same way you use the Auth class. And the same thing for ClientPassword with the Password class.
ClientAuth::gust();
ClientAuth::attempt(array('email' => $email, 'password' => $password));
ClientPassword::remind($credentials);
Don't forget to add your service provider to the service providers list in the app/config/app.php file.
UPDATE:
If you are using Laravel 4.1, the PasswordBroker doesn't need the Redirect class anymore.
return new PasswordBroker(
$app['client.reminder.repository'],
$app['client.auth.provider'],
$app['mailer'],
'emails.client.reminder' // email template for the reminder
);
UPDATE 2
Laravel 5.2 just introduced multi auth, so this is no longer needed in this version.
Ok, I had the same problem and here is how I solved it:
actually in laravel 4 you can simply change the auth configs at runtime so to do the trick you can simply do the following in your App::before filter:
if ($request->is('admin*'))
{
Config::set('auth.model', 'Admin');
}
this will make the Auth component to use th Admin model when in admin urls. but this will lead to a new problem, because the login session key is the same if you have two users in your admins and users table with the same id you will be able to login to the admin site if you have logged in before as a regular user! so to make the two different authetications completely independent I did this trick:
class AdminGuard extends Guard
{
public function getName()
{
return 'admin_login_'.md5(get_class($this));
}
public function getRecallerName()
{
return 'admin_remember_'.md5(get_class($this));
}
}
Auth::extend('eloquent.admin', function()
{
return new AdminGuard(new EloquentUserProvider(new BcryptHasher, 'Admin'), App::make('session.store'));
});
and change the App::before code to:
if ($request->is('admin*'))
{
Config::set('auth.driver', 'eloquent.admin');
Config::set('auth.model', 'Admin');
}
you can see that I made a new auth driver and rewrote some methods on the Guard class so it will generate different session keys for admin site. then I changed the driver for the admin site. good luck.
I had the same problem yesterday, and I ended up creating a much simpler solution.
My requirements where 2 different tables in two different databases. One table was for admins, the other was for normal users. Also, each table had its own way of hashing. I ended up with the following (Code also available as a gist on Github: https://gist.github.com/Xethron/6790029)
Create a new UserProvider. I called mine MultiUserProvider.php
<?php
// app/libraries/MultiUserProvider.php
use Illuminate\Auth\UserProviderInterface,
Illuminate\Auth\UserInterface,
Illuminate\Auth\GenericUser;
class MultiUserProvider implements UserProviderInterface {
protected $providers;
public function __construct() {
// This should be moved to the config later...
// This is a list of providers that can be used, including
// their user model, hasher class, and hasher options...
$this->providers = array(
'joomla' => array(
'model' => 'JoomlaUser',
'hasher' => 'JoomlaHasher',
)
'another' => array(
'model' => 'AnotherUser',
'hasher' => 'AnotherHasher',
'options' => array(
'username' => 'empolyee_number',
'salt' => 'salt',
)
),
);
}
/**
* Retrieve a user by their unique identifier.
*
* #param mixed $identifier
* #return \Illuminate\Auth\UserInterface|null
*/
public function retrieveById($identifier)
{
// Returns the current provider from the session.
// Should throw an error if there is none...
$provider = Session::get('user.provider');
$user = $this->createModel($this->providers[$provider]['model'])->newQuery()->find($identifier);
if ($user){
$user->provider = $provider;
}
return $user;
}
/**
* Retrieve a user by the given credentials.
*
* #param array $credentials
* #return \Illuminate\Auth\UserInterface|null
*/
public function retrieveByCredentials(array $credentials)
{
// First we will add each credential element to the query as a where clause.
// Then we can execute the query and, if we found a user, return it in a
// Eloquent User "model" that will be utilized by the Guard instances.
// Retrieve the provider from the $credentials array.
// Should throw an error if there is none...
$provider = $credentials['provider'];
$query = $this->createModel($this->providers[$provider]['model'])->newQuery();
foreach ($credentials as $key => $value)
{
if ( ! str_contains($key, 'password') && ! str_contains($key, 'provider'))
$query->where($key, $value);
}
$user = $query->first();
if ($user){
Session::put('user.provider', $provider);
$user->provider = $provider;
}
return $user;
}
/**
* Validate a user against the given credentials.
*
* #param \Illuminate\Auth\UserInterface $user
* #param array $credentials
* #return bool
*/
public function validateCredentials(UserInterface $user, array $credentials)
{
$plain = $credentials['password'];
// Retrieve the provider from the $credentials array.
// Should throw an error if there is none...
$provider = $credentials['provider'];
$options = array();
if (isset($this->providers[$provider]['options'])){
foreach ($this->providers[$provider]['options'] as $key => $value) {
$options[$key] = $user->$value;
}
}
return $this->createModel($this->providers[$provider]['hasher'])
->check($plain, $user->getAuthPassword(), $options);
}
/**
* Create a new instance of a class.
*
* #param string $name Name of the class
* #return Class
*/
public function createModel($name)
{
$class = '\\'.ltrim($name, '\\');
return new $class;
}
}
Then, I told Laravel about my UserProvider by adding the following lines to the top of my app/start/global.php file.
// app/start/global.php
// Add the following few lines to your global.php file
Auth::extend('multi', function($app) {
$provider = new \MultiUserProvider();
return new \Illuminate\Auth\Guard($provider, $app['session']);
});
And then, I told Laravel to use my user provider instead of EloquentUserProvider in app/config/auth.php
'driver' => 'multi',
Now, when I authenticate, I do it like so:
Auth::attempt(array(
'email' => $email,
'password' => $password,
'provider'=>'joomla'
)
)
The class would then use the joomlaUser model, with the joomlaHasher, and no options for the hasher... If using 'another' provider, it will include options for the hasher.
This class was built for what I required but can easily be changed to suite your needs.
PS: Make sure the autoloader can find MultiUserProvider, else it won't work.
I'm using Laravel 5 native auth to handle multiple user tables...
It's not difficult, please check this Gist:
https://gist.github.com/danielcoimbra/64b779b4d9e522bc3373
UPDATE: For Laravel 5, if you need a more robust solution, try this package:
https://github.com/sboo/multiauth
Daniel

Layout redirect in zend Framework 2

Is it possible to redirect to a route in Layout.phtml in ZF2. I want to redirect to login page if the user is not logged in from layout.phtml
So far i have tried:
<?php $auth = new AuthenticationService();
if ($auth->hasIdentity()) {?>
<li class="active"><a href="<?php echo $this->url('home') ?>">
<?php echo $this->translate('Home') ?></a></li>
<li class="active"><?php echo $this->translate('Logout') ?></li>
<?php
}
else
{
$this->_forward('login/process');
} ?>
its giving me error "get was unable to fetch or create an instance for _forward"
BOOTSTRAP CODE:
public function onBootstrap(MvcEvent $e)
{
$e->getApplication()->getServiceManager()->get('translator');
$eventManager = $e->getApplication()->getEventManager();
$moduleRouteListener = new ModuleRouteListener();
$moduleRouteListener->attach($eventManager);
$eventManager = $e->getApplication()->getEventManager();
//nothing's available for non logged user, so redirect him to login page
$eventManager->attach("dispatch", function($e) {
$match = $e->getRouteMatch();
$list = $this->whitelist;
// Route is whitelisted
$name = $match->getMatchedRouteName();
if (in_array($name, $list)) {
return;
}
$sm = $e->getApplication()->getServiceManager();
$controller = $e->getTarget();
$auth = $sm->get('AuthService');
if (!$auth->hasIdentity() && $e->getRouteMatch()->getMatchedRouteName() !== 'login/process') {
$application = $e->getTarget();
$e->stopPropagation();
$response = $e->getResponse();
$response->setStatusCode(302);
$response->getHeaders()->addHeaderLine('Location', $e->getRouter()->assemble(array(), array('name' => 'login/process')));
//returning response will cause zf2 to stop further dispatch loop
return $response;
}
}, 100);
}
This is not something that you wanna be doing inside your layout.phtml. Typically you want to hook in to an event that happens before the rendering. In ZF2, the earliest event to hook into that kind of stuff, where it makes sense to hook into, would be the route event. A good diagram of the process that's used in the Authorization-Module BjyAuthorize explains it quite well:
If you don't want to use the Module, you can minify what's happening there, too, like this:
//class Module
public function onBootstrap(MvcEvent $mvcEvent)
{
$eventManager = $mvcEvent->getApplication()->getEventManager();
$eventManager->attach(MvcEvent::EVENT_ROUTE, array($this, 'onRoute'), -1000);
}
public function onRoute(MvcEvent $event)
{
$serviceLocator = $mvcEvent->getApplication()->getServiceLocator();
// From this point onwards you have access to the ServiceLocator and can check
// for an authenticated user and if the user is not logged in, you return a
// Response object with the appropriate ResponseCode redirected and that's it :)
}