i am getting error in my slim framework application. i don't know why twig-view is not working. twig-view is downloaded in vendor directory.
this is my index file
<?php
require __DIR__ . '/vendor/autoload.php';
// Settings
$config = [
'settings' => [
'displayErrorDetails' => true,
'addContentLengthHeader' => false,
],
];
$app = new \Slim\App($config);
// Get container
$container = $app->getContainer();
// Register component on container
$container['view'] = function ($container) {
$view = new \Slim\Views\Twig( __DIR__ . '/resources/views', [
'cache' => false
]);
// Instantiate and add Slim specific extension
$view->addExtension(new Slim\Views\TwigExtension(
$container['router'],
$container['request']->getUri()
));
return $view;
};
// Home
$app->get('/home','index');
function index($request, $response, $args)
{
return $this->view->render($response, 'home.twig'); // here is the error
}
$app->run();
i am getting error om $this keyword
error details
Details
Type: Error
Message: Using $this when not in object context
File: C:\xampp\htdocs\slim\api\index.php
Line: 42
It is not possible to use this when you do not have a closure
If you use a Closure instance as the route callback, the closure’s state is bound to the Container instance. This means you will have access to the DI container instance inside of the Closure via the $this keyword.
(Reference: http://www.slimframework.com/docs/objects/router.html)
You can separate it when you assign the closure to a variable
$indexRoute = function ($request, $response, $args)
{
return $this->view->render($response, 'home.twig'); // here is the error
}
$app->get('/home', $indexRoute);
You are declaring route incorectly, try
// This callback will process GET request to /index URL
$app->get('/index', function($request, $response, $args) {
return $this->view->render($response, 'home.twig');
});
Instead of declaring a function, you should call $app method to register a route.
EDIT
It is also possible to "separate" route declaration from the callback. You can create separate classes (a-la controllers in MVC pattern), like this:
// Declaring a controller class with __invoke method, so it acts as a function
class MyController
{
public function __invoke($request, $resposne)
{
// process a request, return response
}
}
// And here's how you add it to the route
$app->get('/index', 'MyController');
I suggest you read the appropriate section of the documentation. It's dead simple.
Related
So I’m learning how to write a Slim 3 PHP authentication app and I'm using an example code structure to get me started. The example code has a file called dependencies.php which has a series of functions that create object instances of other classes. These are then assigned to a $container variable with a name for each function. An example of these functions in the dependencies.php file can be seen here:
$container['view'] = function ($container) {
$view = new \Slim\Views\Twig(
$container['settings']['view']['template_path'],
$container['settings']['view']['twig'],
[
'debug' => true // This line should enable debug mode
]
);
$basePath = rtrim(str_ireplace('index.php', '', $container['request']->getUri()->getBasePath()), '/');
$view->addExtension(new Slim\Views\TwigExtension($container['router'], $basePath));
$view->addExtension(new \Twig_Extension_Debug());
return $view;
};
$container['validate_sanitize'] = function ($container)
{
$class_path = $container->get('settings')['class_path'];
require $class_path . 'ValidateSanitize.php';
$validator = new ValidateSanitize();
return $validator;
};
$container['hash_password'] = function($container)
{
$class_path = $container->get('settings')['class_path'];
require $class_path . 'HashPassword.php';
$hash = new HashPassword();
return $hash;
};
These functions are then somehow called in my slim routes. For example in my register.php slim route the validate_sanitize class object is called with a simple
$this->get(‘validate_sanitize’);
and assigned to a variable which I can then use to call methods from the validate_sanitize class.
However what I don’t understand is how this get method works on calling a class object from the dependencies.php file.
Here is the aforementioned register route that is a post request for incoming form data:
$app->post('/register', function(Request $request, Response $response)
{
$arr_tainted_params = $request->getParsedBody();
$sanitizer_validator = $this->get('validate_sanitize'); //here for example
$password_hasher = $this->get('hash_password');
$tainted_email = $arr_tainted_params['email'];
$tainted_username = $arr_tainted_params['username'];
$tainted_password = $arr_tainted_params['password'];
$model = $this->get('model');
$sql_wrapper = $this->get('sql_wrapper');
$sql_queries = $this->get('sql_queries');
$db_handle = $this->get('dbase');
$cleaned_email = $sanitizer_validator->sanitize_input($tainted_email, FILTER_SANITIZE_EMAIL);
$cleaned_username = $sanitizer_validator->sanitize_input($tainted_username, FILTER_SANITIZE_STRING);
$cleaned_password = $sanitizer_validator->sanitize_input($tainted_password, FILTER_SANITIZE_STRING);
});
All my routes are contained within a routes.php file that looks like this:
<?php
require 'routes/change_password.php';
require 'routes/forgot_password.php';
require 'routes/homepage.php';
require 'routes/login.php';
require 'routes/logout.php';
require 'routes/register.php';
There is also a bootstrap file that creates a new Slim container, Slim App instance and also includes necessary files. I’m also not entirely sure what a Slim\Container is or what it does. This bootstrap file looks like this:
<?php
session_start();
require __DIR__ . '/../vendor/autoload.php';
$settings = require __DIR__ . '/app/settings.php'; //an array of options containing database configurations and the path to twig templates
$container = new \Slim\Container($settings); //not sure what this does
require __DIR__ . '/app/dependencies.php';
$app = new \Slim\App($container);
require __DIR__ . '/app/routes.php';
$app→run();
I’ve tried reading a number of articles as well as watching various YouTube videos but they do things differently with controllers which just adds more complexity and confusion for me. I’d much rather an explanation on this specific example as I find the code structure fairly simple.
Thanks.
Inside route callable, $this will point to $container instance.
In Slim 3.0, if you take a look at map() method of Slim\App class, you will see following code:
if ($callable instanceof Closure) {
$callable = $callable->bindTo($this->container);
}
bindTo() is what makes you have access to container inside route callable using $this variable.
If you want to use class as route handler and want access to container instance inside class, you need to pass it manually. For example
<?php
namespace App\Controller;
class MyPostRegisterController
{
private $container;
public function __constructor($container)
{
$this->container = $container;
}
public function __invoke(Request $request, Response $response)
{
$sanitizer_validator = $this->container->get('validate_sanitize');
//do something
}
}
Then you can define route as following
$app->post('/register', App\Controller\MyPostRegisterController::class);
If Slim can not find MyPostController class in dependency container, it tries to create them and pass container instance.
Update
To call other method, append method name after class name separated by colon. For example, following route registration will call method home() in MyPostRegisterController class.
$app->post('/register', App\Controller\MyPostRegisterController::class . ':home');
I'm trying to figure out if I can create a custom $request object in Slim 3, with some of my own custom methods. I saw https://github.com/slimphp/Slim/issues/1480 through which I wrote:
'settings' => [
'request' => function ($c) {
return \App\Request::createFromEnvironment($c['environment']);
},
]
$app = new \Slim\App($settings);
Then in my routes:
$app->get('/systems', '\App\Controllers\SystemsController:index');
But in my controller method, my $request object doesn't have any of the methods in my extended function. Not sure what I'm misunderstanding.
Try this in your config/container.php
$container = $app->getContainer();
$container['request'] = function ($container) {
return \App\Request::createFromEnvironment($container['environment']);
};
I have a problem with DI, it's new for me. I would like to give the Container as DI in my Project class, but i got an error :
Argument 1 passed to Project::__construct() must be an instance of Slim\Container, none given
I created a class Project :
use Slim\Container;
class Project {
protected $ci;
public function __construct(Container $ci) {
$this->ci = $ci;
}
}
Here my DIC configuration :
//dependencies.php
$container = $app->getContainer();
$container[Project::class] = function ($c) {
return new Project($c);
};
And here my code index.php to run the code
// Instantiate the app
$settings = require __DIR__ . '/../app/configs/settings.php';
$app = new \Slim\App($settings);
// Set up dependencies
require __DIR__ . '/../app/configs/dependencies.php';
// Register middleware
require __DIR__ . '/../app/configs/middleware.php';
// Register routes
require __DIR__ . '/../app/configs/routes.php';
require __DIR__ . '/../app/model/Project.php';
$app->get('/project/{id}', function (Request $request, Response $response) {
$project = new Project();
return $response;
});
$app->run();
I dont know where i failed, i even tried to use slim-brige, but i got the same result. I also tried to give a string instead of the the Container, and i still get null
Argument 1 passed to Project::__construct() must be an instance of Slim\Container, none given
Well, no argument given to the constructor indeed:
$app->get('/project/{id}', function (Request $request, Response $response) {
$project = new Project(); // You should pass container instance here, but you don't
return $response;
});
Since you have registered your Project class in the container, you can get instance from the container:
$app->get('/project/{id}', function (Request $request, Response $response) {
$project = $this->container->get('Project'); // Get Project instance from the container, you have already registered it
return $response;
});
I'm relatively new to Slim Framework 3. One thing I'm trying to understand is how to use the router, $this->router, in a "global" template.
What I mean by this is a template such as a navigation menu - something that appears on every page.
For templates I'm using the "php-view" library as per the example tutorial which I installed with:
composer require slim/php-view
In my templates directory I have a file called nav.php where I want to output my links.
I understand how to call the router like so
Sign Up
But... the example tutorial only shows how you would pass that link from 1 individual place, e.g. $app->get('/sign-up' ... })->setName("sign-up");
How can you use the router globally in any template, without passing it into every individual URL route as a parameter?
I'm more familiar with frameworks like CakePHP where there is an "AppController" which allows you to set things globally, i.e. available in every request. I don't know if this is how it's done in Slim but this is the effect I'm after.
Well, you can pass it as template variable.
When you instantiate or register PhpRenderer in a container, you have multiple options to define a "global" variable, i.e. a variable that is accessible in all of your templates:
// via the constructor
$templateVariables = [
"router" => "Title"
];
$phpView = new PhpRenderer("./path/to/templates", $templateVariables);
// or setter
$phpView->setAttributes($templateVariables);
// or individually
$phpView->addAttribute($key, $value);
Assuming you're registering PhpRenderer via Pimple:
<?php
// Create application instance
$app = new \Slim\App();
// Get container
$container = $app->getContainer();
// Register PhpRenderer in the container
$container['view'] = function ($container) {
// Declaring "global" variables
$templateVariables = [
'router' => $container->get('router')
];
// And passing the array as second argument to the contructor
return new \Slim\Views\PhpRenderer('path/to/templates/with/trailing/slash/', $templateVariables);
};
<?php namespace App\Helpers;
/********************/
//LinksHelper.php
/********************/
use Interop\Container\ContainerInterface;
class LinksHelper
{
protected $ci;
public function __construct(ContainerInterface $container){
$this->ci = $container;
}
public function __get($property){
if ($this->ci->has($property)) {
return $this->ci->get($property);
}
}
public function pathFor($name, $data = [], $queryParams = [], $appName = 'default')
{
return $this->router->pathFor($name, $data, $queryParams);
}
public function baseUrl()
{
if (is_string($this->uri)) {
return $this->uri;
}
if (method_exists($this->uri, 'getBaseUrl')) {
return $this->uri->getBaseUrl();
}
}
public function isCurrentPath($name, $data = [])
{
return $this->router->pathFor($name, $data) === $this->uri->getPath();
}
public function setBaseUrl($baseUrl)
{
$this->uri = $baseUrl;
}
}
?>
<?php
/********************/
//dependencies.php
/********************/
$container['link'] = function ($c) {
return new \App\Helpers\LinksHelper($c);
};
// view renderer
$container['view'] = function ($c) {
$settings = $c->get('settings');
$view = new App\Views\MyPhpRenderer($settings['renderer']['template_path']);
$view->setLayout('default.php');
//$view->addAttribute('title_for_layout', $settings['title_app'] .' :: ');
$view->setAttributes([
'title_for_layout'=>$settings['title_app'] .' :: ',
'link' => $c->get('link')
]);
return $view;
};
?>
<?php
/********************/
//routes.php
/********************/
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
$app->get('/', function (Request $request, Response $response, array $args) {
return $this->view->render($response, 'your_view.php');
})->setName('home');
?>
<?php
/********************/
//your_view.php
/********************/
?>
Home
You should create a new class, e.g. MainMenu, and there you should create an array with all paths for menu. Object of MainMenu should return an array with labels and paths and then you can pass that array to your view:
$menu = (new MainMenu())->buildMenu();
$response = $this->view->render($response, "index.phtml", [
'menu' => $menu
]);
Then in your *.phtml file you have access to the $menu variable. But what if you do not want repeat that code in each route?
Use middlewares. You can pass a variable from middleware using
$request = $request->withAttribute('foo', 'bar');
and retrieve
$foo = $request->getAttribute('foo');
<?php
require 'vendor/autoload.php';
// Include all controllers
foreach(glob("controllers/*.php") as $controller)
{
include $controller;
}
// Instantiate a new Slip application
$app = new \Slim\Slim(array(
'debug' => true
));
// HOME CONTROLLER
$home = new Home;
$vr = $home->index();
// Register application routes
$app->get('/', function () {
echo $vr;
});
// Run application
$app->run();
This is my controller I want to use controllers and not keep everything in this single file. Anyhow I have a controllers map where I keep all my controllers. I automatically include them all at start however I can't seem to pass $home variable to get() method so I could call $vr indede it or $home->index()
You can pass it to your function like this:
..., function () use($home){
...
I think Slim also passes $app as the first argument to your function.
Edit: actually it doesn't according to its docs, so you'll have to pass that too inside the use statement (function arguments are URL parameters):
$app->get('/', function () use($home, $app) {
$vr = $home->index();
echo $vr;
// $app is accesible too...
});