Confessions of a Converted PHP Developer: On Visibility and Privates
Alright class—today I’m here to talk about the differences and similarities that PHP and Ruby have when it comes to object properties, methods, and their visibility—how you create variables inside classes, and how you can interact with them.
A Quick Recap of What We Know
In PHP, an object’s properties can be set to one of three visibilities; public, protected, and private. Properties that don’t have a visibility declaration default to public. Each of these declarations change what information is available to the class itself, its parents and children objects, and other code interacting with it. Let’s recap on how they work quickly.
- Public variables are available everywhere
- Protected variables are available only inside the class and its parents or children
- Private variables are only available in the class where they are specified, and are not available to classes that extend it
To handle interactions with protected and private properties it’s common to create getter and setter accessor methods. These are functions that allow you to set and retrieve the values of properties, and generally all look pretty similar—unless they’re actually doing manipulation to the data that’s stored, it’s common to see them as just single line functions returning or setting the property:
<?PHP
class animal
{
private $_species;
protected $_name;
public function get_species()
{
return $this->_species;
}
public function get_name()
{
return $this->_name;
}
}
class pterodactyl
extends animal
{
public function __construct()
{
$this->_name = $name;
$this->_species = 'dinosaur';
}
}
$my_big_birdie = new pterodactyl('Daryl');
var_dump($my_big_birdie->get_name()); // string(5) 'Daryl'
var_dump($my_big_birdie->get_species()); // NULL
Calling the get_name method returns the name we set, since that variable was protected and available in the child class. By setting _species in the child class, it created a new public property called _species, and left the private _species property in the parent class NULL. A full dump of $my_big_birdie will show us this:
Object(pterodactyl)#1 (3) {
["_species":"animal":private]=> NULL
["_name":protected]=> string(5) "Daryl"
["_species"]=> string(8) "dinosaur"
}
Ruby doesn’t exactly have properties as we think of them. Instead you have to define getter and setter methods for any instance variables you want to access. These are commonly referred to as readers and writers. (Variables with an @ symbol in front of them are instance variables, which means that they’re available inside the scope of the class.)
class Animal
def initialize(name)
@name = name
end
def name
@name
end
def name=(name)
@name = name
end
def species
@species
end
end
In this example we’ve implemented the animal class from PHP into Ruby. Note how the setter is defined by simply adding an equals to the end of the method name.
Since writing getters and setters can be a repetitive process, Ruby simplifies this by enabling us to declare which instance variables we want available as attributes:
class Animal
attr_reader :species
attr_accessor :name
def initialize(name)
@name = name
end
end
We now have the same class as before, except we’re creating our getters and setter with the Ruby attr_reader and attr_accessor. attr_reader creates only a getter method, attr_writer (unused here) only a setter method and attr_accessor creates both methods. 90% of the time this is all we need to do to expose our variables as attributes and for those times where you need more functionality, you can simply use the standard method construct.
Ruby also has a different implementation of Public, Private and Protected. By default all methods are public, but you can declare visibility by making a single protected or private statement and then any methods that follow will have that visibility. This allows you to group together methods with the same visibility:
class MyClass # the default visibility is public def my_public_method end protected def my_protected_method end def my_other_protected_method end private def my_private_method end end
Ruby’s implementation of protected and private is very different to PHP’s and has nothing at all to do with object inheritance. Private in Ruby is more akin to Protected in PHP. A private method in Ruby can be accessed inside the context of an object, or any inherited object.
class Foo
private
def bar
puts "bar called"
end
end
class Blarg < Foo
def initialize
bar
end
end
Because Blarg class extends Foo, it’s able to call the private method bar.
The implementation of protected in Ruby is different entirely to anything in PHP. Protected methods can be called by any object of the same class:
class Person
attr_writer :mother
def initialize(name)
@name = name
end
def mother_name
@mother.name if
end
protected
def name
@name
end
end
me = Person.new('Mal')
mum = Person.new('Jeni')
me.mother = mum
puts me.mother_name # => Jeni
puts me.name # => NoMethodError: protected method `name' called for #
Here we have a person class that allows protected access to the name only. When I create two instances and set the mother attribute of one instance to the other instance, then the mother_name method is capable of calling the mother variable’s name attribute, however I’m not capable of calling the name attribute outside the scope of the object itself.
This was a brief overview of some of the fundamental differences between PHP and Ruby’s class properties and method visibility—can you think of any other core differences that are worth mentioning?
One small note: "The implementation of protected in Ruby is different entirely to anything in PHP. Protected methods can be called by any object of the same class"
Thats not actually true - in PHP, instances of the same class can interact with eachothers protected and even private properties.
name = $name;
}
public function setMother(Person $mother)
{
$this->mother = $mother;
}
public function getMotherName()
{
if (isset($this->mother)) {
return $this->mother->name;
}
}
}
$me = new Person('Mal');
$mum = new Person('Jeni');
$me->setMother($mum);
echo $me->getMotherName();
Cheers!
ruby-1.9.2-p180 :001 > Class.new{ private; attr_accessor :a }.new.a
NoMethodError: private method `a' called for #<#:…>
I'm sorry, that is completely wrong. Private methods/properties can only be accessed by the class that has defined them. Classes that extend a parent class can only access the parent's methods/variables if they are protected or public.
Example:
class a {
private $var1 = 'test';
protected $var2 = 'test';
public $var3 = 'test';
private function classA_private_method() {
echo "called!";
}
protected function classA_protected_method() {
echo "called!";
}
public function classA_public_method() {
echo "Called!";
}
}
class b extends a {
private $var4 = 'test';
protected $var5 = 'test';
public $var6 = 'test';
public function __construct() {
echo parent::$var1;
echo parent::$var2;
echo parent::$var3;
parent::classA_private_method();
parent::classA_protected_method();
parent::classA_public_method();
}
}
$b = new b();
In the above example, calls to private_method or $var1 will result in fatal errors, as the "b" class does NOT have access to these properties/methods.
I think he was referring to the fact that an instance of class a, can access protected / private properties of ANOTHER instance of class a (the same class).
Even if you pass the new instance of class a (the same class) to the class a object, it cannot access the new instance properties (as they are defined private). All because they have the same name and object type doesn't mean anything. At the end of the day, they would be treated as 2 separate objects and thus the access permissions apply.
protected methods/properties go without saying: you can access these methods/properties if you are extending then through inheritance only (so only subclasses can access protected methods (or the parent itself)).
<?PHP
class foo{
protected $protected = "I'm protected\n";
private $private = "I'm private\n";
public function test($obj)
{
echo $obj->protected;
echo $obj->private;
}
}
class bar extends foo{
public function test($obj)
{
echo $obj->protected;
echo $obj->private;
}
}
$a = new foo;
$b = new foo;
$c = new bar;
$a->test($b);
$c->test($b);
Outputs:
I'm protected
I'm private
I'm protected
PHP Fatal error: Cannot access private property foo::$private in /home/mal/scripts/test.php on line 19
So one instance can access the private and protected properties of another instance, but obviously when it's extended it can't access the private property, only the protected one.
You are absolutely correct and I was very much wrong. Next time I'll test my theory out before stating it as fact haha
A year late, but better late than never ;)
-Jon