I have this problem with Slim framework. I have class Template that have render method and I want for Slim to render objects of this class if they are returned by route handlers
$app->get('/test', function() {
return new Template('main', function() use ($error) {
return array(
'content' => "Hello"
);
});
});
it work I created child class (in System.php)
class System extends Slim {
function __constructor() {
Slim::__construct();
}
private function auto_render_fun($callable) {
$app = $this;
return function() use ($callable, $app) {
$args = func_get_args();
$ret = call_user_func_array($callable, $args);
if ($ret instanceof Template) {
//render Template - Slim ignore return value
$app->response()->body($ret->render());
}
};
}
protected function mapRoute($args) {
$last = count($args)-1;
$args[$last] = $this->auto_render_fun($args[$last]);
return Slim::mapRoute($args);
}
}
I wanted to do the same thing with notFound
$app->notFound(function () use ($app) {
$response = $app->response();
$response['Content-Type'] = 'text/html';
return new Template('main', function() {
return array('content' => new Template('error_404', null));
});
});
so I've overwritten notFound function to wrap Closure and render it's return value
First I try to use smaller code
public function notFound($callable = null) {
if (!is_null(($callable))) {
$this->router->notFound($this->auto_render_fun($callable));
} else {
Slim::notFound();
}
}
I also try this (copy and modify old code).
public function notFound($callable = null) {
if ( !is_null($callable) ) {
$this->router->notFound($this->auto_render_fun($callable));
// $this->router->notFound($callable); // old line
} else {
ob_start();
$customNotFoundHandler = $this->router->notFound();
if ( is_callable($customNotFoundHandler) ) {
call_user_func($customNotFoundHandler);
} else {
call_user_func(array($this, 'defaultNotFound'));
}
$this->halt(404, ob_get_clean());
}
}
but the reason why it didn't work is that it trow Slim_Exception_Stop that's suppose to be cached by Slim here is that line for code that call $this->notFound();
https://github.com/codeguy/Slim/blob/master/Slim/Slim.php#L1160
it's inside try..catch.
Here is stack trace (I cached it inside notFound function - but it should be handled in Slim class).
Slim_Exception_Stop in file libs/Slim/Slim/Slim.php at 862
0: Slim->stop() in libs/Slim/Slim/Slim.php at 882
1: Slim->halt(integer, string) in libs/System.php at 187
2: System->notFound() in libs/Slim/Slim/Slim.php at 1161
3: Slim->call() in libs/Slim/Slim/Middleware/Flash.php at 84
4: Slim_Middleware_Flash->call() in libs/Slim/Slim/Middleware/MethodOverride.php at 91
5: Slim_Middleware_MethodOverride->call() in libs/Slim/Slim/Middleware/PrettyExceptions.php at 65
6: Slim_Middleware_PrettyExceptions->call() in libs/Slim/Slim/Middleware/PrettyExceptions.php at 65
7: Slim_Middleware_PrettyExceptions->call() in libs/Slim/Slim/Slim.php at 1098
8: Slim->run() in index.php at 573
Ok, I found the reason, exception was handled as it should but there was not content. I wrongly assume that it's something with Classes, but simply the Slim code have bugs.
public function halt( $status, $message = '' ) {
$this->cleanBuffer();
$this->response->status($status);
$this->response->body($message);
$this->stop();
}
function overwrite data that you use in the handler like this
$app->notFound(function() {
$app->response()->body('Error');
});
won't work, function not found should look like this
public function notFound($callable = null) {
if (!is_null(($callable))) {
$this->router->notFound($this->auto_render_fun($callable));
} else {
$customNotFoundHandler = $this->router->notFound();
if (is_callable($customNotFoundHandler)) {
call_user_func($customNotFoundHandler);
} else {
call_user_func(array($this, 'defaultNotFound'));
}
$this->response->status(404);
$this->stop();
}
}
Related
I'm attempting to write a custom auth checker for Symfony5. This is to run on selected Controllers, all of which have an AuthenticationControllerInterface which includes numerous other bits of relevant code.
I am attempting to use an EventSubscriber bound to the ControllerEvent. This checks for this interface and identifies correctly the relevant controllers.
Please see the below for context:
class BearerTokenSubscriber implements EventSubscriberInterface
{
public function onKernelController(ControllerEvent $event)
{
$controller = $event->getController();
// load the controller
if (is_array($controller)) {
$controller = $controller[0];
if ($controller instanceof AuthenticationControllerInterface) {
if(
// my assorted auth functionality: works as expected
) {
//... where my question lies:
}
}
}
}
}
public static function getSubscribedEvents()
{
return [
KernelEvents::CONTROLLER => 'onKernelController',
];
}
}
At "where my question lies": is the point I want to do a redirect... in the following ways (in order of preference):
returning a specific controller method (I have a 403 pre-configured).
redirecting to another URL (with a 403)
Thanks in advance.
You can use ControllerEvent::setController($myController) to provide a controller of your choice once your conditions are met:
class TestControllerEventSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
$events = [];
$events[KernelEvents::CONTROLLER] = ['onKernelController', 0];
return $events;
}
public function onKernelController(ControllerEvent $event): void
{
$controller = $this->getControllerObjectFromEvent($event);
// Check if your conditions are met
if ($controller instanceof AuthenticationControllerInterface && $whateverElse) {
$event->stopPropagation();
// Set your custom controller that produces a 403 response
$event->setController(static function () {
return new Response(null, 403);
});
}
}
private function getControllerObjectFromEvent(ControllerEvent $event): ?object
{
$controller = $event->getController();
if (true === is_object($controller)) {
return (object) $controller;
}
if (false === is_array($controller)) {
return null;
}
foreach ($controller as $value) {
if (true === is_object($value)) {
return $value;
}
}
return null;
}
}
The specific answer in my case was:
if (/* some test logic */) {
$event->stopPropagation();
$event->setController(
static function () use ($controller) {
return $controller->some403ErrorResponse();
}
);
}
Many thanks to Jeroen who pointed me in the correct direction.
Have marked his as correct also.
So my issue is, I have no idea how to handle page not found handling, Since routing runs every time a route is added if you do anything other than the first route it'll have 2 outputs.
Routes.php
Route::set('index.php', function() {
Index::CreateView('Index');
});
Route::set('test', function() {
Test::CreateView('Test');
});
?>
Routes.php (class)
<?php
class Route {
public static $validRoutes = array();
public static function set($route, $function) {
self::$validRoutes[] = $route;
$url = $_GET['url'];
if($url == $route) {
$function->__invoke();
die();
}
if(!in_array($url, self::$validRoutes)) {
Controller::CreateView("404");
die();
}
}
}
?>
I'm trying to understand how I'd even handle if its not found.
How about something like:
<?php
class Router
{
public $routes = [];
public function add($route, $function)
{
$this->routes[$route] = $function;
}
public function route($path)
{
$function =
$this->routes[$path] ??
$this->routes['404'] ?? null;
if(is_null($function))
http_response_code(404) && exit('404 Not Found.');
$function->__invoke();
}
}
$router = new Router;
$router->add('foo', function() {
echo 'bar';
});
$router->add('greeting', function() {
echo 'hello earth';
});
$router->route('greeting');
Output:
hello earth
Rather than trying to resolve your routes upon each addition, you can add them all, and then resolve/dispatch/route later.
I've simplified the Router::routes array to use the paths as keys.
When resolving, if the path cannot be found (the index does not exist) it will try and check for a '404' key in the routes array. Failing that it responds with a basic 404.
Here is my code:
public function call_with_attempts($class_obj, $method_name,array $params = [], $attempts = 5) {
while ($attempts-- > 0) {
try {
if (call_user_func(array($class_obj, $method_name), $params)['result_code'] == '01') {
// log success
return $class_obj->$method_name();
}
throw new Exception("failed");
} catch (Exception $e) {
trigger_error($e->getMessage());
// log the error
}
}
}
I call it like this:
$url = 'www.example.com';
$obj = new Profile($url);
all_with_attempts($obj, 'mymethod');
And here is Profile class:
class profile {
public $url;
public function __construct( $passed_url )
{
$this->url = $passed_url;
}
public function mymethod(){
// do stuff
}
}
Any my code (when I call this function all_with_attempts($obj, 'mymethod');). throws this error:
Missing argument 1 for App\classes\Profile::__construct(), called in {/path}
As you know, this error ^ means Profile class will be create again .. well that's not what I want. I want to use $obj. How can I do that?
I wonder if this below is possible in php class object, just as I would do in javascript (jquery).
In jquery, I would do,
(function($){
var methods = {
init : function( options ) {
// I write the function here...
},
hello : function( options ) {
// I write the function here...
}
}
$.fn.myplugin = function( method ) {
if ( methods[method] ) {
return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
} else if ( typeof method === 'object' || ! method ) {
return methods.init.apply( this, arguments );
} else {
$.error( 'Method ' + method + ' does not exist.' );
}
return this;
};
})(jQuery);
So, when I want to call a function inside myplugin, I just do this,
$.fn.myplugin("hello");
So, I thought, there might be a way to do this in php as well when you come to write a class?
$method = (object)array(
"init" => function() {
// I write the function here...
},
"hello" => function() {
// I write the function here...
}
);
EDIT:
Could it be a class like this?
class ClassName {
public function __construct(){
//
}
public function method_1(){
$method = (object)array(
"init" => function() {
// I write the function here...
},
"hello" => function() {
// I write the function here...
}
);
}
public function method_2(){
$method = (object)array(
"init" => function() {
// I write the function here...
},
"hello" => function() {
// I write the function here...
}
);
}
}
Your $.fn.myplugin function is very similar to the __call() magic function in PHP. However, you would have to define it in a class and emulate the logic:
class Example {
private $methods;
public function __construct() {
$methods = array();
$methods['init'] = function() {};
$methods['hello'] = function() {};
}
public function __call($name, $arguments) {
if( isset( $methods[$name])) {
call_user_func_array( $methods[$name], $arguments);
} else if( $arguments[0] instanceof Closure) {
// We were passed an anonymous function, I actually don't think this is possible, you'd have to pass it in as an argument
call_user_func_array( $methods['init'], $arguments);
} else {
throw new Exception( "Method " . $name . " does not exist");
}
}
}
Then, you would do:
$obj = new Example();
$obj->hello();
It's not tested but hopefully it's a start.
PHP supports Closure (Anonymous functions)
similar to jQuery take a look at
function x(callable $c){
$c();
}
then use
x(function(){
echo 'Hello World';
});
class ClassName {
public function __construct(){
//this is your init
}
public function hello(){
//write your function here
}
}
is how you would write it
then
$a = new ClassName()
$a->hello();
to call it
I'm trying to make a very basic routing class and learn PHP closures by example. Basically, I want to make a routing feature like in Laravel but only with using closures.
function get($uri)
{
if($uri == '/account')
{
return true;
}
else
{
return false;
}
}
function Boo()
{
echo "Boo";
}
$route = new Route();
$route->get('/account', function() {
return $route->Boo();
});
I can do this without closures and see "Boo" as output.
How can I do this with closures? I currently see a blank output.
Ps. Functions are in correct class.
You need to actually accept the closure as a parameter of your get method, and call it, here's an example
class Route
{
function get($uri, Closure $closure=null)
{
if($uri == '/account')
{
// if the closure exists, call it, passing it this instance as its parameter
if (null !== $closure) {
$closure($this);
}
return true;
}
else
{
return false;
}
}
function Boo()
{
echo "Boo";
}
}
$route = new Route();
// have the closure accept a route as it's parameter
$route->get('/account', function($route) {
return $route->Boo();
});