Array as class property?

15,705

Theory

First, a bit of background. Objects are composed of "state" (fields–in PHP, these are usually called "properties", but I'm going to use that term in another way) and "behavior" (methods). State is an important part of encapsulation: it allows data to persist as long as an object exists and lets the data be visible in multiple functions. You use object fields when you need data to have these two attributes. These attributes are examples of two very important properties: accessibility (similar to variable scope) and storage duration. Discussions usually cover the scope and duration of variables (which associate names to data), but here we'll focus on data.

Accessibility determines when and where the data can be, well, accessed by code. Other types of accessibility include local (where data is only accessible within a single function) and global (where data is accessible to all code in a code unit in every function call). Like global data, state is accessible to multiple functions, but unlike global data, the same method will access different data when invoked on different objects. An example in a made up language that somewhat confuses variables & data:

i=0
inc() {...}
dec() {...}
class C {
  i=0
  inc() {...}
  dec() {...}
}


a = C()
b = C()

inc() // the global i is visible in both these calls, 
dec() // which access the same data

a.inc() // C::i is visible in both C::inc and C::dec,
b.dec() // but these calls access different data

i    // The global i is accessible here
// C::i not accessible

Storage duration determines how long the data exists (when it's created and destroyed). Types of duration include automatic (where the data exists until the function that created it exits), static (the data exists for the life of the process) and dynamic (data is explicitly created and either explicitly destroyed or destroyed by a garbage collecter when no longer accessible). State shares duration with its object: if the object is automatic, the state is automatic; if dynamic, the state is dynamic.

State isn't the only way of having data accessible between method calls. You can also pass the data as arguments to the methods, in which case the data has local duration. The difference between the to is that for state, "between" includes times when no method is being called (i.e. on the call stack), while the latter doesn't. Whether to use state or arguments depends on what type of duration is required. With public methods, having too many arguments reduces readability and can cause bugs (with a function of high arity, it's easier to get the order wrong, or forget an argument entirely). As a secondary consideration, state can help reduce the number of arguments.

Application

From what you've shown so far, the data you're asking about doesn't need to be accessible between methods and doesn't need to exist outside of each method invocation. The post fields you're asking about are basically arguments for a remote procedure call (RPC); If you were to allow building up these arguments by invoking methods, then it would make sense to store the data as object state. As it is, storing the post fields as state is valid, but not best practice. It's also not necessarily worst practice. Best case, you're cluttering up the object and wasting memory by keeping the data around when not within a method that's using the API. Worst case, you set arguments in one method that then get passed in the RPC when another method is invoked.

abstract class ApiSomething {
    public function eatSaltyPork() {
        $this->_postFields["action"] = __FUNCTION__;
        $this->_postFields['spices[]'] = 'salt';
        $result = $this->_connection->Apiconnect($this->_postFields);
        ...
    }
    public function eachCheese() {
        $this->_postFields["action"] = __FUNCTION__;
        $result = $this->_connection->Apiconnect($this->_postFields);
        ...
    }
}


$thing = new ApiSomething();
$thing->eatSaltyPork();
$thing->eatCheese(); // ends up eating salty cheese

This is very much something you want to avoid. It could easily be done by setting the post fields array to an empty array, but at that point you might as well use a local variable rather than a field.

Share:
15,705
MEM
Author by

MEM

Updated on July 26, 2022

Comments

  • MEM
    MEM almost 2 years

    I have this API that requires me to have a specific array key to be send. Since that array needs to be used on ALL class methods, I was thinking on putting as a class property.

    abstract class something {
        protected $_conexion;
        protected $_myArray = array();
    }
    

    Later on, on the methods of this class, I will then use:

    $this->_myArray["action"] = "somestring";
    

    (Where "action" is the key that needs to be send to this API);

    Is this ok? I've not seen enough OOP in front of my eyes that's why I'm asking this.

    As requested, here is more info about the API:

    class Apiconnect {
        const URL = 'https://someurl.com/api.php';
        const USERNAME = 'user';
        const PASSWORD = 'pass';
    
        /**
         *
         * @param <array> $postFields
         * @return SimpleXMLElement
         * @desc this connects but also sends and retrieves the information returned in XML
         */
        public function Apiconnect($postFields)
        {
            $postFields["username"] = self::USERNAME;
            $postFields["password"] = md5(self::PASSWORD);
            $postFields["responsetype"] = 'xml';
    
            $ch = curl_init();
            curl_setopt($ch, CURLOPT_URL, self::URL);
            curl_setopt($ch, CURLOPT_POST, 1);
            curl_setopt($ch, CURLOPT_TIMEOUT, 100);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 
            curl_setopt($ch, CURLOPT_POSTFIELDS, $postFields);
            $data = curl_exec($ch);
            curl_close($ch);
    
            $data = utf8_encode($data);
            $xml = new SimpleXMLElement($data);
    
            if($xml->result == "success")
            {
                return $xml;
            }
            else
            {  
                return $xml->message;
            }
        }
    
    }
    
    
    abstract class ApiSomething
    {
        protected $_connection;
        protected $_postFields = array();
    
        /**
         * @desc - Composition.
         */
        public function __construct()
        {
            require_once("apiconnect.php");
    
            $this->_connection = new Apiconnect($this->_postFields);
        }
    
        public function getPaymentMethods()
        {
            //this is the necessary field that needs to be send. Containing the action that the API should perform.
            $this->_postFields["action"] = "dosomething";
    
            //not sure what to code here;
    
            if($apiReply->result == "success")
            {
                //works the returned XML
                foreach ($apiReply->paymentmethods->paymentmethod as $method)
                {
                    $method['module'][] = $method->module;
                    $method['nome'][] = $method->displayname;
                }
    
                return $method;
            } 
        }
    }
    

    Thanks a lot, MEM