2

I have a rather large class XYZ which has a different behavior with different arguments given. Every argument shall have a helptext.

Since the class XYZ is very big, I want to encapsulate all argument-functions into an argumentManager class, and all help related functions into an helpManager class. This is possible since they have functions which are independent from XYZ. The result is like this:

<?php

class ArgumentManager {

    // DO NOT CALL
    public function addArgument($option, $argumentCount) {
        ...
    }

    // Other functions MAY be called by the user
    public function isArgumentInList($option) {
        ...
    }

    ...

}

class HelpManager {

    // DO NOT CALL
    public function addHelpEntry($option, $helptext) {
        ...
    }

    // Other functions MAY be called by the user
    public function printHelpPage() {
        ...
    }

    ...

}

class XYZ {

    private $helpManager;
    private $argumentManager;

    // The user may call everything in there, EXCEPT HelpManager::addHelpEntry, since it must be a created pair by XYZ::addArgumentWithHelp
    public function getHelpManager() {
        return $this->helpManager;
    }

    // The user may call everything in there, EXCEPT ArgumentManager::addArgument, since it must be a created pair by XYZ::addArgumentWithHelp
    public function getArgumentManager() {
        return $this->argumentManager;
    }

    // Create classes which contain independent functionalities
    public function __construct() {
        $this->helpManager = new HelpManager();
        $this->argumentManager = new ArgumentManager();
    }

    // Intended usage for creating an argument with help (they should always be a couple)
    public function addArgumentWithHelp($option, $helptext, $argumentCount) {
        $this->argumentManager->addArgument($option, $argumentCount);
        $this->helpManager->addHelpEntry($option, $helptext);
    }

    // Many other functions of the big class XYZ
    .....

}

The class XYZ is now much smaller.

An argument with helptext can be added by calling $XYZ->addArgumentWithHelp() .

Help related functions related functions can be called e.g. via $XYZ->getHelpManager()->printHelpPage() . The same goes for Argument related functions.

The problem is that I do not want that $XYZ->getHelpManager()->addHelpEntry() or $XYZ->getArgumentManager->addArgument() are called by anybody else except XYZ , since I want to enforce that both, the argumentManager and the helpManager have their informations about the option.

Daniel Marschall
  • 3,739
  • 2
  • 28
  • 67
  • 1
    It's a little unclear to me what you want the outcome to be. Are you saying you want to restrict calling of argumentManager/helpManager to XYZ, or that you want to hide argumentManager/helpManager from outside the class but still allow the addArgument method? Or something else? – kellanburket Feb 14 '15 at 00:50
  • The functions inside $argumentManager and $helpManager may be called, so that the user can work with these. The only functions which shall not be called, are the ones which I marked with "DO NOT CALL" . They shall only be called by the class XYZ. In other programming languages like Delphi, I would set the visibility to "unit only" (unit = script file) – Daniel Marschall Feb 14 '15 at 01:00

6 Answers6

1

In your initial question you asked:

Since the class XYZ is very big, I want to encapsulate all argument-functions into an argumentManager class, and all help related functions into an helpManager class.

So it seems that what you really need to achieve can be done just using traits without any additional classes at all. Here is how I would structure it, without including all of the code (which you can cut and paste from your original question):

<?php

trait ArgumentManagerTrait {

    // DO NOT CALL
    protected function addArgument($option, $argumentCount) {
        ...
    }

    // Other functions MAY be called by the user
    public function isArgumentInList($option) {
        ...
    }

    ...

}

trait HelpManagerTrait {

    // DO NOT CALL
    protected function addHelpEntry($option, $helptext) {
        ...
    }

    // Other functions MAY be called by the user
    public function printHelpPage() {
        ...
    }

    ...

}

class XYZ {

    use HelpManagerTrait, ArgumentManagerTrait;

    // Intended usage for creating an argument with help (they should always be a couple)
    public function addArgumentWithHelp($option, $helptext, $argumentCount) {
        $this->addArgument($option, $argumentCount);
        $this->addHelpEntry($option, $helptext);
    }

    // Many other functions of the big class XYZ
    .....

}

In this solution you do away with the need for any additional classes or internal objects and just encapsulate everything by using traits. It's cleaner and neater and it's the solution I would pick. Because HelpManagerTrait and ArgumentManagerTrait are traits of the parent class then their methods (protected and public) become part of the parent.

delatbabel
  • 3,601
  • 24
  • 29
0

So you need to make the properties private or protected so they are only accessible via XYZ:

class XYZ 
{
    protected $helpManager;
    protected $argumentManager;

    public function __construct(ArgumentManager $argumentManager, HelpManager $helpManager) {
         $this->helpManager = $helpManager;
         $this->argumentManager = $argumentManager
    }

    // CORRECT USAGE
    public function addArgument($option, $helptext) {
        $this->argumentManager->addArgument($option, $argumentCount);
        $this->helpManager->addHelpEntry($option, $helptext);

        return $this;

    }

I feel like your design could be better here but beyond this i cant say because its not clear how these 3 classes really interact. If I had more information id probably have additional recommendations/solutions.

prodigitalson
  • 60,050
  • 10
  • 100
  • 114
  • Having $helpManager and $argumentManager protected does not solve the problem. The user should call functions inside these instances, e.g. `$XYZ->helpManager->printHelpPage()` . The only functions which shall not be called, are the ones which I marked with "DO NOT CALL" . They shall only be called by the class XYZ. In other programming languages like Delphi, I would set the visibility to "unit only" (unit = script file) – Daniel Marschall Feb 14 '15 at 01:02
0

It sounds to me like you're talking about C++ style friendship, in which certain classes have privileged access to private and protected members of a so-called friend class. Unfortunately friendship isn't implemented in PHP. If you're looking for a complicated workaround using namespaces to alter visibility, you can read this post: Nested Or Inner Classes in PHP

Community
  • 1
  • 1
kellanburket
  • 12,250
  • 3
  • 46
  • 73
0

What you want can be done with traits.

Declare the main parts of argument manager and help manager as traits, with the functions you don't want called from outside as protected.

Declare the real argument manager and help manager classes as using those traits.

Inside XYZ declare a variant of the real argument manager and help manager classes but override the visibility of the protected functions as public. Then from inside XYZ you can type cast from the real class to the variant class and call the methods you need to call on the internal copies.

Sorry for the lack of code examples, I'm typing this on my tablet. If you want some I can provide them later but meanwhile have a read of the php manual section on traits.

delatbabel
  • 3,601
  • 24
  • 29
  • Thank you for this information! I am sorry, but I am not quite understand it. This is the initial scenario (very very simple for demontration): http://pastebin.com/NugSQFUj . I tried it this way, but I am doing it wrong somehow: http://pastebin.com/K1KJq2yJ – Daniel Marschall Feb 14 '15 at 22:29
  • I will give you a couple of alternative answers using traits, you can pick one at your leisure. – delatbabel Feb 16 '15 at 02:04
  • Note that in your second pastebin the code you give will not work. At this point: **$test = (HelpManagerTrait)$test;** you are trying to typecast an object to a trait which cannot be done. Read the manual section in traits in a bit more detail: http://php.net/manual/en/language.oop5.traits.php – delatbabel Feb 16 '15 at 02:37
0

I think I have found a solution/workaround. Using reflection, I force to call addArgument by changing the visibility at runtime, which will only happen at class XYZ. The user can not accidently access these protected methods.

<?php

class ArgumentManager {

    // Will be called by "friend" class XYZ
    protected function addArgument($option, $argumentCount) {
        ...
    }

    // Other functions MAY be called by the user
    public function isArgumentInList($option) {
        ...
    }

    ...

}

class HelpManager {

    // Will be called by "friend" class XYZ
    protected function addHelpEntry($option, $helptext) {
        ...
    }

    // Other functions MAY be called by the user
    public function printHelpPage() {
        ...
    }

    ...

}

class XYZ {

    private $helpManager;
    private $argumentManager;

    public function getHelpManager() {
        return $this->helpManager;
    }

    public function getArgumentManager() {
        return $this->argumentManager;
    }

    // Create classes which contain independent functionalities
    public function __construct() {
        $this->helpManager = new HelpManager();
        $this->argumentManager = new ArgumentManager();
    }

    // Intended usage for creating an argument with help (they should always be a couple)
    public function addArgumentWithHelp($option, $helptext, $argumentCount) {
        $argMethod = new ReflectionMethod($this->argumentManager, 'addArgument');
        $argMethod->setAccessible(true);
        $argMethod->invoke($this->argumentManager, $option, $argumentCount);

        $helpMethod = new ReflectionMethod($this->helpManager, 'addHelpEntry');
        $helpMethod->setAccessible(true);
        $helpMethod->invoke($this->helpManager, $option, $helptext);
    }

    // Many other functions of the big class XYZ
    .....

}
Daniel Marschall
  • 3,739
  • 2
  • 28
  • 67
0

Here is another answer, for which I have to be a bit philosophical. You could declare your classes and traits like this:

<?php

trait ArgumentManagerTrait {

    // DO NOT CALL
    protected function addArgument($option, $argumentCount) {
        ...
    }

    // Other functions MAY be called by the user
    public function isArgumentInList($option) {
        ...
    }

    ...

}

trait HelpManagerTrait {

    // DO NOT CALL
    protected function addHelpEntry($option, $helptext) {
        ...
    }

    // Other functions MAY be called by the user
    public function printHelpPage() {
        ...
    }

    ...

}

class HelpManager { use HelpManagerTrait; }
class ArgumentManager { use ArgumentManagerTrait; }
class HelpManagerInternal { use HelpManagerTrait { addHelpEntry as public; }
class ArgumentManagerInternal { use ArgumentManagerTrait { addArgument as public; }

class XYZ {

    protected $helpManager; // of class HelpManagerInternal
    protected $argumentManager; // of class ArgumentManagerInternal

    public function __construct(ArgumentManager $argumentManager, HelpManager $helpManager) {
         $this->helpManager = $this->helpManagerCastToInternal($helpManager);
         $this->argumentManager = $this->argumentManagerCastToInternal($argumentManager);
    }

    // Intended usage for creating an argument with help (they should always be a couple)
    public function addArgumentWithHelp($option, $helptext, $argumentCount) {
        $this->argumentManager->addArgument($option, $argumentCount);
        $this->helpManager->addHelpEntry($option, $helptext);
    }

    public function getHelpManager() {
        return $this->helpManagerCastToExternal($this->helpManager);
    }

    public function getArgumentManager() {
        return $this->argumentManagerCastToExternal($this->argumentManager);
    }

    // Many other functions of the big class XYZ
    .....

}

You will notice that I have omitted 4 functions here -- helpManagerCastToInternal, helpManagerCastToExternal and the same 2 for argumentManager. How you implement these depends on the internal attribute structure of the argumentManager and helpManager traits/classes which you haven't given in your question.

What I would do is to have both classes descend from something like Laravel\Fluent or Symfony\Component\HttpFoundation\ParameterBag. Those are classes in frameworks that you can pull in using composer and give you some basic functionality such as internal storage of attributes in a logical fashion with setters/getters, etc. Your cast function can then just fetch all of the attributes of one object, create a new object of the other type, and then push the attributes from the first object into the second object -- the castToInternal and castToExternal basically doing each others' jobs but in reverse. Note that this is really an object copy and not an object cast so if your other functions within helpManager and argumentManager mess with the internal attributes you need to copy them back at some point, which may not be what you want.

Alternatively you could use reflection methods as suggested here: How to Cast Objects in PHP -- that would work regardless of your internal attribute structure.

It really depends on how you want to structure and use the internal attributes of your classes (hence the bit about being philosophical).

Community
  • 1
  • 1
delatbabel
  • 3,601
  • 24
  • 29