Reputation: 19366
Why is it not possible to hide the constructor in a child class?
I am getting the following Exception:
Fatal error: Access level to CIS\Logger\WPLogger::__construct() must be public (as in class Katzgrau\KLogger\Logger) in /builds/r2o/website/wp-content/mu-plugins/toolsets/lib/cis-logger/src/WPLogger.php on line 12
Code of superclass (from external library):
public function __construct($logDirectory, $logLevelThreshold = LogLevel::DEBUG, array $options = array()) { // ...
}
Code of WPLogger.php (subclass):
private function __construct(string $logDirectory, string $logLevelThreshold = LogLevel::DEBUG, array $options = array()) {
parent::__construct($logDirectory, $logLevelThreshold, $options);
// ... some actions
}
}
public static function getInstance(string $logFileRelative = self::DEFAULT_LOG_NAME, string $logLevelThreshold = LogLevel::DEBUG, array $options = array()) {
// ...
}
I do not want this special subclass to be instantiated with new
. I want it to be created statically with getInstance()
instead for some reasons. How can I achieve this?
Upvotes: 4
Views: 437
Reputation: 6456
You can expand methods visibility with the usage of inheritance. The following example is correct
class A
{
private function __construct()
{}
}
class B extends A
{
public function __construct()
{}
}
But you aren't able to restrict methods visibility in children's classes. The following example is incorrect
class A
{
public function __construct()
{}
}
class B extends A
{
private function __construct()
{}
}
In your case, you try to hide constructor because you want to make the singleton. Singleton is bad practice in many cases. You should use other patterns for object builing: Static Factory, Service Locator, Dependency Injection Container
For example, you can archive the necessary result with the simple modification of static factory:
class LoggerFactory
{
private static $instance = null;
public static function build()
{
if (is_null(static::$instance)) {
static::$instance = // making of the logger
}
return static:$instance;
}
}
Upvotes: 3
Reputation: 36924
You cannot make a subclass method ( in this case is a constructor, but same principle applies ) more restrictive than the parent class. Because the client code that will use that type expect to have the same contract/interface of the super class. Otherwise you cannot replace an instance of the super class with an instance of your class transparently, and viceversa.
But, your super class depends on AbstractLogger
which implements from Psr\Log\LoggerInterface
. And since I'm sure that everybody follow the Dependency Inversion Principle, you should be able to say that it's fine for WPLogger
to implements directly from LoggerInterface
. So:
use Psr\Log\LoggerInterface;
final class WPLogger implements LoggerInterface
{
private $wrapperLogger;
private function __construct() {}
public static function getInstance(string $logFileRelative = self::DEFAULT_LOG_NAME, string $logLevelThreshold = LogLevel::DEBUG, array $options = array()) {
// @TODO, implement here your singleton if you want
$self = new self();
$self->wrapperLogger = new \Katzgrau\KLogger\Logger(...);
return $self;
}
// implement the rest of the interface, delegating to wrapperLogger
}
Upvotes: 1
Reputation: 31614
There really isn't any way to force a child class to accept a lower level of visibility than the parent function you're overwriting.
What you probably want here is a wrapper class that emulates the parent but does not extend
it
class Singleton {
/** @var \Katzgrau\KLogger\Logger */
protected $logger;
/** @var Singleton */
protected $instance;
private function __construct($someargs) {
$this->logger = new \Katzgrau\KLogger\Logger($someargs);
}
public static function getInstance($someargs) {
if($this->instance instanceof Singleton) return $this->instance;
$this->instance = new self($someargs);
return $this->instance;
}
/** Magic method to pass along calls to the other class */
function __call($method, $args) {
call_user_func_array(array($this->logger, $method), $args);
}
}
Upvotes: 1