Strict standards warning: Singleton implementation with PHP - php

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);
}

Related

PHPUnit: Can you exclude 3rd party libraries from having their notices converted to exceptions? EG with convertNoticesToExceptions

I've been running PHPUnit with convertNoticesToExceptions turned on as best practice.
https://phpunit.readthedocs.io/en/9.5/configuration.html#the-convertnoticestoexceptions-attribute
But of course, that's only appropriate for my own code: I'd prefer to have a different setting for (eg) vendor/. It's not as if I'm going to fix all the notices thrown by third party code.
The documentation has no suggestion that the settings can be different for different directories. (I vaguely remember reading about a way of ignoring notices from libraries, but might be wrong.)
I know about using #, but that solves a different problem.
It this just not an option?
For the sake of brevity, let's say that you have two classes in your project:
namespace MyNamespace;
class Foo
{
public function myFunc(): bool
{
$i = [];
$i["1"];
return true;
}
}
namespace MyNamespace;
class Bar
{
public function myFunc(): bool
{
$i = [];
$i["1"];
return true;
}
}
Both functions myFunc will trigger w warning (no clue what would trigger a notice in PHP 8.1), but we don't want to report it for the class Foo.
Your test would then look like this:
namespace MyNamespace\Tests;
use PHPUnit\Framework\TestCase;
use MyNamespace\Foo;
use MyNamespace\Bar;
class MyTest extends TestCase
{
protected function setUp(): void
{
set_error_handler(static function (int $errorNr, string $errorStr, string $errorFile, int $errorLine, array $errorContext = []): bool {
if (str_contains('/home/awons/Projects/test/src/Foo.php', $errorFile) && $errorNr === E_WARNING) {
return true;
}
$phpUnitHandler = new \PHPUnit\Util\ErrorHandler(true, true, true, true);
return $phpUnitHandler->__invoke($errorNr, $errorStr, $errorFile, $errorLine, $errorContext);
});
}
protected function tearDown(): void
{
restore_error_handler();
}
public function testFoo(): void
{
$foo = new Foo();
self::assertTrue($foo->myFunc());
}
public function testBar(): void
{
$bar = new Bar();
self::assertTrue($bar->myFunc());
}
public function testFooAgain(): void
{
$foo = new Foo();
self::assertTrue($foo->myFunc());
}
}
This is not exactly the most performant solution and I would consider it a hack, but it should do the trick if that's what you need.
All you have to do is to make a decision about when you want to trigger the PhpUnit's error handler and when you want to ignore the notice. You can replace /home/awons/Projects/test/src/Foo.php with /vendor/ (or anything more specific to be 100% sure you got the correct path).
Also, I'm not sure how to get the current settings for the error handler. You might need to parse the config file for that or just move everything to a base class and hardcode values there (just keep them in sync with the actual settings).
::Edit::
There is even a way not to instantiate the PhpUnit's handler but just get it from the current context. All we have to do is to register an empty error handler and restore it immediately. Registering a new error handler returns the previous one.
Assuming the base class is in ./tests you can build the following abstract test case:
use PHPUnit\Framework\TestCase;
abstract class BaseTestCase extends TestCase
{
protected function setUp(): void
{
$phpUnitErrorHandler = set_error_handler(function () {
});
restore_error_handler();
set_error_handler(
static function (
int $errorNr,
string $errorStr,
string $errorFile,
int $errorLine,
array $errorContext = []
) use ($phpUnitErrorHandler): bool {
$vendorDir = realpath(__DIR__ . "/../vendor/");
if (str_contains($vendorDir, $errorFile) && $errorNr === E_NOTICE) {
return true;
}
return call_user_func_array(
$phpUnitErrorHandler,
[$errorNr, $errorStr, $errorFile, $errorLine, $errorContext]
);
}
);
}
protected function tearDown(): void
{
restore_error_handler();
}
}

Dependency Injection with Adapter Pattern and Type Hinting

I'm trying to get my head around type hinting in combination with adapters.
The system fetches XML feeds via different services and updates the database with the changes - I am refactoring to help learn design patterns.
Log Interface:
interface LoggerAdapterInterface {
public function debug($string);
public function info($string);
public function error($string);
}
MonoLog Adapter
class MonoLogAdapter implements LoggerAdapterInterface
{
public $logger;
public function __construct(\Monolog\Logger $logger)
{
$this->logger = $logger;
}
public function debug($string)
{
$this->logger->debug($string);
}
public function info($string)
{
$this->logger->info($string);
}
public function error($string)
{
$this->logger->error($string);
}
}
FeedFactory
class FeedFactory
{
public function __construct()
{
}
public static function build(LoggerAdapter $logger, $feedType)
{
// eg, $feedType = 'Xml2u'
$className = 'Feed' . ucfirst($feedType);
// eg, returns FeedXml2u
return new $className($logger);
}
}
Implementation
// get mono logger
$monoLogger = $this->getLogger();
// create adapter and inject monologger
$loggerAdapter = new MonoLogAdapter($monoLogger);
// build feed object
$Feed = FeedFactory::build($loggerAdapter, 'Xml2u');
Error
PHP Catchable fatal error: Argument 1 passed to FeedFactory::build()
must be an instance of LoggerAdapter, instance of MonoLogAdapter
given, called in /src/shell/feedShell.php on line 64 and defined in
/src/Feeds/FeedFactory.php on line 25
So I am using the LoggerAdapter so that I am not tied to one logging platform. The problem is that when I create a new instance of MonoLogger and try to inject it into the factory - PHP type-hinting does not realize that MonoLogger implements LoggerAdapter.
Am I doing something wrong here?
As #Ironcache suggested - use interface as argument in your build method.
public static function build(LoggerAdapterInterface $logger, $feedType)
{
// eg, $feedType = 'Xml2u'
$className = 'Feed' . ucfirst($feedType);
// eg, returns FeedXml2u
return new $className($logger);
}
note: also check namespace

PHPUnit call to undefined method `Mock_x_::method()`

I'm trying to create my first phpunit test and find myself needing to stub a method on an IMailer interface.
interface IMailer
{
public function send($to, $from, $cc, $subject, $body);
public function sent();
}
$mailer = $this->getMockBuilder(
'IMailer',
array('send', 'sent'))->getMock();
$mailer->method('send')->willRreturn(0);
However, I keep getting
PHP Fatal error:
Call to undefined method Mock_Mailer_13fc0a04::method()
in ...Test.php on line 16
a var_dump($mailer); results in
class Mock_IMailer_4c3e02a7#215 (1) {
private $__phpunit_invocationMocker =>
NULL
}
Working with the expect($this->any()) gives a dito error - it seems that the mocked object does not have any mock functionality...
I'm running phpunit 3.7.28, and php 5.5.9, on an ubuntu box.
How come? How can I fix it?
The getMockBuilder function accepts only the className as parameter. The correct way to initialize your mock object methods would be to use setMethods function (see phpunit docs)
$mailer = $this->getMockBuilder('IMailer')
->setMethods(array('send', 'sent'))
->getMock();
Additionally you probably want to have some expects definition also when you use your mock object:
$mailer->expects($this->any())
->method('send')
->willReturn(0);
EDIT
The above holds true for newer phpunit versions. For phpunit 3.7.28 the mock object usage is a bit different (i.e. the expects seems to be mandatory and willReturn is not yet available). For 3.7.28 version you should modify the second part to:
$mailer->expects($this->any())
->method('send')
->will($this->returnValue(0));
I would recommend updating to later phpunit version as it seems to be somewhat difficult to find documentation to this much older releases.
An alternative solution, for anybody that is still using old versions of PHPUnit, but still wants to be able to call method() directly, is to override the default mock object class template.
Copy MockObject/Generator/mocked_class.tpl.dist, and name the copy mocked_class.tpl. Then, just add the code for the method() method to the template:
public function method()
{
$any = new PHPUnit_Framework_MockObject_Matcher_AnyInvokedCount;
$expects = $this->expects($any);
$args = func_get_args();
return call_user_func_array(array($expects, 'method'), $args);
}
This will allow you to call $mock->method() directly. However, you need to still use ->will($this->returnValue(0)) instead of ->willReturn(0). To do that, you need to introduce a custom invocation builder and invocation mocker:
class My_MockObject_Builder_InvocationMocker
extends PHPUnit_Framework_MockObject_Builder_InvocationMocker {
public function willReturn( $value ) {
return $this->will( new PHPUnit_Framework_MockObject_Stub_Return( $value ) );
}
}
class My_MockObject_InvocationMocker
extends PHPUnit_Framework_MockObject_InvocationMocker {
public function expects( PHPUnit_Framework_MockObject_Matcher_Invocation $matcher ) {
return new My_MockObject_Builder_InvocationMocker($this, $matcher);
}
}
And update your template again, to use My_MockObject_InvocationMocker instead of PHPUnit_Framework_MockObject_InvocationMocker.
The full template would then look like this:
{prologue}{class_declaration}
{
protected static $staticInvocationMocker;
protected $invocationMocker;
{clone}{mocked_methods}
public function expects(PHPUnit_Framework_MockObject_Matcher_Invocation $matcher)
{
return $this->__phpunit_getInvocationMocker()->expects($matcher);
}
public function method()
{
$any = new PHPUnit_Framework_MockObject_Matcher_AnyInvokedCount;
$expects = $this->expects($any);
$args = func_get_args();
return call_user_func_array(array($expects, 'method'), $args );
}
public static function staticExpects(PHPUnit_Framework_MockObject_Matcher_Invocation $matcher)
{
return self::__phpunit_getStaticInvocationMocker()->expects($matcher);
}
public function __phpunit_getInvocationMocker()
{
if ($this->invocationMocker === NULL) {
$this->invocationMocker = new My_MockObject_InvocationMocker;
}
return $this->invocationMocker;
}
public static function __phpunit_getStaticInvocationMocker()
{
if (self::$staticInvocationMocker === NULL) {
self::$staticInvocationMocker = new My_MockObject_InvocationMocker;
}
return self::$staticInvocationMocker;
}
public function __phpunit_hasMatchers()
{
return self::__phpunit_getStaticInvocationMocker()->hasMatchers() ||
$this->__phpunit_getInvocationMocker()->hasMatchers();
}
public function __phpunit_verify()
{
self::__phpunit_getStaticInvocationMocker()->verify();
$this->__phpunit_getInvocationMocker()->verify();
}
public function __phpunit_cleanup()
{
self::$staticInvocationMocker = NULL;
$this->invocationMocker = NULL;
}
}{epilogue}

Strict standards non static method

I want to fix this error:
Strict Standards: Non-static method
Gpf_Settings_Regional::getInstance() should not be called statically,
assuming $this from incompatible context on line 39
The code producing it:
$this->addValue(Gpf_Settings_Gpf::REGIONAL_SETTINGS_DECIMAL_SEPARATOR,
Gpf_Settings_Regional::getInstance()->getDecimalSeparator());
I know this is method for PHP 5.3, but I have 5.4 on shared hosting and need to call static on PHP 5.4
Problem:
You are accessing a non-static method statically with strict standards error reporting switched on.
Solutions:
You can update Gpf_Settings_Regional class
change
public function getInstance()
to
public static function getInstance()
If you are unable to change that class you can change error reporting and switch of reporting of strict standards errors, but it would be better to improve the code.
How to eliminate php5 Strict standards errors?
if i see good it's already made it by script
*/
private static $instance;
public static function create(Gpf_Application $application) {
setlocale(LC_ALL, 'en.UTF-8');
self::$instance = $application;
self::$instance->registerRolePrivileges();
self::$instance->initLogger();
self::$instance->addSmartyPluginsDir();
$timezone = Gpf_Settings_Gpf::DEFAULT_TIMEZONE;
try {
$timezone = Gpf_Settings::get(Gpf_Settings_Gpf::TIMEZONE_NAME);
} catch (Gpf_Exception $e) {
Gpf_Log::error('Unable to load timezone: %s - using default one.', $e->getMessage());
}
if(false === #date_default_timezone_set($timezone)) {
Gpf_Log::error('Unable to set timezone %s:', $timezone);
}
}
public function getDefaultLanguage() {
return 'en-US';
}
/**
* #return Gpf_Application
*/
public static function getInstance() {
if(self::$instance === null) {
throw new Gpf_Exception('Application not initialize');
}
return self::$instance;
}
and this is part where problem is
abstract class Gpf_ApplicationSettings extends Gpf_Object {
/**
* #var Gpf_Data_RecordSet
*/
private $recordSet;
const CODE = "code";
const VALUE = "value";
protected function loadSetting() {
$this->addValue("theme", Gpf_Session::getAuthUser()->getTheme());
$this->addValue("date_time_format", 'MM/d/yyyy HH:mm:ss');
$this->addValue("programVersion", Gpf_Application::getInstance()->getVersion());
$this->addValue(Gpf_Settings_Gpf::NOT_FORCE_EMAIL_USERNAMES, Gpf_Settings::get(Gpf_Settings_Gpf::NOT_FORCE_EMAIL_USERNAMES));
$quickLaunchSettings = new Gpf_Desktop_QuickLaunch();
$this->addValue(Gpf_Desktop_QuickLaunch::SHOW_QUICK_LAUNCH, $quickLaunchSettings->getShowQuickLaunch());
$this->addValue(Gpf_Settings_Gpf::REGIONAL_SETTINGS_THOUSANDS_SEPARATOR,
Gpf_Settings_Regional::getinstance()->getThousandsSeparator());
$this->addValue(Gpf_Settings_Gpf::REGIONAL_SETTINGS_DECIMAL_SEPARATOR, Gpf_Settings_Regional::getInstance()->getDecimalSeparator());
$this->addValue(Gpf_Settings_Gpf::REGIONAL_SETTINGS_DATE_FORMAT, Gpf_Settings_Regional::getInstance()->getDateFormat());
$this->addValue(Gpf_Settings_Gpf::REGIONAL_SETTINGS_TIME_FORMAT, Gpf_Settings_Regional::getInstance()->getTimeFormat());
Gpf_Plugins_Engine::extensionPoint('Core.loadSetting', $this);
}

PHP Strict Standards: Creating default object from empty value

I have following setup.
index.php
require_once "common.php";
...
common.php
...
$obj = new MyClass;
require_once "config.php"
...
config.php
...
require_once "settings.php";
...
settings.php
$obj->dostuff = true;
...
When i open index.php i get: Strict Standards: Creating default object from empty value in settings.php on 3
If i put $obj->dostuff = true; inside config.php it does not produce error message.
Can someone explain why i get this error? I am not asking how to fix it just understand why.
EDIT: My bad i had 2 config.php classes for each part of site and i only changed something in one of them leaving old include order in another now it works fine after it all loads in correct order.
It looks a scope issue. In settings.php, the $obj is not accessible. PHP is creating new one from standard class, and giving you a warning. You can confirm it by putting
echo get_class($obj);
in Your settings.php, just after the line that is producing the error. If it echos "StdClass", then that is the case.
Are You sure the $obj is not created within a function/method ?
If $obj is meant to be a system wide globally accessible object, you can you use the singleton pattern to access from anywhere:
class MyClass
{
protected static $_instance;
static function getInstance()
{
if (null === self::$_instance) {
self::$_instance = new self();
}
return self::$_instance;
}
}
You can then create your methods in this class. To get the object itself simply call:
$obj = MyClass::getInstance();
Additionally, if you just want to call one of its methods but theres no need to return anything:
MyClass::getInstance()->objectMethod();
I find this to be a very efficient way to organize integral singleton based system wide operations.
In practice, my project uses this to get configuration from anywhere in the system:
class syConfig
{
protected static $_instance;
private $_config;
static function getInstance()
{
if (null === self::$_instance) {
self::$_instance = new self();
}
return self::$_instance;
}
public function load($xmlString)
{
$xml = simplexml_load_string($xmlString);
$this->_config = $xml;
}
public function getConfig()
{
return $this->_config;
}
}

Categories