I have an helper class with some static functions. All the functions in the class require a ‘heavy’ initialization function to run once (as if it were a constructor).
Is there a good practice for achieving this?
The only thing I thought of was calling an init function, and breaking its flow if it has already run once (using a static $initialized var). The problem is that I need to call it on every one of the class’s functions.
Sounds like you'd be better served by a singleton rather than a bunch of static methods
class Singleton
{
/**
*
* #var Singleton
*/
private static $instance;
private function __construct()
{
// Your "heavy" initialization stuff here
}
public static function getInstance()
{
if ( is_null( self::$instance ) )
{
self::$instance = new self();
}
return self::$instance;
}
public function someMethod1()
{
// whatever
}
public function someMethod2()
{
// whatever
}
}
And then, in usage
// As opposed to this
Singleton::someMethod1();
// You'd do this
Singleton::getInstance()->someMethod1();
// file Foo.php
class Foo
{
static function init() { /* ... */ }
}
Foo::init();
This way, the initialization happens when the class file is included. You can make sure this only happens when necessary (and only once) by using autoloading.
Actually, I use a public static method __init__() on my static classes that require initialization (or at least need to execute some code). Then, in my autoloader, when it loads a class it checks is_callable($class, '__init__'). If it is, it calls that method. Quick, simple and effective...
NOTE: This is exactly what OP said they did. (But didn't show code for.) I show the details here, so that you can compare it to the accepted answer. My point is that OP's original instinct was, IMHO, better than the answer he accepted.
Given how highly upvoted the accepted answer is, I'd like to point out the "naive" answer to one-time initialization of static methods, is hardly more code than that implementation of Singleton -- and has an essential advantage.
final class MyClass {
public static function someMethod1() {
MyClass::init();
// whatever
}
public static function someMethod2() {
MyClass::init();
// whatever
}
private static $didInit = false;
private static function init() {
if (!self::$didInit) {
self::$didInit = true;
// one-time init code.
}
}
// private, so can't create an instance.
private function __construct() {
// Nothing to do - there are no instances.
}
}
The advantage of this approach, is that you get to call with the straightforward static function syntax:
MyClass::someMethod1();
Contrast it to the calls required by the accepted answer:
MyClass::getInstance->someMethod1();
As a general principle, it is best to pay the coding price once, when you code a class, to keep callers simpler.
If you are NOT using PHP 7.4's opcode.cache, then use Victor Nicollet's answer. Simple. No extra coding required. No "advanced" coding to understand. (I recommend including FrancescoMM's comment, to make sure "init" will never execute twice.) See Szczepan's explanation of why Victor's technique won't work with opcode.cache.
If you ARE using opcode.cache, then AFAIK my answer is as clean as you can get. The cost is simply adding the line MyClass::init(); at start of every public method. NOTE: If you want public properties, code them as a get / set pair of methods, so that you have a place to add that init call.
(Private members do NOT need that init call, as they are not reachable from the outside - so some public method has already been called, by the time execution reaches the private member.)
There is a way to call the init() method once and forbid it's usage, you can turn the function into private initializer and ivoke it after class declaration like this:
class Example {
private static function init() {
// do whatever needed for class initialization
}
}
(static function () {
static::init();
})->bindTo(null, Example::class)();
I am posting this as an answer because this is very important as of PHP 7.4.
The opcache.preload mechanism of PHP 7.4 makes it possible to preload opcodes for classes. If you use it to preload a file that contains a class definition and some side effects, then classes defined in that file will "exist" for all subsequent scripts executed by this FPM server and its workers, but the side effects will not be in effect, and the autoloader will not require the file containing them because the class already "exists". This completely defeats any and all static initialization techniques that rely on executing top-level code in the file that contains the class definition.
If you don't like public static initializer, reflection can be a workaround.
<?php
class LanguageUtility
{
public static function initializeClass($class)
{
try
{
// Get a static method named 'initialize'. If not found,
// ReflectionMethod() will throw a ReflectionException.
$ref = new \ReflectionMethod($class, 'initialize');
// The 'initialize' method is probably 'private'.
// Make it accessible before calling 'invoke'.
// Note that 'setAccessible' is not available
// before PHP version 5.3.2.
$ref->setAccessible(true);
// Execute the 'initialize' method.
$ref->invoke(null);
}
catch (Exception $e)
{
}
}
}
class MyClass
{
private static function initialize()
{
}
}
LanguageUtility::initializeClass('MyClass');
?>
Some tests of assigning static public properties :
settings.json :
{
"HOST": "website.com",
"NB_FOR_PAGINA": 8,
"DEF_ARR_SIZES": {
"min": 600,
"max": 1200
},
"TOKEN_TIME": 3600,
"WEBSITE_TITLE": "My website title"
}
now we want to add settings public static properties to our class
class test {
/** prepare an array to store datas */
public static $datas = array();
/**
* test::init();
*/
public static function init(){
// get json file to init.
$get_json_settings =
file_get_contents(dirname(__DIR__).'/API/settings.json');
$SETTINGS = json_decode($get_json_settings, true);
foreach( $SETTINGS as $key => $value ){
// set public static properties
self::$datas[$key] = $value;
}
}
/**
*
*/
/**
* test::get_static_properties($class_name);
*
* #param {type} $class_name
* #return {log} return all static properties of API object
*/
public static function get_static_properties($class_name) {
$class = new ReflectionClass($class_name);
echo '<b>infos Class : '.$class->name.'</b><br>';
$staticMembers = $class->getStaticProperties();
foreach( $staticMembers as $key => $value ){
echo '<pre>';
echo $key. ' -> ';
if( is_array($value) ){
var_export($value);
}
else if( is_bool($value) ){
var_export($value);
}
else{
echo $value;
}
echo '</pre>';
}
// end foreach
}
/**
* END test::get_static_properties();
*/
}
// end class test
ok now we test this code :
// consider we have the class test in API folder
spl_autoload_register(function ($class){
// call path to API folder after
$path_API = dirname(__DIR__).'/API/' . $class . '.php';
if( file_exists($path_API) ) require $path_API;
});
// end SPL auto registrer
// init class test with dynamics static properties
test::init();
test::get_static_properties('test');
var_dump(test::$HOST);
var_dump(test::$datas['HOST']);
this return :
infos Class : test
datas -> array (
'HOST' => 'website.com',
'NB_FOR_PAGINA' => 8,
'DEF_ARR_SIZES' =>
array (
'min' => 600,
'max' => 1200,
),
'TOKEN_TIME' => 3600,
'WEBSITE_TITLE' => 'My website title'
)
// var_dump(test::$HOST);
Uncaught Error: Access to undeclared static property:
test::$HOST
// var_dump(test::$datas['HOST']);
website.com
Then if we modify the class test like this :
class test {
/** Determine empty public static properties */
public static $HOST;
public static $NB_FOR_PAGINA;
public static $DEF_ARR_SIZES;
public static $TOKEN_TIME;
public static $WEBSITE_TITLE;
/**
* test::init();
*/
public static function init(){
// get json file to init.
$get_json_settings =
file_get_contents(dirname(__DIR__).'/API/settings.json');
$SETTINGS = json_decode($get_json_settings, true);
foreach( $SETTINGS as $key => $value ){
// set public static properties
self::${$key} = $value;
}
}
/**
*
*/
...
}
// end class test
// init class test with dynamics static properties
test::init();
test::get_static_properties('test');
var_dump(test::$HOST);
this return :
infos Class : test
HOST -> website.com
NB_FOR_PAGINA -> 8
DEF_ARR_SIZES -> array (
'min' => 600,
'max' => 1200,
)
TOKEN_TIME -> 3600
WEBSITE_TITLE -> My website title
// var_dump(test::$HOST);
website.com
I actually need to initialize an object with public static properties that I will reuse in many other classes, which I think is supposed to, I don't want to do new api() in every method where I would need, for example to check the host of the site or indicate it. Also I would like to make things more dynamic so that I can add as many settings as I want to my API, without having to declare them in my initialization class.
All other methods I've seen no longer work under php > 7.4
I keep looking for a solution for this problem.
Note - the RFC proposing this is still in the draft state.
class Singleton
{
private static function __static()
{
//...
}
//...
}
proposed for PHP 7.x (see https://wiki.php.net/rfc/static_class_constructor )
Related
Consider having a Class whose method is being called in iteration n times, and the method in itself has a statement that pulls data from a cache system - Redis.
Now if this method is called n times the cache is also hit n times, leading to a continuous fetch and unserializing of the same data n times, which in the case of an interpreter like PHP consumes a good amount of time & CPU.
Passing the cached data to this method could also be a no, as we might instantiate n number of instances of this class.
Hence is there a way where we can avoid hitting the cache multiple times in the context of a Class and/or Object?
Maybe we can somehow use static properties of the Object to hold the value?
First, write a service class that:
Provides getter, which:
Loads required value from cache (based on unique-key),
But also backups the loaded-value,
Finally, returns backupped-value instead of re-loading from cache.
Provides setter which updates both backup and cache.
Then simply use Laravel's feature to inject and/or get instance of said service-class.
Example
<?php
namespace App\Services;
use Illuminate\Support\Facades\Cache;
class MyCacheService
{
protected $backupMap = [];
/**
* Requests an instance of this-class from Laravel.
*
* #return MyCacheService|null
*/
public static function instance()
{
return \Illuminate\Support\Facades\App::make(MyCacheService::class);
}
public function get($key)
{
$backup = & $this->backupMap[$key];
if ( ! isset($backup)) {
$backup = $this->rawGet($key);
}
return $buckup;
}
public function set($key, $value)
{
$this->rawSet($key, $value);
$this->backupMap[$key] = $value;
}
/** Loads from cache */
private function rawGet($key)
{
// ... your normal loading from cache logic goes here,
// but as sub-example:
return Cache::get($key);
}
/** Saves into cache */
private function rawSet($key, $value)
{
// ... your normal saving into cache logic goes here,
// but as sub-example:
Cache::set($key, $value);
}
}
Usage:
use App\Services\MyCacheService;
use Illuminate\Support\Facades\App;
// ...
// Get instance once, like:
$cache = MyCacheService::instance();
// Or like:
/** #var MyCacheService|null $cache */
$cache = App::make(MyCacheService::class);
// Finally, reuse as many times as required, like:
$myValue = $cache->get('my-unique-key');
$myOtherValue = $cache->get('my-other-key');
Note that Laravel stores the class's instance for us, else one could use private static property named $instance, and return that from instance() method.
WARNING: as you may already know, maybe replace rawGet and rawSet's logic.
Also, place MyCacheService.php file (which's source is above) in app/Services folder.
If you are in the opinion that you should not use a service-class (which the other answer explains),
then consider using PHP's $GLOBALS variable to backup the loaded-Cache (instead of adding yet another custom static variable).
Examples
Maybe backup once for all:
In Laravel's AppServiceProvider.php file, do something like:
<?php
namespace App\Providers;
// ...
use Illuminate\Support\Facades\Cache;
class AppServiceProvider extends ServiceProvider
{
// ...
public function boot()
{
$keyList = [
'my-unique-key',
'my-other-key',
];
foreach($keyList as $key) {
$GLOBALS['cache-' . $key] = Cache::get($key);
}
}
}
Finally, use $GLOBALS['cache-my-unique-key'] anywhere required.
Or, backup per class:
<?php
use Illuminate\Support\Facades\Cache;
class YourClass {
private $myData;
public function __construct()
{
$key = 'my-unique-key';
$value = & $GLOBALS['cache-' . $key];
if ( ! isset($value)) {
$value = Cache::get($key);
}
$this->myData = $value;
}
// Finally, use `$this->myData` anywhere in this same class.
}
Note that as you may already know, in both cases we use 'cache-' prefix to not overwrite anything by mistake.
You can use static properties to store the cached data and use access it same.
class CacheClass
{
private static $cachedData;
public function retrieveData()
{
if (!isset(self::$cachedData)) {
// re-assign the value
self::$cachedData = fetchDataFromCache();
}
return self::$cachedData;
}
}
I have only basic PHP knowledge and am reading the book "PHP 5 E-commerce Development.pdf" which code source can be found here: https://github.com/rogeriorps/books_demo.
I am right at the beginning, on the creation of the registry with "objects" such as database handling, authentication and template sending.
I have a problem with the last line of code of this function, in a class that is a singleton and has objects:
public function storeObject( $object, $key )
{
if( strpos( $object, 'database' ) !== false ) {
$object = str_replace( '.database', 'database', $object);
require_once('databaseobjects/' . $object
. '.database.class.php');
} else {
require_once('objects/' . $object . '.class.php');
}
self::$objects[ $key ] = new $object( self::$instance );
}
Well, for the authentication class for instance, the constructor is empty: public
function __construct() { }
So it would require authentication.class.php and then create a new authentification(self::$instance)... On a constructor has no arguments!
How is that possible? What bothers me is the use of the word new, which normally calls the empty constructor, and gives it arguments out of the blue.
Any further explanations about how this all works are welcome as well, thank you :-)
PHP is a quite forgiving language, in that certain language constructs and practices are not as strictly applied as in other programming languages.
PHP does not complain if you provide more parameters than a class method expects, whether that method is a costructor or regular method. See below, which outputs "Hello World!" just fine:
error_reporting(E_ALL);
ini_set('display_errors', TRUE);
ini_set('display_startup_errors', TRUE);
class Foo
{
public function __construct() {}
public function hello($input) { return 'Hello ' . $input . '!'; }
}
$foo = new Foo(123);
echo $foo->hello('World', 'Universe');
I'm still trying to understand what your question is.
While the link to the source code is good, can you be more specific as to what files you are talking about.
Is this the line you are referring too? :
//I will call this class 'Singleton',as I have no idea what it's name is.
class Singleton
{
protected static $instance;
protected static $objects = [];
...
public function storeObject( $object, $key )
{
...
self::$objects[ $key ] = new $object( self::$instance ); //<--- this line
...
}
}
And then you say you have a class like this ( with an empty constructor )
class authenticate{
public function __construct() {} //empty
}
IF I follow that right, the extra argument is ignored in this case. However, consider having another class that can be stored in Singleton
class user{
//instance of singleton
protected $singleton;
public function __construct( $Singleton ) {
$this->singleton = $Singleton;
}
}
So in this case the same class Singlton calls a different class that does accept an instance of Singlton.
This is what I would call a form of Polymorphism.
Polymorphism is the provision of a single interface to entities of different types.
Personally I would prefer they actually have an interface for this. I will try to explain this as best I can. An interface can be thought of like a contract, it guarantees that any class implementing it will expose some public methods, with given arguments, in a specific order.
For example this
interface StorableObjectInterface{
//interfaces do not contain a method body, only the definition
//here I am using type hinting to tell it to only accept instance of Singleton
public function __construct( Singleton $Singleton );
}
So what this does is require that every class that implements this interface requires an instance of singleton as it's constructors first argument. They can still ignore it, but it should be a contractual obligation of being called from Singleton (IMO).
Then your clasess would look like this
class authenticate implements StorableObjectInterface{
public function __construct(Singleton $Singleton) {} // still empty
}
class user implements StorableObjectInterface{
//instance of singlton
protected $singlton;
public function __construct(Singleton $Singleton ) {
$this->singlton = $Singleton;
}
}
And then to lock it all together in Singleton you would check that $Object implements the interface.
public function storeObject( $object, $key )
{
...
if( is_a( $object, StorableObjectInterface::class ){
self::$objects[ $key ] = new $object( self::$instance );
}else{
//throw exception
throw new Exception("Class {$object} must implement interface ".StorableObjectInterface::class);
}
...
}
This is the way I would do it... It wasn't clear if you are just Using someones system, or creating your own.
So you may wonder why go through all this trouble, I'll give you an example.
Say later on you may need something like a path to a config file in authenticate, so you can easily load you credentials etc.
So you look in that class and see this ( we'll forget we know what we know )
class authenticate{
public function __construct() {} // still empty
}
So you figure you can just tack it in the constructor ( say you were using this class somewhere outside of Singlton ). So you change it.
class authenticate{
protected $config;
public function __construct($configFile = null) {
if( $configFile )
$this->config = include $configFile;
}
}
//then you call it for you new code
$z = new authenticate('passwordsAndStuf.php');
This is all fine until Singleton calls that constructor with an instance of itself. Now everything blows up. The main issue is that just looking at authenticate there is no way to tell this is going to happen. So by adding an Interface we are making a contract with Singleton any class implementing this interface will always accept an instance ofSingleton` as the first argument.
Hope that makes sense.
I've been reading / watching a lot of recommended material, most recently this - MVC for advanced PHP developers. One thing that comes up is Singletons are bad, they create dependency between classes, and Dependency Injection is good as it allows for unit testing and decoupling.
That's all well and good until I'm writing my program. Let's take a Product page in a eshop as an example. First of all I have my page:
class Page {
public $html;
public function __construct() {
}
public function createPage() {
// do something to generate the page
}
public function showPage() {
echo $this->html;
}
}
All fine so far, but the page needs a product, so let's pass one in:
class Page {
public $html;
private $product;
public function __construct(Product $product) {
$this->product = $product;
}
public function createPage() {
// do something to generate the page
}
public function showPage() {
echo $this->html;
}
}
I've used dependency injection to avoid making my page class dependent on a product. But what if page had several public variables and whilst debugging I wanted to see what was in those. No problem, I just var_dump() the page instance. It gives me all the variables in page, including the product object, so I also get all the variables in product.
But product doesn't just have all the variables containing all the details of the product instantiated, it also had a database connection to get those product details. So now my var_dump() also has the database object in it as well. Now it's starting to get a bit longer and more difficult to read, even in <pre> tags.
Also a product belongs to one or more categories. For arguments sake let's say it belongs to two categories. They are loaded in the constructor and stored in a class variable containing an array. So now not only do I have all the variables in product and the database connection, but also two instances of the category class. And of course the category information also had to be loaded in from the database, so each category instance also has a database private variable.
So now when I var_dump() my page I have all the page variables, all the product variables, multiples of the category variables in an array, and 3 copies of the database variables (one from the products instance and one from each of the category instances). My output is now huge and difficult to read.
Now how about with singletons? Let's look at my page class using singletons.
class Page {
public $html;
public function __construct() {
}
public function createPage() {
$prodId = Url::getProdId();
$productInfo = Product::instance($prodId)->info();
// do something to generate the page
}
public function showPage() {
echo $this->html;
}
}
And I use similar singletons inside the Product class as well. Now when I var_dump() my Page instance I only get the variables I wanted, those belonging to the page and nothing else.
But of course this has created dependencies between my classes. And in unit testing there's no way to not call the product class, making unit testing difficult.
How can I get all the benefits of dependency injection but still make it easy to debug my classes using var_dump()? How can I avoid storing all these instances as variables in my classes?
I'll try to write about several things here.
About the var_dump():
I'm using Symfony2 as a default framework, and sometimes, var_dump() is the best option for a quick debug. However, it can output so much information, that there is no way you're going to read all of it, right? Like, dumping Symfony's AppKernel.php, or, which is more close to your case, some service with an EntityManager dependency. IMHO, var_dump() is nice when you debugging small bits of code, but large and complex product make var_dump() ineffective. Alternative for me is to use a "real" debugger, integrated with your IDE. With xDebug under PhpStorm I have no real need of var_dump() anymore.
Useful link about "Why?" and "How-to?" is here.
About the DI Container:
Big fan of it. It's simple and makes code more stable; it's common in modern applications. But I agree with you, there is a real problem behind: nested dependencies. This is over-abstraction, and it will add complexity by adding sometimes unnecessary layers.
Masking the pain by using a dependency injection container is making
your application more complex.
If you want to remove DIC from your application, and you actually can do it, then you don't need DIC at all. If you want alternative to DIC, well... Singletons are considered bad practice for not testable code and a huge state space of you application. Service locator to me has no benefits at all. So looks like there is the only way, to learn using DI right.
About your examples:
I see one thing immediately - injecting via construct(). It's cool, but I prefer optional passing dependency to the method that requires it, for example via setters in services config.yml.
class Page
{
public $html;
protected $em;
protected $product;
public function __construct(EntityManager $em) {
$this->em = $em;
}
//I suppose it's not from DB, because in this case EM handles this for you
protected function setProduct(Product $product)
{
$this->product = $product;
}
public function createPage()
{
//$this->product can be used here ONLY when you really need it
// do something to generate the page
}
public function showPage()
{
echo $this->html;
}
}
I think it gives needed flexibility when you need only some objects during execution, and at the given moment you can see inside your class only properties you need.
Conclusion
Excuse me for my broad and somewhat shallow answer. I really think that there is no direct answer to your question, and any solution would be opinion based. I just hope that you might find that DIC is really the best solution with limited downside, as well as integrated debuggers instead of dumping the whole class (constructor, service, etc...).
I exactly know that it's possible to reach result what you wish, and don't use extreme solutions.
I am not sure that my example is good enough for you, but it has: di and it easy to cover by unit test and var_dump will be show exactly what you wish, and i think it encourage SRP.
<?php
class Url
{
public static function getProdId()
{
return 'Category1';
}
}
class Product
{
public static $name = 'Car';
public static function instance($prodId)
{
if ($prodId === 'Category1') {
return new Category1();
}
}
}
class Category1 extends Product
{
public $model = 'DB9';
public function info()
{
return 'Aston Martin DB9 v12';
}
}
class Page
{
public $html;
public function createPage(Product $product)
{
// Here you can do something more to generate the page.
$this->html = $product->info() . PHP_EOL;
}
public function showPage()
{
echo $this->html;
}
}
$page = new Page();
$page->createPage(Product::instance(Url::getProdId()));
$page->showPage();
var_export($page);
Result:
Aston Martin DB9 v12
Page::__set_state(array(
'html' => 'Aston Martin DB9 v12
',
))
Maybe this will help you:
class Potatoe {
public $skin;
protected $meat;
private $roots;
function __construct ( $s, $m, $r ) {
$this->skin = $s;
$this->meat = $m;
$this->roots = $r;
}
}
$Obj = new Potatoe ( 1, 2, 3 );
echo "<pre>\n";
echo "Using get_object_vars:\n";
$vars = get_object_vars ( $Obj );
print_r ( $vars );
echo "\n\nUsing array cast:\n";
$Arr = (array)$Obj;
print_r ( $Arr );
This will returns:
Using get_object_vars:
Array
(
[skin] => 1
)
Using array cast:
Array
(
[skin] => 1
[ * meat] => 2
[ Potatoe roots] => 3
)
See the rest here http://php.net/manual/en/function.get-object-vars.php
The short answer is, yes you can avoid many private variables and using dependency injection. But (and this is a big but) you have to use something like an ServiceContainer or the principle of it.
The short answer:
class A
{
protected $services = array();
public function setService($name, $instance)
{
$this->services[$name] = $instance;
}
public function getService($name)
{
if (array_key_exists($name, $this->services)) {
return $this->services[$name];
}
return null;
}
private function log($message, $logLevel)
{
if (null === $this->getService('logger')) {
// Default behaviour is to log to php error log if $logLevel is critical
if ('critical' === $logLevel) {
error_log($message);
}
return;
}
$this->getService('logger')->log($message, $logLevel);
}
public function actionOne()
{
echo 'Action on was called';
$this->log('Action on was called', 0);
}
}
$a = new A();
// Logs to error log
$a->actionOne();
$a->setService('logger', new Logger());
// using the logger service
$a->actionOne();
With that class, you have just one protected variable and you are able to add any functionality to the class just by adding a service.
A more complexer example with an ServiceContainer can be somthing like that
<?php
/**
* Class ServiceContainer
* Manage our services
*/
class ServiceContainer
{
private $serviceDefinition = array();
private $services = array();
public function addService($name, $class)
{
$this->serviceDefinition[$name] = $class;
}
public function getService($name)
{
if (!array_key_exists($name, $this->services)) {
if (!array_key_exists($name, $this->serviceDefinition)) {
throw new \RuntimeException(
sprintf(
'Unkown service "%s". Known services are %s.',
$name,
implode(', ', array_keys($this->serviceDefinition))
)
);
}
$this->services[$name] = new $this->serviceDefinition[$name];
}
return $this->services[$name];
}
}
/**
* Class Product
* Part of the Model. Nothing too complex
*/
class Product
{
public $id;
public $info;
/**
* Get info
*
* #return mixed
*/
public function getInfo()
{
return $this->info;
}
}
/**
* Class ProductManager
*
*/
class ProductManager
{
public function find($id)
{
$p = new Product();
$p->id = $id;
$p->info = 'Product info of product with id ' . $id;
return $p;
}
}
class UnusedBadService
{
public function _construct()
{
ThisWillProduceAnErrorOnExecution();
}
}
/**
* Class Page
* Handle this request.
*/
class Page
{
protected $container;
/**
* Set container
*
* #param ServiceContainer $container
*
* #return ContainerAware
*/
public function setContainer(ServiceContainer $container)
{
$this->container = $container;
return $this;
}
public function get($name)
{
return $this->container->getService($name);
}
public function createPage($productId)
{
$pm = $this->get('product_manager');
$productInfo = $pm->find($productId)->getInfo();
// do something to generate the page
return sprintf('<html><head></head><body><h1>%s</h1></body></html>', $productInfo);
}
}
$serviceContainer = new ServiceContainer();
// Add some services
$serviceContainer->addService('product_manager', 'ProductManager');
$serviceContainer->addService('unused_bad_service', 'UnusedBadService');
$page = new Page();
$page->setContainer($serviceContainer);
echo $page->createPage(1);
var_dump($page);
You can see, if you look at the var_dump output, that just the services, you called are in the output.
So this is small, fast and sexy ;)
I'm trying to write some abstract MVC classes in PHP using PHPStorm. Some of the overriding class properties I am using for generic class wrappers tend to be better suited to using static methods and properties.
The code works fine, but whenever I use a variable to represent a class name when pointing it at a static variable (one that exists in inherited methods) PHPStorm doesn't know how to handle it and shows it as an error in the IDE. I'm just wondering if there's a better way to do this sort of thing or if it's simply going to be something I have to learn to ignore in the IDE.
Example:
class AbstractModel {
protected static $_dataMapper = null;
protected static $_subs = null;
public function __construct($data=null) {
// constructor omitted with things like a generic class init, etc.
}
// $_subs is a static array who's value is a class name use for
// populating sub-objects as part of a class instance
public function setSubProperty($subKeyName,$value) {
$dataType = get_called_class();
/*************************************
* PHPStorm complains here on $_subs *
*************************************/
if(is_array($dataType::$_subs)
&& array_key_exists($subKeyName,$dataType::$_subs)) {
$setMethod = 'set' . ucfirst($subKeyName);
return $dataType->$setMethod($value);
} else {
return false; // or throw an exception
}
}
}
class SomedataModel extends AbstractModel {
public $other = null;
protected static $_subs = array(
'other' => "OtherModel"
);
public function __construct($data=null) {
parent::__construct($data=null);
foreach(array_keys(self::$_subs) as $_k) {
$setMethod = 'set'.ucfirst($_k);
$this->$setMethod();
}
}
public function setOther($data=null) {
// sanitize and set instance of 'other' as $this->other
}
}
You can easily work around that by using the static keyword instead:
...
public function setSubProperty($subKeyName,$value) {
if (is_array(static::$_subs)
&& array_key_exists($subKeyName, static::$_subs)) {
$setMethod = 'set' . ucfirst($subKeyName);
...
PHPStorm supports it very well. Support of variable string values as classnames resolution of static members and properties is not supported. You might want to open a feature request (if it does not yet exists), however I doubt it's technically feasible because it wouldn't be type-hinting but value-hinting which I think is not supported in Phpstorm.
Writing unit tests for code which is already written is fun sometimes.
I am writing a test case for the following code (an example):
<?php
class mockPrivate {
public static function one($a){
$var = static::_two($a);
return $var;
}
private static function _two($a){
return $a+1;
}
}
?>
The test class is like this:
<?php
require_once 'mockPvt.php';
class mockPrivate_test extends PHPUnit_Framework_TestCase {
public $classMock;
protected function setUp(){
$this->classMock = $this->getMock('mockPrivate', array('_two'));
}
public function test_one(){
$a = 1;
$retVal = 2;
$classmock = $this->classMock;
$classmock::staticExpects($this->once())
->method('_two')
->with($a)
->will($this->returnValue($retVal));
$value = $classmock::one($a);
$this->assertEquals($value, $retVal);
}
}
?>
After running by $ phpunit mockPrivate_test.php I got this error:
PHP Fatal error: Call to private method Mock_mockPrivate_531a1619::_two() from context 'mockPrivate' in /data/www/dev-sumit/tests/example
s/mockPvt.php on line 6
But if I change the
private static function _two()
to
public static function _two() or
protected static function _two()
it works totally fine. Since this is a legacy code I can't change the private to public/protected. So is there any way I can test the function one or Is this a limitation of phpunit?
Another option is to create a class that extends mockPrivate, allowing accessibility to the object you wish to test. Your engineers should be thinking long and hard about why something is private (because that means the class is not easily extensible). Also remember that you can mock the test class if you need to override what it returns.
class Test_MockPrivate extends MockPrivate
{
/**
* Allow public access to normally protected function
*/
public static function _two($a){
return parent::_two($a);
}
}
// Code to force the return value of a now public function
$mock = $this->getMock('Test_MockPrivate', array('_two'));
$mock->expects($this->any())
->method('_two')
->will($this->returnValue('Some Overridden Value');
You can use reflection for changing visibility of methods. You can find more info in
PHP object, how to reference?
Use mock and reflection... (posted this solution, since this is the top google result)
$oMock = $this->getMock("Your_class", array('methodToOverride'));
$oMock->expects( $this->any() )
->method('methodToOverride')
->will( $this->returnValue( true ) );
$oReflection = new ReflectionClass("Your_Class");
$oMethod = $oReflection->getMethod('privateMethodToInvoke');
$oMethod->setAccessible( true );
$oMethod->invoke( $oMock );