Blackbam
Blackbam

Reputation: 19366

How do you make a child constructor private when the parent is public?

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

Answers (3)

Maksym Fedorov
Maksym Fedorov

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

Federkun
Federkun

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

Machavity
Machavity

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

Related Questions