skip to Main Content

Sorry if this has been asked before but Googling didn’t give it to me.

How do you protect methods (or properties) from only unrelated classes, but have it available for specific, related classes?

class supplier {
    protected person $contactPerson;
}

class unrelated {
    function unrelatedStuff(supplier $supplier) {
        $noneOfMyBusiness = $supplier->contactPerson; // should generate error
    }
}

class order {

    function __construct(readonly public supplier $supplier) {}

    function informAboutDelivery() {
         $contact = $this->supplier->contactPerson; 
         // should work (but doesn't) since to process an order you need the suppliers details
         $contact->mail('It has been delivered');
    }
}

I wrote a trait that can allow access to certain predefined classes to certain methods, whilst disallowing all other access, with a __method magic method and attributes. I can also think of using Reflection. But I feel that’s all overcomplicated and that I am missing something quite obvious here. Many classes are very closely related, others are unrelated. What is the normal way to deal with this? How do you shield information but not from all classes?

2

Answers


  1. Chosen as BEST ANSWER

    Another solution (still not very elegant, but more elegant than magic methods) that would work in quite a few cases, also restricts access to orders only for this particular supplier (which is more strict but that's good in this case), could be:

    class supplier {
        protected person $contactPerson;
    
        function contact(order $caller): person {
            if ($caller->supplier === $this) return $this->contactPerson;
            throw new Exception("only orders to this supplier may access the contact information");
        }        
    }
    

    This returns the supplier only if called from the class orders as such:

    $supplier->contact(caller: $this);
    

    The problem that I have with it, is that you will have to copy the code every time you want to use it, it's not a structural solution.

    So I moved on to a more structural solution. This is the best I found (could also be done with implements instead of a trait):

    trait testRelatedClass {
        function returnPrivatePropertyIfAllowed(object $caller, string $propertyName, string $storedObjectPropertyName) {
            if ($caller->$storedObjectPropertyName === $this) return $this->$propertyName;
            throw new Exception("property $propertyName is only accessible to objects who have this instance of {$this::class} stored as a property $storedObjectPropertyName");
        }  
    }
    
    class supplier {
        protected person $contactPerson;
    
        use testRelatedClass ;
    
        function contact(order $order): person {
            return $this->returnPrivatePropertyIfAllowed($order, 'contactPerson', 'supplier');
        }        
    }
    

    Which is a bit complicated, but efficient. This hack wouldn't work:

    class unrelated {
        public function unrelatedStuff(supplier $supplier){
            $this->supplier = $supplier;
            return $supplier->contact($this); // error because $this is not of class order
        }
    }
    

  2. What about using a magic method with a backtrace?

    
    class supplier {
    
        private $_contact;
    
        //DEFINE THE CLASSES THAT ARE ALLOWED TO ACCESS PROPERTIES
        private $_allowedClasses = [
            'contact' => [
                order::class,
                //Another::class
            ],
            'otherProperty' => [
                //Another::class
            ]
        ];
    
        public function __get($name){
            
            //DEFINE THE PREFIXED NAME
            $prefixed = '_'.$name;
    
            //CHECK IF THE ATTRIBUTE EXISTS
            if(isset($this->$prefixed){
    
                //GET THE BACKTRACE EFFICIENTLY
                $trace = debug_backtrace(2);
    
                //CHECK IF THE CALLING CLASS IS ALLOWED
                if(isset($this->_allowedClasses[$name] && isset($trace[1]['class']) && !in_array($trace[1]['class'])){
                    throw new Exception("{$trace[1]['class]} is not allowed to access this");
                }
    
                //SEND BACK THE VALUE
                return $this->$prefixed;
            }
        }
    }
    
    class unrelated {
        public function unrelatedStuff(supplier $supplier){
            return $supplier->contact; //THROWS AN ERROR BECAUSE THIS CLASS CANT CALL THE contact PROPERTY
        }
    }
    
    class order {
    
        protected $supplier;
    
        public function __construct(supplier $supplier){
    
            //SET SUPPLIER
            $this->supplier = $supplier;
        }
        
        public function informAboutDelivery(){
            //GET THE CONTACT (IF THIS CLASS IS NOT ALLOWED AN ERROR WILL BE THROWN)
            $contact = $this->supplier->contact;
    
            //SEND YOUR EMAIL
            $contact->mail("Some message");
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search