Search code examples
phpclassrestriction

PHP: Restrict class method to related classes


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?


Solution

  • 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):

    Separate trait, to be re-used wherever you want:

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

    Which is a bit complicated, but efficient. This is how you use that trait:

    class supplier {
        protected person $contactPerson;
    
        use testRelatedClass ;
    
        function contact(order $order): person {
            // use strong typing to prevent access from other classes 
            return $this->returnPrivatePropertyIfAllowed($order, 'contactPerson', 'supplier');
        }        
    }
    

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