Link to home
Start Free TrialLog in
Avatar of rgb192
rgb192Flag for United States of America

asked on

copy by identifier versus copy by reference

from lynda.com php object oriented tutorial

copy by identifier versus copy by reference
"object comparison makes it clear that actions taken on the core object affect the referenced object"




changing line 51 from
$address_business_copy = $address_business;
to
$address_business_copy = &$address_business;
has the same output.

I do not understand the purpose of this lesson
demo.php
<?php

/**
 * Define autoloader.
 * @param string $class_name 
 */
function __autoload($class_name) {
  include 'class.' . $class_name . '.inc';
}

echo '<h2>Instantiating AddressResidence</h2>';
$address_residence = new AddressResidence();

echo '<h2>Setting properties...</h2>';
$address_residence->street_address_1 = '555 Fake Street';
$address_residence->city_name = 'Townsville';
$address_residence->subdivision_name = 'State';
$address_residence->country_name = 'United States of America';
echo $address_residence;
echo '<tt><pre>' . var_export($address_residence, TRUE) . '</pre></tt>';

echo '<h2>Testing Address __construct with an array</h2>';
$address_business = new AddressBusiness(array(
  'street_address_1' => '123 Phony Ave',
  'city_name' => 'Villageland',
  'subdivision_name' => 'Region',
  'country_name' => 'Canada',
));
echo $address_business;
echo '<tt><pre>' . var_export($address_business, TRUE) . '</pre></tt>';

echo '<h2>Instantiating AddressPark</h2>';
$address_park = new AddressPark(array(
  'street_address_1' => '789 Missing Circle',
  'street_address_2' => 'Suite 0',
  'city_name' => 'Hamlet',
  'subdivision_name' => 'Territory',
  'country_name' => 'Australia',
));
echo $address_park;
echo '<tt><pre>' . var_export($address_park, TRUE) . '</pre></tt>';

echo '<h2>Cloning AddressPark</h2>';
$address_park_clone = clone $address_park;
echo '<tt><pre>' . var_export($address_park_clone, TRUE) . '</pre></tt>';
echo '$address_park_clone is ' . ($address_park == $address_park_clone ?
  '' : 'not ') . ' a copy of $address_park.';
  
  
echo '<h2>Copying AddressBusiness reference</h2>';
$address_business_copy = $address_business;
echo '$address_business_copy is '. ($address_business === $address_business_copy ?
 '': 'not' ). ' a copy of $address_business.';
 
 echo '<h2>Setting address_business as a new AddressPark</h2>';
 $address_business = new AddressPark();
 echo '$address_business_copy is ' . ($address_business === $address_business_copy ?
 '': 'not' ). ' a copy of $address_business.';
 echo '<br>$address_business is class '.get_class($address_business) . '.';
 echo '<br>$address_business_copy is ' . ($address_business_copy instanceof AddressBusiness ? '' : 'not'). ' an AddressBusiness.';

Open in new window



class.Address.inc
<?php

/**
 * Physical address. 
 */
abstract class Address implements Model {
  
  const ADDRESS_TYPE_RESIDENCE = 1;
  const ADDRESS_TYPE_BUSINESS = 2;
  const ADDRESS_TYPE_PARK = 3;
  
  // Address types.
  static public $valid_address_types = array(
    Address::ADDRESS_TYPE_RESIDENCE => 'Residence',
    Address::ADDRESS_TYPE_BUSINESS => 'Business',
    Address::ADDRESS_TYPE_PARK => 'Park',
  );
  
  // Street address.
  public $street_address_1;
  public $street_address_2;
  
  // Name of the City.
  public $city_name;
  
  // Name of the subdivison.
  public $subdivision_name;
  
  // Postal code.
  protected $_postal_code;
  
  // Name of the Country.
  public $country_name;
  
  // Primary key of an Address.
  protected $_address_id;
  
  // Address type id.
  protected $_address_type_id;
  
  // When the record was created and last updated.
  protected $_time_created;
  protected $_time_updated;
  
  /**
  * post clone behavior
  * 
  */
  function __clone(){
    $this->_time_created=time();
    $this->_time_updated=NULL;
  }
  
  /**
   * Constructor.
   * @param array $data Optional array of property names and values.
   */
  function __construct($data = array()) {
    $this->_init();
    $this->_time_created = time();
    
    // Ensure that the Address can be populated.
    if (!is_array($data)) {
      trigger_error('Unable to construct address with a ' . get_class($name));
    }
    
    // If there is at least one value, populate the Address with it.
    if (count($data) > 0) {
      foreach ($data as $name => $value) {
        // Special case for protected properties.
        if (in_array($name, array(
          'time_created',
          'time_updated',
        ))) {
          $name = '_' . $name;
        }
        $this->$name = $value;
      }
    }
  }
  
  /**
   * Magic __get.
   * @param string $name 
   * @return mixed
   */
  function __get($name) {
    // Postal code lookup if unset.
    if (!$this->_postal_code) {
      $this->_postal_code = $this->_postal_code_guess();
    }
    
    // Attempt to return a protected property by name.
    $protected_property_name = '_' . $name;
    if (property_exists($this, $protected_property_name)) {
      return $this->$protected_property_name;
    }
    
    // Unable to access property; trigger error.
    trigger_error('Undefined property via __get: ' . $name);
    return NULL;
  }
  
  /**
   * Magic __set.
   * @param string $name
   * @param mixed $value 
   */
  function __set($name, $value) {
    // Allow anything to set the postal code.
    if ('postal_code' == $name) {
      $this->$name = $value;
      return;
    }
    
    // Unable to access property; trigger error.
    trigger_error('Undefined or unallowed property via __set(): ' . $name);
  }
  
  /**
   * Magic __toString.
   * @return string 
   */
  function __toString() {
    return $this->display();
  }
  
  /**
   * Force extending classes to implement init method. 
   */
  abstract protected function _init();
  
  /**
   * Guess the postal code given the subdivision and city name.
   * @todo Replace with a database lookup.
   * @return string 
   */
  protected function _postal_code_guess() {
    $db = Database::getInstance();
    $mysqli = $db->getConnection();
    
    $sql_query  = 'SELECT postal_code ';
    $sql_query .= 'FROM location ';
    
    $city_name = $mysqli->real_escape_string($this->city_name);
    $sql_query .= 'WHERE city_name = "' . $city_name . '" ';
    
    $subdivision_name = $mysqli->real_escape_string($this->subdivision_name);
    $sql_query .= 'AND subdivision_name = "' . $subdivision_name . '" ';
    
    $result = $mysqli->query($sql_query);
    
    if ($row = $result->fetch_assoc()) {
      return $row['postal_code'];
    }
  }
  
  /**
   * Display an address in HTML.
   * @return string 
   */
  function display() {
    $output = '';
    
    // Street address.
    $output .= $this->street_address_1;
    if ($this->street_address_2) {
      $output .= '<br/>' . $this->street_address_2;
    }
    
    // City, Subdivision Postal.
    $output .= '<br/>';
    $output .= $this->city_name . ', ' . $this->subdivision_name;
    $output .= ' ' . $this->postal_code;
    
    // Country.
    $output .= '<br/>';
    $output .= $this->country_name;
    
    return $output;
  }
  
  /**
   * Determine if an address type is valid.
   * @param int $address_type_id
   * @return boolean
   */
  static public function isValidAddressTypeId($address_type_id) {
    return array_key_exists($address_type_id, self::$valid_address_types);
  }
  
  /**
   * If valid, set the address type id.
   * @param int $address_type_id 
   */
  protected function _setAddressTypeId($address_type_id) {
    if (self::isValidAddressTypeId($address_type_id)) {
      $this->_address_type_id = $address_type_id;
    }
  }
  
  /**
   * Load an Address.
   * @param int $address_id 
   */
  final public static function load($address_id) {}
  
  /**
   * Save an Address. 
   */
  final public function save() {}
}

Open in new window



output
Instantiating AddressResidence
Setting properties...

QUERY: SELECT 1+1 
FOUND 1 ROWS OF DATA USING MySQLi_Result::Fetch_Object(): stdClass Object ( [1+1] => 2 ) 555 Fake Street
Townsville, State 12345
United States of America
AddressResidence::__set_state(array(
   'street_address_1' => '555 Fake Street',
   'street_address_2' => NULL,
   'city_name' => 'Townsville',
   'subdivision_name' => 'State',
   '_postal_code' => '12345',
   'country_name' => 'United States of America',
   '_address_id' => NULL,
   '_address_type_id' => 1,
   '_time_created' => 1378182435,
   '_time_updated' => NULL,
))
Testing Address __construct with an array
123 Phony Ave
Villageland, Region 67890
Canada
AddressBusiness::__set_state(array(
   'street_address_1' => '123 Phony Ave',
   'street_address_2' => NULL,
   'city_name' => 'Villageland',
   'subdivision_name' => 'Region',
   '_postal_code' => '67890',
   'country_name' => 'Canada',
   '_address_id' => NULL,
   '_address_type_id' => 2,
   '_time_created' => 1378182436,
   '_time_updated' => NULL,
))
Instantiating AddressPark
789 Missing Circle
Suite 0
Hamlet, Territory 34567
Australia
AddressPark::__set_state(array(
   'street_address_1' => '789 Missing Circle',
   'street_address_2' => 'Suite 0',
   'city_name' => 'Hamlet',
   'subdivision_name' => 'Territory',
   '_postal_code' => '34567',
   'country_name' => 'Australia',
   '_address_id' => NULL,
   '_address_type_id' => 3,
   '_time_created' => 1378182436,
   '_time_updated' => NULL,
))
Cloning AddressPark
AddressPark::__set_state(array(
   'street_address_1' => '789 Missing Circle',
   'street_address_2' => 'Suite 0',
   'city_name' => 'Hamlet',
   'subdivision_name' => 'Territory',
   '_postal_code' => '34567',
   'country_name' => 'Australia',
   '_address_id' => NULL,
   '_address_type_id' => 3,
   '_time_created' => 1378182436,
   '_time_updated' => NULL,
))
$address_park_clone is a copy of $address_park.
Copying AddressBusiness reference
$address_business_copy is a copy of $address_business.
Setting address_business as a new AddressPark
$address_business_copy is not a copy of $address_business.
$address_business is class AddressPark.
$address_business_copy is an AddressBusiness.

Open in new window



transcript of the 3 minute video which I did not understand
00:00	In PHP, a reference is an alias, meaning two different variables can write to the same value.
00:06	There is a good chance you've seen this in procedural code before.
00:09	Referenced objects are a bit different however, in that the object variables do
00:13	not contain the actual object itself, only the internal object identifier, which
00:18	is behind the scenes in PHP.
00:20	This is one of the major changes from PHP 4, which greatly improved memory
00:24	usage and performance.
00:26	To demonstrate this, make a copy of the variable for AddressBusiness.
00:29	Add the following block to the end of the demo: echo <h2>Copying AddressBusiness
00:37	reference; $address_business _copy = $address_business.
00:45	Copy the logic from the last demo.
00:47	Use triple equals (===) to determine if it's an exact copy.
00:51	address_business_copy is or is not a copy of address_business.
01:00	Next, we're going to set address_ business_copy as a new address_park. echo <h2>
01:06	setting_address_business as a new address_park. $address_business = new
01:17	address_park. And, we'll paste the same line.
01:24	Finally, use the get_class function to get the name of an object's class. echo
01:28	'<br/>$address_business is class ' . get_class($address_business).
01:40	You can also use the function instanceof to make logical decisions.
01:45	echo '<br/> address business copy is address_business_copy instanceof
01:58	AddressBusiness and $address_business.
02:07	This last line will determine whether or not address_business_copy is an
02:10	instance of address_business.
02:12	Save, and view the result in your browser.
02:18	When you copy by identifier, you end up with a copy of the object, as you would expect.
02:23	To demonstrate this, I am going to make a copy by reference.
02:26	Return to the demo code, and add the reference symbol to the assignment, after
02:30	address_business_copy.
02:31	Save, and rerun the demo.
02:34	This time, the object comparison makes it clear that actions taken on the core
02:38	object affect the referenced object.
02:41	In this chapter, the focus has been on class relationships and interactions.
02:45	We started by extending the Address class with address type specific subclasses,
02:49	and enabled autoloading to deal with all the different class files.
02:53	We then abstracted the Address class and methods, and then created a shared
02:56	interface to add structure.
02:58	We overrode methods and properties, and learned how to override constants, as well.
03:03	We made copies of objects, and compared them to one another, and
03:06	implemented cloning behaviors.
03:08	Finally, we experimented with referencing objects.
03:11	In the next chapter, I'm going to demonstrate objects that are ready built into
03:15	PHP, including the standard class and exceptions.

Open in new window

Avatar of Loganathan Natarajan
Loganathan Natarajan
Flag of India image

Have you read this link? http://www.php.net/manual/en/language.oop5.references.php
The same point talked here.
Sigh.  This is one of those things that is computer-science 101, but was confused by PHP's early history of trying to make everything so easy that you didn't need any computer science background to write computer programs.  I would speculate that the vast majority of PHP programmers are going to have trouble with the concept, so don't feel like you're alone in this.

In most programming languages, a variable name is a pointer to a "container" that holds data.  Example here, where $a is the pointer to a storage location containing the integer 100.

$a = 100;

In PHP you can copy that variable with an assignment statement, and PHP will create another container that holds another integer 100.

$b = $a;

If you add 1 to $a, your $a container will have 101, but your $b container will still have 100.

$a = $a + 1;
var_dump($b);

However this is different in PHP when $a is a pointer to an object.  In this case the assignment operator does not create a copy of the object, instead it creates a new reference to the old object.  

$a = new stdClass;
$a->value = 100;
$b = $a;
$a->value = $a->value + 1;
var_dump($b); // PRINTS 101

In a nutshell, non-objects are assigned by copying the data to a new storage location and associating the new variable name with the new storage location.  However PHP objects are assigned by adding a new variable name to the symbol table, with the new name pointing to the old storage location.  If you want a totally new instance of the object, not just a pointer of another name, you must instantiate a new object using the PHP keyword new.

$a = new stdClass;
$a->value = 100;
$b = new stdClass;
$b->value = 100;
$a->value = $a->value + 1;
var_dump($b); // PRINTS 100

Does that help?
Avatar of rgb192

ASKER

$a = new stdClass;
$a->value = 100;
$b = new stdClass;
$b->value = 100;
$a->value = $a->value + 1;
var_dump($b); // PRINTS 100


could you do code with a '&'
could you do code with a '&'
Maybe, but why would you want to?  There is a logical difference that is handled via assignment or instantiation.

Either you want two variable names to point to the same object or you would want two variable names to point to different objects (with different properties).  In the first case you would use the assignment operator; in the latter case you would use the new keyword.
Avatar of rgb192

ASKER

>>could you do code with a '&'

I meant could you, 'Ray' write an example with '&'
because I do not understand yet
I can understand why you do not understand.  It's one of the crazy-funhouse things about PHP, and it's very inconsistent throughout the language.  Objects are treated differently from arrays, some functions imply the use of the &, directly acting on passed variables, etc.

Here is the simplest example I can create.  Explanation follows.
http://www.laprbass.com/RAY_temp_rgb192.php

<?php // RAY_temp_rgb192.php
error_reporting(E_ALL);

// SEE http://www.experts-exchange.com/Web_Development/Web_Languages-Standards/PHP/Q_28229102.html#a39466146

// A SIMPLE FUNCTION TO ADD ONE TO A VARIABLE
function addone($var)
{
    $var = $var + 1;
    return $var;
}

// PASS THE VARIABLE TO THE FUNCTION BY COPY
$x = 3;
$y = addone($x);
echo "X=$x Y=$y";

echo '<br>';

// PASS THE VARIABLE TO THE FUNCTION BY REFERENCE
$x = 3;
$y = addone(&$x);
echo "X=$x Y=$y";

Open in new window

This function adds one to a variable, and returns the value.  The output from this script is:

X=3 Y=4
X=4 Y=4

On line 15, the call is made "normally" in the way we would usually write a function call.  The variable $x is passed to the function "by copy" meaning that the actual $x variable is copied by PHP and a copy of the variable is given to the function.  The data inside the function is encapsulated so that changes in the function do not leak into the outside scope.

On line 22, the call is made with the ampersand notation.  The variable $x is passed "by reference" meaning that the variable is not copied, but the original $x variable pointer is given to the function.  This has the effect of making the function perform its work on a variable that is outside the function definition.

Why would anyone want to do this?  Well, what if you had a huge data structure and you did not want PHP to duplicate all of the data, pass it to the function, then reassign all of the data after the function ran?  If you use the &$var notation, the function can operate on the original data, not on the copy.  This lowers your memory requirements.  The PHP sorting functions all work this way, using an implicit "pass by reference" on the array.
Avatar of rgb192

ASKER

so on line 22
both x and y are changed

how does that happen
because I do not see an assignment for x to change
$x=new value
ASKER CERTIFIED SOLUTION
Avatar of Ray Paseur
Ray Paseur
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of rgb192

ASKER

Thanks.

Now I understand one example but I still do not understand 'why'.

I have a followup question:
https://www.experts-exchange.com/questions/28232216/Real-world-examples-of-pass-by-reference.html