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');
Related
I'm using Slim3, but I'm having an issue registering a dependency. According to the error the constructor I created expects the type of argument 1 to be Slim\Views\Twig.
The problem is I am passing an instance of Slim\Views\Twig - at least I think I am. I've not used Slim in a few months so I might be missing something obvious. Never the less I can't find the issue.
The error I'm getting is:
Catchable fatal error: Argument 1 passed to App\Controllers\RegistrationController::__construct() must be an instance of Slim\Views\Twig, instance of Slim\Container given
controllers/RegistrationController.php
<?php
namespace App\Controllers;
class RegistrationController {
protected $view;
public function __construct(\Slim\Views\Twig $view) {
$this->view = $view;
}
public function register($request, $response, $args) {
// Does some stuff ...
}
}
dependencies.php
<?php
use \App\Controllers\RegistrationController;
$container = $app->getContainer();
// Twig View
$container['view'] = function ($c) {
$settings = $c->get('settings')['renderer'];
$view = new \Slim\Views\Twig($settings['template_path']);
$basePath = rtrim(str_ireplace('index.php', '', $c['request']->getUri()->getBasePath()), '/');
$view->addExtension(new Slim\Views\TwigExtension($c['router'], $basePath));
return $view;
};
// monolog
$container['logger'] = function ($c) {
$settings = $c->get('settings')['logger'];
$logger = new Monolog\Logger($settings['name']);
$logger->pushProcessor(new Monolog\Processor\UidProcessor());
$logger->pushHandler(new Monolog\Handler\StreamHandler($settings['path'], $settings['level']));
return $logger;
};
// sqlite3
$container['db'] = function ($c) {
return new SQLite3(__DIR__ . '/../db/prod.db');
};
// Registration Controller
$container['RegistrationController'] = function($c) {
return new RegistrationController($c->get('view'));
};
route
$app->post('/signup', '\App\Controllers\RegistrationController:register');
Also tried the following:
$app->post('/signup', \App\Controllers\RegistrationController::class . ':register');
Any ideas?
The problem was I was defining the route incorrectly and I completely missed the section on Container Resolution
Here's how it should look:
$app->post('/signup', 'RegistrationController:register');
In my LoadFixture.php, I add reference to all my fixtures like this :
public function load(ObjectManager $manager) {
$user = new user("Dummy");
$this->persist($user);
$this->addReference("user", $user);
}
In my test class I load them like this :
public function setUp() {
if(self::$do_setup){
$this->loadFixtures(array(
"Bundle\\Tests\\Fixtures\\LoadUser"
)) ;
}
}
In my tests I use them like this :
public function testOne() {
$client = $this->createClient($this->getReference("user_a"));
$client->request('GET', '/');
$this->assertStatusCode(200, $client);
self::$do_setup=false;
}
public function testTwo() {
$client = $this->createClient($this->getReference("user_a"));
$client->request('GET', '/home');
$this->assertStatusCode(200, $client);
}
The thing is, technically, I dont need to use setUp() for each test, so I use $do_setup and a if to execute setUp if needed.
But if I dont execute the setUp() in my testTwo, while my fixtures are in my database, $this->getReference("user_a") is giving me an error :
Call to a member function getReferenceRepository() on a non-object
How can I solve that ?
UPDATE
I have found a solution. So I post it here, just in case someone face the same problem as me.
Many thanks to #Damien Flament for his answer, regarding the fact that the TestCase is deleted after each test.
I changed the name of my setUp() method to open(), and my tearDown() method to close().
The first method of the class call the open() method, and now return $this.
The next method is annoted #depends testOne and take a parameter.
With this parameter I can use my references again.
Ex :
// new setUp Metod
public function open() {
if(self::$do_setup){
$this->loadFixtures(array(
"Bundle\\Tests\\Fixtures\\LoadUser"
)) ;
}
}
//new tearDown method
public function close() {
$this->getContainer()->get('doctrine.orm.entity_manager')->getConnection()->close();
}
public function testOne() {
$this->open();
$client = $this->createClient($this->getReference("user_a"));
$client->request('GET', '/');
$this->assertStatusCode(200, $client);
return $this;
}
/**
* #depends testOne
*/
public function testTwo($it) {
$client = $this->createClient($it->getReference("user_a"));
$client->request('GET', '/home');
$this->assertStatusCode(200, $client);
return $it;
}
/**
* #depends testTwo
*/
public function testThree($it) {
$client = $this->createClient($it->getReference("user_a"));
$client->request('GET', '/about');
$this->assertStatusCode(200, $client);
$this->close();
}
I think the TestCase object is deleted and recreated by PHPUnit (I didn't read the PHPUnit source code, but I think it's the more easy way to reset the testing environment for each test).
So your object (probably referenced by a test class object attribute) is probably garbage collected.
To setup fixture once per test class, use the TestCase::setUpBeforeClass() method.
See documention on "Sharing fixtures".
I am I'm wondering why the Container::getInstance() can return a application class.
For example:
I want to make a hash str, I want to know how they work:
app('hash')->make('password');
and I found the source code in laravel :
vendor/laravel/framework/src/Illuminate/Foundation/helpers.php
if (! function_exists('app')) {
/**
* Get the available container instance.
*
* #param string $make
* #param array $parameters
* #return mixed|\Illuminate\Foundation\Application
*/
function app($make = null, $parameters = [])
{
if (is_null($make)) {
return Container::getInstance();
}
return Container::getInstance()->make($make, $parameters);
}
}
I dont know what the Container::getInstance() will return, then I dd(Container::getInstance()) and I know it will can return an application class, but I dont know how they work.
Maybe I'm a little bit late with my answer, but anyway.
Description is current as of Laravel framework version 5.3.24.
Why calling app(), that then calls Container::getInstance() returns object, instance of Application?
For example, why
Route::get('/', function () {
var_dump(app());
});
outputs:
object(Illuminate\Foundation\Application)
...
Because... And here we have to go step by step though it to understand everything.
User initiates a web request. The request is processed by /public/index.php
/public/index.php contains the following:
$app = require_once __DIR__.'/../bootstrap/app.php';
/bootstrap/app.php has the following lines:
$app = new Illuminate\Foundation\Application(
realpath(__DIR__.'/../')
);
When an $app object is instantiated, the Illuminate\Foundation\Application class constructor is called.
/vendor/laravel/framework/src/Illuminate/Foundation/Application.php
class Application extends Container implements ...
{
// ...
public function __construct($basePath = null)
{
// 5. constructor triggers the following method:
$this->registerBaseBindings();
// ...
}
// ...
protected function registerBaseBindings()
{
// 6. which then triggers the following:
static::setInstance($this);
// 7. Important! $this points to class Application here
// and is passed to Container
// ...
}
// ...
}
static::setInstance($this); refers us to class Container, because class Application extends Container
/vendor/laravel/framework/src/Illuminate/Container/Container.php
class Container implements ...
{
// ...
// 11. $instance now contains an object,
// which is an instance of Application class
protected static $instance;
// ...
public static function setInstance(ContainerContract $container = null)
{
// 9. $container = Application here, because it has been passed
// from class Application while calling static::setInstance($this);
// 10. Thus, static::$instance is set to Application here
return static::$instance = $container;
}
// ...
}
Now, suppose, we have written the following lines in our routes file.
/routes/web.php
Route::get('/', function () {
dd(app()); // 13. We a calling an app() helper function
});
14 Calling app() leads us to
/vendor/laravel/framework/src/Illuminate/Foundation/helpers.php
// ...
/** #return mixed|\Illuminate\Foundation\Application */
function app($make = null, $parameters = [])
{
// 15. $make is null, so this is the case
if (is_null($make)) {
// 16. The following static method is called:
return Container::getInstance();
}
// ...
}
// ...
Now we are back in our Container class
/vendor/laravel/framework/src/Illuminate/Container/Container.php
public static function getInstance()
{
// 18. Important!
// To this point static::$instance is NOT null,
// because it has already been set (up to "step 11").
if (is_null(static::$instance)) {
static::$instance = new static; // Thus, we skip this.
}
// 19. static::$instance is returned
// that contains an object,
// which is an instance of Application class
return static::$instance;
}
Some important notes for steps 16-19.
Important note 1!
Static Keyword
Declaring class properties or methods as static makes them accessible
without needing an instantiation of the class.
Important note 2!
static::$instance = new static; is NOT related to calling our app() function in step 13. And was somewhat misleading for me at first...
But just to note, it makes use of Late Static Bindings.
The Application class (Illuminate\Foundation\Application) extends the Container class. This is the core of the framework and allows all the dependency injection magic, and it follows a Sigleton pattern, this means that when you request the Application object (with the app() helper function, or more internally Container::getInstance()) anywhere in your code you get the same global instance.
Without getting to complex: this let's you bind any class into that Container instance:
app()->bind('MyModule', new MyModuleInstace());
So then you can "resolve" that class out of the container with:
app()->make('MyModule);
But remember there are several methods to do this, with different pattern targets, this is just a basic demonstration of the concept.
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.
I have created a logger as a singleton for my PHP application with Zend framework.
Implementation is pretty straight-forward:
class Logger
{
protected static $_logger;
private function __construct()
{
// initialize logger
$writer = new Zend_Log_Writer_Stream(LOG_PATH);
$this->_logger = new Zend_Log($writer);
}
public static function getLogger()
{
if (null === self::$_logger)
{
self::$_logger = new self();
}
return self::$_logger;
}
public static function Log($message, $logType)
{
if ($logType <= LOG_MAX)
{
$logger = self::getLogger();
$logger->_logger->log($message, $logType);
}
}
}
To ad an entry to the log, I just call static method:
Logger::Log('message', Zend_Log::ERR);
Logger works as supposed to, but since I have upgraded my PHP version to 5.4.3 I get an error:
Strict standards: Accessing static property Logger::$_logger as non static in Z:\Software\PHP\EA Game\application\classes\Logger.php on line 28
Line 28 is in function __construct(): $this->_logger = new Zend_Log($writer);
I can always disable E_STRICT, but it is not a preferable option.
I would like to implement Singleton pattern without getting Strict standards warning.
I would be grateful if some could point me in the right direction to implementing Singleton pattern without getting String standards warning.
EDIT:
I used jValdrons advice and replaced $this->_logger to self::$_logger.
I still was getting strict standards warning and I changed Log function to be as follows:
public static function Log($message, $logType)
{
if ($logType <= LOG_MAX)
{
self::getLogger()->log($message, $logType);
}
}
But now I have another problem.
Code does not throw Strict standards warning, but it does not works as supposed to.
I have 2 separate loggers, 1 for application and 1 for crons.
Basically it's just the same code:
2 static variables:
protected static $_logger;
protected static $_cronLogger;
constructor initializes both of them:
private function __construct()
{
// initialize all loggers
$writer = new Zend_Log_Writer_Stream(LOG_PATH);
self::$_logger = new Zend_Log($writer);
$writerCron = new Zend_Log_Writer_Stream(CRON_LOG_PATH);
self::$_cronLogger = new Zend_Log($writerCron);
}
and 2 methods GetCronLogger() and LogCron():
public static function getCronLogger()
{
if (null === self::$_cronLogger)
{
self::$_cronLogger = new self();
}
return self::$_cronLogger;
}
public static function LogCron($message, $logType)
{
if ($logType <= CRON_LOG_MAX)
{
self::getCronLogger()->log($message, $logType);
}
}
But now self::getCronLogger()->log($message, $logType); calls my method Log(), not Zend_log->log() and it will always add records to my main logger, not crons logger.
Am I missing something or calling something in incorrect way?
You're accessing the logger using $this->logger, which is not a static way to access it. Since it's a static variable, you got to use self:: just like you did got getLogger, so:
private function __construct()
{
// initialize logger
$writer = new Zend_Log_Writer_Stream(LOG_PATH);
self::$_logger = new Zend_Log($writer);
}