php dependency injection in method not working - php

I am trying to get use dependency injection in methods via type hint. its not working for me. calling $container->get(Name::class); works
if (false === file_exists(__DIR__ . '/../vendor/autoload.php')) {
die('Install the composer dependencies');
}
require __DIR__ . '/../vendor/autoload.php';
//(new Dotenv())->bootEnv(dirname(__DIR__) . '/.env');
/* Load external routes file */
require_once dirname(__DIR__) . '/config/routes.php';
$builder = new DI\ContainerBuilder();
$builder->addDefinitions(dirname(__DIR__) . '/config/Definitions.php');
try {
$builder->useAutowiring(true);
$container = $builder->build();
} catch (Exception $e) {
die($e->getMessage());
}
function test(Cache $cache)
{
dd($cache);
}
test();
die;
The Definitions file
<?php
declare(strict_types = 1);
use Psr\Container\ContainerInterface;
require dirname(__DIR__) . '/config/config.php';
return [
Cache::class => DI\create(Cache::class)->constructor(MEMCACHED_SERVERS),
\Twig\Environment::class => function () {
$loader = new \Twig\Loader\FilesystemLoader(dirname(__DIR__) . '/Views/');
$twig = new \Twig\Environment($loader, [
'cache' => dirname(__DIR__) . '/Cache/'
]);
return $twig;
},
];
The error i am getting :
Fatal error: Uncaught ArgumentCountError: Too few arguments to
function test(), 0 passed

That’s because you made the test function require Cache then when you run test() you don’t inject the Cache object here:
function test(Cache $cache)
{
dd($cache);
}
// here you need to pass instance of Cache
test();
I am not familiar with this code base, but you would either need an auto-injection script based on Reflection, or what-have-you, to do that auto-injection or you need to manually pass the Cache object:
test(new Cache);
If the Cache object uses all static methods or class constants, you don't need to create it injectable, you would just use it where it's required as in the script referenced by Cache::class;
One last possibility is that $cache is available in some include in one of those files somewhere, though I don't see that anywhere, but if that is available, you could make a test() a variable function with use():
$test = function() use ($cache)
{
dd($cache);
};
// No injection needed
$test();

Related

PHP - Autoload only needed classes

I have an autoloader that is placed as a php file above all other sub directories in my project.
What it does is it loads all possible classes at once for any specific server request. After further thought I concluded I need to autoload only the required classes.
What do I need to do to avoid loading other classes not needed?
If I need to post the relevant code snippets of the class files in my subdirectories, I can.
<?php
namespace autoloader;
class autoloader
{
private $directoryName;
public function __construct($directoryName)
{
$this->directoryName = $directoryName;
}
public function autoload()
{
foreach (glob("{$this->directoryName}/*.class.php") as $filename)
{
include_once $filename;
}
foreach (glob("{$this->directoryName}/*.php") as $filename)
{
include_once $filename;
}
}
}
# nullify any existing autoloads
spl_autoload_register(null, false);
# instantiate the autoloader object
$classes = [
new autoloader('request'),
new autoloader('config'),
new autoloader('controllers'),
new autoloader('models'),
new autoloader('data')
];
# register the loader functions
foreach ($classes as $class)
spl_autoload_register(array($class, 'autoload'));
All registered autoloader functions will be called when you try to instantiate a new class or until it finally loads the class or throws an error. The way you have it now, you're registering the same autoloader function again and again for each directory, and file.
What you'd want to do is something along the lines of this.
namespace autoloader;
class autoloader
{
public function __construct()
{
spl_autoload_register([$this, 'autoload']);
}
public function autoload($classname)
{
if (! file_exists("{$classname}.class.php")) {
return;
}
include_once "{$classname}.class.php";
}
}
new autoloader();
Every autoloader function gets the class FQCN passed into it, and from there you'll have to parse it and figure out if you can load the file where that class exists. For instance, if I do the following.
use Some\Awesome\ClassFile;
$class = new ClassFile();
The autoloader we've registered will get the string Some\Awesome\ClassFile passed in as an argument, which we can then parse and see if we have a file for that class, if we don't we return out of the function and let the next registered autoloader function try and find the class.
You can read more about autoloaders in the documentation, I also wrote a blog post about it like 2 months ago that might interest you.
I had to refactor the code and remove the unnecessary load all functionality that I mistakenly thought would lazy load my classes on request.
Here is what I came up with:
Entry Point
<?php
require_once 'enums.php';
require_once 'api.class.php';
spl_autoload('AutoLoader\AutoLoader');
use App\API;
class MyAPI extends API
{
public function __construct($request){
parent::__construct($request);
}
}
$api = new MyAPI($_REQUEST);
echo $api->processRequest();
AutoLoader Implementation (located under subdirectory Autoloader/Autoloader.php and inaccessible via browser by using .htaccess)
<?php
namespace Autoloader;
spl_autoload_register("AutoLoader\AutoLoader::ClassLoader");
spl_autoload_register("AutoLoader\AutoLoader::RequestLoader");
class Autoloader
{
public static function ClassLoader(String $fileName)
{
foreach ([".Class.php", ".php"] as $extension)
if (file_exists($fileName.$extension))
include $fileName.$extension;
}
public static function RequestLoader()
{
self::ClassLoader('Request');
}
}
Snippet for processRequest() (located in api.class.php - my request router)
public function processRequest()
{
$id1 = $this->requestObj->id1;
$id2 = $this->requestObj->id2;
$endpoint1 = $this->requestObj->endpoint1;
$endpoint2 = $this->requestObj->endpoint2;
$goto = $this->requestObj->goto;
$isDestination = in_array($id1, ['first', 'prev', 'next', 'last']);
$numSetEndpoints = (int)isset($endpoint1) + (int)isset($endpoint2);
switch($numSetEndpoints)
{
case 0:
if ($isDestination)
return json_decode($this->_response("No Endpoint: ", $endpoint1));
return json_decode($this->_response("No Endpoint: " . $endpoint2 ?? $endpoint));
case 1:
$className = $endpoint1.'Controller';
break;
case 2:
$className = $endpoint2.'Controller';
break;
}
$class = "\\Controllers\\$className";
if (class_exists($class))
{
$method = strtolower($this->method);
if (method_exists($class, $method))
{
$response = (new $class($this->requestObj))->{$method}();
if ($response['Succeeded'] == false)
{
return $response['Result'];
}
else if ($response['Succeeded'] == true)
{
header("Content-Type: application/json");
return $this->_response($response);
}
else if ($response['Result'])
{
header("Content-Type: text/html");
return $this->_response($response);
}
}
}
}

Oriented Object PHP autoloader issue

I encounterd a little problem with my classes : they simply do not load through my autoloader.
I get this message error :
Warning:
require(C:\wamp64\www\blog\appAutoloader.php/Table/PostsTable.php):
failed to open stream: No such file or directory in
C:\wamp64\www\blog\app\Autoloader.php on line 23
Fatal error: require(): Failed opening required
'C:\wamp64\www\blog\appAutoloader.php/Table/PostsTable.php'
(include_path='.;C:\php\pear') in
C:\wamp64\www\blog\app\Autoloader.php on line 23
Factory class :
use Core\config;
use Core\Database\MysqlDatabase;
class App {
public $title = "My super site";
private $db_instance;
private static $_instance;
public static function getInstance()
{
if (is_null(self::$_instance))
{
self::$_instance = new App();
}
return self::$_instance;
}
public static function load()
{
session_start();
require ROOT . '/app/Autoloader.php';
App\Autoloader::register();
require ROOT .'/core/Autoloader.php';
Core\Autoloader::register();
}
public function getTable($name)
{
$class_name = '\\App\\Table\\' . ucfirst($name) .'Table';
return new $class_name($this->getDb());
}
public function getDb()
{
$config = Config::getInstance(ROOT . '/config/config.php');
if (is_null($this->db_instance)) {
$this->db_instance = new MysqlDatabase($config->get('db_name'), $config->get('db_user'), $config->get('db_pass'), $config->get('db_host'));
}
return $this->db_instance;
}
}
Namespace App autoloader class :
<?php
namespace App;
class Autoloader {
static function register()
{
spl_autoload_register(array(__CLASS__, 'autoload')); // __CLASS__ load the current class
}
static function autoload($class)
{
if (strpos($class, __NAMESPACE__ .'\\') === 0) {
$class = str_replace(__NAMESPACE__ . '\\', '', $class); // _NAMESPACE_ load the current name_space
$class = str_replace('\\', '/', $class);
require __DIR__ . 'Autoloader.php/' . $class . '.php'; // __DIR__ = the parent folder. Here "app"
}
}
}
Namespace Core autoloader class :
<?php
namespace Core;
class Autoloader {
static function register()
{
spl_autoload_register(array(__CLASS__, 'autoload')); // __CLASS__ load the current class
}
static function autoload($class)
{
if (strpos($class, __NAMESPACE__ .'\\') === 0) {
$class = str_replace(__NAMESPACE__ . '\\', '', $class); // _NAMESPACE_ load the current name_space
$class = str_replace('\\', '/', $class);
require __DIR__ . 'Autoloader.php/' . $class . '.php'; // __DIR__ = the parent folder. Here "app"
}
}
}
Empty PostTable
namespace App\Table;
use Core\Table\Table;
class PostsTable extends Table
{
}
Index page :
define('ROOT', dirname(__DIR__));
require ROOT . '/app/App.php';
App::load();
$app = App::getInstance();
$posts = $app->getTable('Posts');
var_dump($posts->all());
How to make it works please?
AS I said in the comments check this path
require(C:\wamp64\www\blog\appAutoloader.php/Table/PostsTable.php)
Doesn't look right to me
require(C:\wamp64\www\blog\ [appAutoloader.php] /Table/PostsTable.php)
What's that bit doing there....
Also namespace of App is not app for the folder its App because this may work on Windows but you will find it does not work on Linux. Because Linux paths are case sensitive, and windows are not.
Further this makes little to no sense
require __DIR__ . 'Autoloader.php/' . $class . '.php'; // __DIR__ = the parent folder. Here "app"
Require 2 files? Paths don't work that way, not that I am aware of at least.
On top of that your implementation ignores the _ Typically underlines will be part of the class name but are replaced by directory, this allows a shorter namespace. So for example instead of having a namespace like this
Namespace \APP\Table;
class PostsTable ..
You could have a class in the same place Like so
Namespace \APP;
class Table_PostsTable ..
With a shorter namespace but still located in the App/Table/PostsTable.php file. However, that's just how I read the spec for PSR autoloaders.
PRO TIP
Take this path C:\wamp64\www\blog\appAutoloader.php/Table/PostsTable.php open the file browser on you desktop and see if it pulls up the file by pasting it into the navigation bar. It wont, but you can be sure your path is wrong by eliminating the code.

Load External Libraries in Symfony 3

I have Service directory in my symfony application like below
src/SwipeBundle/Service
Inside of this directory I have class called.
RSA.php
This class has
namespace SwipeBundle\Service;
require_once __DIR__ . 'RSA/Crypt/RSA.php';
require_once __DIR__ . 'RSA/File/X509.php';
class RSA
{
public function privateKey() {
require_once __DIR__ . 'RSA/Certificates/private.txt';
}
public function certificates() {
return require_once __DIR__ . 'RSA/Certificates/pinew.cer';
}
}
and the files directory are
src/SwipeBundle/Service/RSA/Certificates/pinew.cer
src/SwipeBundle/Service/RSA/Certificates/private.txt
src/SwipeBundle/Service/RSA/Crypt/RSA.php
src/SwipeBundle/Service/RSA/File/X509.php
I want to load the classes of this service class to my Controller, like so.
Controller
use SwipeBundle\Service;
class BillsPayController extends Controller
{
public function indexAction() {
$rsa = new \Crypt_RSA();
$hash = new \Crypt_Hash('sha1');
$x509 = new \File_X509();
$privatekey = file_get_contents(RSA()->privateKey());
$x509->loadX509(file_get_contents(RSA()->certificates()));
}
}
I tried also using this one.
use SwipeBundle\Service\RSA;
class BillsPayController extends Controller
{
public function indexAction() {
$a = new RSA();
$a->privateKey();
}
}
Error I encountered.
Attempt result 1: Attempted to load class "Crypt_RSA" from the global namespace.
Did you forget a "use" statement?
Attempt result 2: Compile Error: main(): Failed opening required '/Users/jaysonlacson/Sites/contactless/src/SwipeBundle/ServiceRSA/Crypt/RSA.php' (include_path='.:')
I think you are missing a forward slash like this:
require_once __DIR__ . '/RSA/Crypt/RSA.php';
require_once __DIR__ . '/RSA/File/X509.php';
I'm just guessing, but can you try that?

phpunit test a mapper with silex php

I would like test my class LocationMapper whose the aim is make CRUD operations:
<?php
class LocationMapper {
/*
* Database connection.
*/
var $db = NULL;
/*
* Constructor function. Loads model from database if the id is known.
*
* #param $db
* Database connection
*/
function __construct($app) {
$this->db = $app['db'];
}
/**
* Load data from the db for an existing user.
*/
function read($app, $id) {
$statement = $this->db->prepare('SELECT * FROM location WHERE id = :id');
$statement->execute(array(':id' => $id));
$data = $statement->fetch(PDO::FETCH_ASSOC);
$comments = $app['CommentMapper']->loadAllByLocation($id);
$location = new Location($data['latitude'], $data['longitude'], $data['address'], $data['name'], $data['checked'], $comments);
$location->set('id', $data['id']);
return $location;
}
...
}
And in my index.php, I create my database connection and my mapper like this :
require_once __DIR__ . '/../config.php';
// Add the database connection as a shared service to the container.
$db_string = 'mysql:host=' . $app['db.hostname'] .';dbname=' . $app['db.dbname'];
$app['db'] = $app->share(function ($app) use ($db_string) {
try {
return new PDO($db_string, $app['db.username'], $app['db.password']);
}
catch (PDOException $e) {
$app->abort(500, 'Unable to connect to database. Check your configuration');
}
});
// Initialize mappers
$app['LocationMapper'] = $app->share(function () use ($app) {
return new LocationMapper($app);
});
How test the class LocationMapper with a real database ?
I tested this :
class LocationMapperTest extends PHPUnit_Framework_TestCase
{
protected $locationMapper;
protected $app;
public function setUp() {
$app = new Silex\Application();
// Add the database connection as a shared service to the container.
$db_string = 'mysql:host=' . $app['db.hostname'] .';dbname=' . $app['db.dbname'];
$app['db'] = $app->share(function ($app) use ($db_string) {
try {
return new PDO($db_string, $app['db.username'], $app['db.password']);
}
catch (PDOException $e) {
$app->abort(500, 'Unable to connect to database. Check your configuration');
}
});
$locationMapper = new LocationMapper();
}
public function createTest() {
$this->assertEquals(0, 0);
}
}
but I have an error PHP Fatal error: Class 'Silex\Application' not found
I guess you've installed Silex using Composer, so you are including the Composer autoload at the very beginning of your application (probably inside your config.php) to automatically include all needed files.
The problem is that PHPUnit is not using your config file, so it's not autoloading the classes: PHPUnit won't use an autoload file unless you told it to do so.
To tell PHPUnit to load your autoload file you have two options:
The first one is to use the bootstrap switch while executing the tests on the command line. That way, PHPUnit will include the bootstrap file at the beginning of the tests execution. If you pass it your autoload file, it will autoload your classes.
The second options is to use a configuration file, where you specify your bootstrap file.
Apart from that, a few observations about your code. Your unit tests shouldn't use your real database for testing. That way, your tests will modify the real database, making the tests unrepeteable (you'd have to re-populate the database with data if you delete rows, for example), slow, and, more importantly, you may affect real data.
I'd recommend you to mock your database, or use a database for testing purposes at least.

PHP trying to use autoload function to find PDO class

This has been bugging me for some time now and I can't seem to make sense of it.
My phpinfo reports that PDO is installed and I can connect to my database on my index.php file. But when I try to open a PDO connection on a namespaced class, php is trying to use my autoload function to find PDO.php which won't work.
My class is as follows:
abstract class {
protected $DB;
public function __construct()
{
try {
$this->DB = new PDO("mysql:host=$host;port=$port;dbname=$dbname", $user, $pass);
}
catch(PDOException $e) {
echo $e->getMessage();
}
}
}
And the error is
Warning: require_once((...)/Model/PDO.php): failed to open stream: No such file or directory in /(...)/Autoloader.php
Fatal error: require_once(): Failed opening required 'vendor/Model/PDO.php' (include_path='.:/Applications/MAMP/bin/php/php5.4.4/lib/php') in /(...)/Autoloader.php
As far I as know the autoloader should be called because PHP PDO extension is installed (yes I'm completely sure).
My autoload is as follows:
spl_autoload_register('apiv2Autoload');
/**
* Autoloader
*
* #param string $classname name of class to load
*
* #return boolean
*/
function apiv2Autoload($classname)
{
if (false !== strpos($classname, '.')) {
// this was a filename, don't bother
exit;
}
if (preg_match('/[a-zA-Z]+Controller$/', $classname)) {
include __DIR__ . '/../controllers/' . $classname . '.php';
return true;
} elseif (preg_match('/[a-zA-Z]+Mapper$/', $classname)) {
include __DIR__ . '/../models/' . $classname . '.php';
return true;
} elseif (preg_match('/[a-zA-Z]+Model$/', $classname)) {
include __DIR__ . '/../models/' . $classname . '.php';
return true;
} elseif (preg_match('/[a-zA-Z]+View$/', $classname)) {
include __DIR__ . '/../views/' . $classname . '.php';
return true;
}
}
Any help please?
It's not really an autoload issue. You are attempting to call a class on the root namespace.
By the looks of it, you are in some 'Model' namespace and calling PDO, you must remember that namespaces are relative by default.
What you want is to either call the the absolute path:
\PDO
or at the top of your file say you're going to use PDO like this:
use PDO;

Categories