I want to make simple template rendering in Slim3 but I get an error:
Here is my code :
namespace controller;
class Hello
{
function __construct() {
// Instantiate the app
$settings = require __DIR__ . '/../../src/settings.php';
$this->app = new \Slim\App($settings);
}
public function index(){
return $this->app->render('web/pages/hello.phtml'); //LINE20
}
}
This is the error I get :
Message: Method render is not a valid method
The App object doesn't handle any rendering on its own, you'll need a template add-on for that, probably this one based on your template's .phtml extension. Install with composer:
composer require slim/php-view
Then your controller method will do something like this:
$view = new \Slim\Views\PhpRenderer('./web/pages');
return $view->render($response, '/hello.phtml');
You'll eventually want to put the renderer in the dependency injection container instead of creating a new instance in your controller method, but this should get you started.
I handle this by sticking my renderer in the container. Stick this in your main index.php file.
$container = new \Slim\Container($configuration);
$app = new \Slim\App($container);
$container['renderer'] = new \Slim\Views\PhpRenderer("./web/pages");
Then in your Hello class's file.
class Hello
{
protected $container;
public function __construct(\Slim\Container $container) {
$this->container = $container;
}
public function __invoke($request, $response, $args) {
return $this->container->renderer->render($response, '/hello.php', $args);
}
}
To clean up this code, make a base handler that has this render logic encapsulated for you.
Related
My Setup is like this:
I request my settings from this file and store them in the settings variable.
$settings = require __DIR__ . '/settings.php';
Next create a new Slim instance like so:
$app = new \Slim\App($settings);
$container = $app->getContainer();
$container['logger'] = function($c) {
$settings = $c->get('settings')['logger'];
$logger = new \Monolog\Logger($settings['name']);
$file_handler = new \Monolog\Handler\StreamHandler($settings['path']);
$logger->pushHandler($file_handler);
return $logger;
};
Then i am calling my route:
$this->get('/testlogger, __testReq::class . ':test);
The above route calls the "test" method inside of my class. Which gets loaded over autoload. Below my class (controller) in which i am trying to access the container like explained on Slim Website.
class __testReq {
function test($request, $response){
//According to Documentation i am supposed to be able to call logger like so:
$this->logger->addInfo("YEY! I am logging...");
}
}
Why is it not working?
From Slim documentation (Documentation uses HomeController class as example):
Slim first looks for an entry of HomeController in the container, if it’s found it will use that instance otherwise it will call it’s constructor with the container as the first argument.
So in your class __testReq constructor, you need to set up the object:
class __testReq {
// logger instance
protected $logger;
// Use container to set up our newly created instance of __testReq
function __construct($container) {
$this->logger= $container->get('logger');
}
function test($request, $response){
// Now we can use $this->logger that we set up in constructor
$this->logger->addInfo("YEY! I am logging...");
}
}
Background. I am using Slim where an ID is either in the endpoint or parameters. Based on the ID, a factory creates the appropriate object to perform the needed action.
I have a service which needs some data obtained in the request injected into it. I could therefore do the following:
//index.php
require '../vendor/autoload.php';
use Pimple\Container;
$container = new Container();
class SomeService
{
private $dataFromTheRequest;
public function __construct($dataFromTheRequest){
$this->dataFromTheRequest=$dataFromTheRequest;
echo($dataFromTheRequest);
}
}
$dataFromTheRequest='stuff1';
$container['someService1'] = function ($c) use($dataFromTheRequest) {
return new SomeService($dataFromTheRequest);
};
$someService=$container['someService1'];
But the service is not used in index.php where the service is defined but in another class, so i can do the following:
class SomeOtherClass1
{
public function someMethod($dataFromTheRequest){
$someService=new SomeService($dataFromTheRequest);
}
}
$someOtherClass1=new SomeOtherClass1();
$someOtherClass1->someMethod('stuff2');
But I want to use the instance assigned in index.php, so I can do the following:
$container['someService2'] = function ($c) {
return new SomeService($c['dataFromTheRequest']);
};
class SomeOtherClass2
{
public function __construct($container){
$this->container=$container;
}
public function someMethod($dataFromTheRequest){
$this->container['dataFromTheRequest']='stuff3';
$someService=$this->container['someService2'];
}
}
$someOtherClass2=new SomeOtherClass2($container);
$someOtherClass2->someMethod();
But using the container to pass data just seems wrong.
How should data be injected in a pimple service if that data is not known when the service is defined?
I’m following this tutorial from laracast(https://laracasts.com/series/php-for-beginners) and I’m at this episode(16 - Make a Router) in the series
have done all sorts of things to understand one part in this tutorial but I could't after I spent many hours.
The tutorial is about make routes system similar to laravel framework
The route class
/**
* Created by PhpStorm.
* User: ahmadz
* Date: 7/2/2017
* Time: 7:30 PM
*/
class Router
{
public $route = [
'GET' => [],
'POST' => [],
];
public static function load($file)
{
$router = new static;
require $file;
return $router;
}
public function get($name, $path)
{
$this->route['GET'][$name] = $path;
}
public function uriProcess($uri, $method)
{
if(array_key_exists($uri,$this->route[$method])){
return $this->route[$method][$uri];
}
throw new Exception('Error in the uri');
}
}
routes file
$router->get('learn/try','controller/index.php');
$router->get('learn/try/contact','controller/contact.php');
index file
require Router::load('routes.php')->uriProcess(Request::uri(), Request::method());
the problem occur when I change this to
public static function load($file)
{
require $file;
}
I removed these 2 lines
$router = new static;
return $router;
and then instantiate an object in routes file
$router = new Router;
$router->get('learn/try','controller/index.php');
$router->get('learn/try/contact','controller/contact.php');
When I do this I get these errors
Fatal error: Uncaught Error: Call to a member function uriProcess() on
null in C:\xampp\htdocs\learn\try\index.php on line 12 ( ! ) Error:
Call to a member function uriProcess() on null in
C:\xampp\htdocs\learn\try\index.php on line 12
Can you explain way I can't instantiate an object in routes file instead of load function ?
You've removed the important part of your code.
In your load() method you actually instantiate the Router class and then return the newly created $router object.
When you remove the following lines:
$router = new static;
return $router;
The method load() returns nothing, hence you get the aforementioned error.
You've to understand, you're trying to use the method uriProcess() which is a method of the class Router, but how do you expect this method to work when you don't have any object in your hand?
You will have to use the code you've shown at the beginning:
public static function load($file)
{
$router = new static;
require $file;
return $router;
}
Edit:
After I understood what you meant, you may try the following code:
Router::load('routes.php');
$router = new Router;
$router->uriProcess(Request::uri(), Request::method());
$router->get('learn/try', 'controller/index.php');
$router->get('learn/try/contact', 'controller/contact.php');
I am trying to build a loader to load various controller with their methods (as needed) within a controller. I sketched out a simple code on my home controller to call the LeftController (now a dummy controller but I intend to use this controller as menu).
require 'controller/LeftController.php';
$LeftController = new LeftController();
$LeftController->index();
This works within the HomeController. It loads the LeftController controller and displays the method index().
Basing my Loader on the above code this is what I have done till now
class Loader
{
public function controller($controller)
{
$file = 'controller/' . $controller . '.php';
$class = $controller;
if (file_exists($file)) {
require($file); // require 'controller/LeftController.php';
$controller = new $class(); //$LeftController = new LeftController();
var_dump($controller);
}
}
}
This works too and the controller is instantiated. I see result using the var_dump().
Now, I need to call the method, as we see at the top most code $LeftController->index(); but on the Loader class this time.
One way of doing this is if I add $controller->index() right after the $controller = new $class(); but this will always call the index() method of the controller.
How do I code this method part as such that I can call any method associated with the controller and not just the index().
You can pass a method argument with your controller:
public function controller($controller, $method)
and then call it on your newly created object:
$controller->$method()
However,
it seems you are trying to reinvent the wheel. The part where you verify if a files exists, include it and instantiate the class, is called autoloading.
The code could look like this:
public function controller($controller, $method)
{
$instance = new $controller();
return $instance->$method();
}
While the autoloading part makes use of spl_autoload_register() to manage finding and including files.
The spl_autoload_register() function registers any number of autoloaders, enabling for classes and interfaces to be automatically loaded if they are currently not defined.
So you can use the code you already have, and abstract it from the action of instantiating the class:
spl_autoload_register(function autoloader($controller) {
$file = 'controller/' . $controller . '.php';
if (file_exists($file)) { require($file); }
});
I am trying to write unit test for my application. which as logging the information functionality.
To start with i have service called LogInfo, this how my class look like
use Zend\Log\Logger;
class LogInfo {
$logger = new Logger;
return $logger;
}
I have another class which will process data. which is below.
class Processor
{
public $log;
public function processData($file)
{
$this->log = $this->getLoggerObj('data');
$this->log->info("Received File");
}
public function getLoggerObj($logType)
{
return $this->getServiceLocator()->get('Processor\Service\LogInfo')->logger($logType);
}
}
here i am calling service Loginfo and using it and writing information in a file.
now i need to write phpunit for class Processor
below is my unit test cases
class ProcessorTest{
public function setUp() {
$mockLog = $this->getMockBuilder('FileProcessor\Service\LogInfo', array('logger'))->disableOriginalConstructor()->getMock();
$mockLogger = $this->getMockBuilder('Zend\Log\Logger', array('info'))->disableOriginalConstructor()->getMock();
$serviceManager = new ServiceManager();
$serviceManager->setService('FileProcessor\Service\LogInfo', $mockLog);
$serviceManager->setService('Zend\Log\Logger', $mockLogger);
$this->fileProcessor = new Processor();
$this->fileProcessor->setServiceLocator($serviceManager);
}
public function testProcess() {
$data = 'I have data here';
$this->fileProcessor->processData($data);
}
}
I try to run it, i am getting an error "......PHP Fatal error: Call to a member function info() on a non-object in"
i am not sure , how can i mock Zend logger and pass it to class.
Lets check out some of your code first, starting with the actual test class ProcessorTest. This class constructs a new ServiceManager(). This means you are going to have to do this in every test class, which is not efficient (DRY). I would suggest constructing the ServiceMananger like the Zend Framework 2 documentation describes in the headline Bootstrapping your tests. The following code is the method we are interested in.
public static function getServiceManager()
{
return static::$serviceManager;
}
Using this approach makes it possible to obtain the instance of ServiceManager through Bootstrap::getServiceManager(). Lets refactor the test class using this method.
class ProcessorTest
{
protected $serviceManager;
protected $fileProcessor;
public function setUp()
{
$this->serviceManager = Bootstrap::getServiceManager();
$this->serviceManager->setAllowOverride(true);
$fileProcessor = new Processor();
$fileProcessor->setServiceLocator($this->serviceManager);
$this->fileProcessor = $fileProcessor;
}
public function testProcess()
{
$mockLog = $this->getMockBuilder('FileProcessor\Service\LogInfo', array('logger'))
->disableOriginalConstructor()
->getMock();
$mockLogger = $this->getMockBuilder('Zend\Log\Logger', array('info'))
->disableOriginalConstructor()
->getMock();
$serviceManager->setService('FileProcessor\Service\LogInfo', $mockLog);
$serviceManager->setService('Zend\Log\Logger', $mockLogger);
$data = 'I have data here';
$this->fileProcessor->processData($data);
}
}
This method also makes it possible to change expectations on the mock objects per test function. The Processor instance is constructed in ProcessorTest::setUp() which should be possible in this case.
Any way this does not solve your problem yet. I can see Processor::getLoggerObj() asks the ServiceManager for the service 'Processor\Service\LogInfo' but your test class does not set this instance anywhere. Make sure you set this service in your test class like the following example.
$this->serviceManager->setService('Processor\Service\LogInfo', $processor);