This post comes from the first version of this blog.
Please send me an email if anything needs an update. Thanks!

Czasami mam taki nostalgiczny nastrój tęsknoty za “starymi dobrymi czasami”, kiedy spod moich palców wyszło całkiem sporo kodu C++. Naprawdę lubię ten język i uważam za dobrą decyzję twórców wielu języków, aby lekko mówiąc “ściągnąć” składnię z tego języka do własnego rozwiązania. PHP w pewnym sensie jest jednym z nich, także istnieje pewne powiązanie pomiędzy obiema technologiami [poza faktem, że sam interpreter jest napisany w C]. W dzisiejszym wpisie chciałbym pokazać jedną rzecz, mały lukier składniowy, który był obecny w C++, a trochę brakowało mi go w PHP.

Fotografia: trekkyandy, CC-BY-SA.

PHP: Skrócony zapis kwalifikatorów widoczności metod klasy.

Aktualizacja: oczywiście niech Was nie zmyli tytuł, chodzi o atrybuty klasy, nie metody. Zagapiłem się podczas pisania, ech. ;]

Tym, którzy reagują alergicznie na zwrot “kwalifikator widoczności” śpieszę z wyjaśnieniami, że chodzi o modyfikatory public, protected i private. Artykuł Wikipedii o C++ tłumaczy wszystko w bardzo przystępny sposób:

Within a class, members can be declared as either public, protected, or private in order to explicitly enforce encapsulation. A public member of the class is accessible to any function. A private member is accessible only to functions that are members of that class and to functions and classes explicitly granted access permission by the class ("friends"). A protected member is accessible to members of classes that inherit from the class in addition to the class itself and any friends.
Co zatem jest takiego fajnego w C++, czego nie ma w PHP? Otóż, możemy zapisać kod klasy w następujący sposób:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Foo
	{
	public
		one,
		two;
	protected
		bar,
		baz;
	private
		lst,
		oth;

	void three(void);

	public:
		void one(void);
		int two(int param);

	protected:
		char bar(void);
		float baz(int one);

	private:
		void lst(void);
		void oth(void);
	};

Gdzie jest zysk? Zarówno w jednokrotnym wykorzystaniu poszczególnych kwalifikatorów widoczności, pozwalających na pogrupowanie metod / atrybutów klas, jak i w pewnym sensie w czytelności kodu - słówka “public”, “protected” i “private” górują nad deklaracjami funkcji, dzięki czemu lista metod jest łatwiejsza do przyswojenia dla oka programisty. Co jest nam w stanie zaoferować PHP dla podobnego przypadku? Spójrzmy:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
class FooP
	{
	public $one;
	public $two;

	protected $bar;
	protected $baz;

	private $lst;
	private $oth;

	function three() {}

	public function one() {}
	public function two($param) {}

	protected function bar() {}
	protected function baz($one) {}

	private function lst() {}
	private function oth() {}
	};
/* Powyższy kod zawiera przykład małego dysonansu pomiędzy działaniem programów w C++ i skryptów PHP. Jak na pewno zauważyliście, obie klasy Foo zawierają po jednej metodzie bez żadnego modyfikatora. Problem polega na tym, że w C++ domyślnie zostanie przypisany kwalifikator private, a w PHP public - przykład w dalszej części artykułu. */
Jak widać, ilość słów kluczowych klasyfikatorów to prawie połowa kodu. Oczywiście w rzeczywistych przypadkach nie wygląda to aż tak źle, ponieważ metody mają swoje ciała, dobrą praktyką jest także skomentowanie odpowiednich partii kodu blokami PHPDoc, także jest to w jakiś sposób akceptowalne. Czy nie da się jednak z tym nic zrobić?

Studiując kod frameworka symfony, a dokładnie jednego z wbudowanych helperów Doctrine trafiłem w pewnym momencie na dosyć ciekawy zapis:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
/**
 * sfDoctrine pager class.
 *
 * @package    sfDoctrinePlugin
 * @subpackage pager
 * @author     Jonathan H. Wage <jonwage@gmail.com>
 * @version    SVN: $Id: sfDoctrinePager.class.php 28897 2010-03-30 20:30:24Z Jonathan.Wage $
 */
class sfDoctrinePager extends sfPager implements Serializable
{
  protected
    $query             = null,
    $tableMethodName   = null,
    $tableMethodCalled = false;

Nie chwaląc się zbytnio mogę powiedzieć, że oko programisty od razu wypatrzyło pewną anomalię - brak klasyfikatora widoczności w deklaracji atrybutów klasy. W tym momencie zaświeciła się lampka - skoro oni tak mogą, to ja też! Spójrzmy na poniższy listing:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Foo
	{
	public
		$var = 'var',
		$bar = 'bar',
		$baz = 'baz';
	protected
		$one = 1,
		$two = 2,
		$three = 3;
	private
		$list = array(0, 1),
		$hash = array(2, 3),
		$obj = array();

	function goo() {}

	public function baz() {}
	public function bar() {}

	protected function one() {}
	protected function two() {}

	private function obj() {}
	private function hash() {}
	}

W ten sposób całkiem przyjemnie zadeklarowaliśmy sobie dziewięć atrybutów klasy, używając zaledwie trzech modyfikatorów. Dla konkretnych przypadków będzie jeszcze lepiej, ponieważ zazwyczaj wszystkie dane klasy będą opatrzone modyfikatorem private albo protected. Niestety dla metod to nie działa, jeśli spróbujemy zrobić coś takiego:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
	(...)
	public
		function baz() {}
		function bar() {}
	protected
		function one() {}
		function two() {}
	private
		function obj() {}
		function hash() {}
	(...)

To niestety wszystkie metody bez wyraźnego wskazania kwalifikatora widoczności zostaną przypisane do publicznego API klasy. Możemy to zobaczyć na poniższym listingu:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
echo '<pre>';
$foo = new Foo();
$fooReflection = new ReflectionClass('Foo');
$fooMethods = $fooReflection->getMethods();
foreach($fooMethods as $key => $method)
	{
	$modifiers = $method->getModifiers();
	echo
		$modifiers.' '.$method->getName()
		.($modifiers &amp; ReflectionMethod::IS_PUBLIC ? ' PUBLIC' : '')
		.($modifiers &amp; ReflectionMethod::IS_PROTECTED ? ' PROTECTED' : '')
		.($modifiers &amp; ReflectionMethod::IS_PRIVATE ? ' PRIVATE' : '')
		.($modifiers &amp; ReflectionMethod::IS_STATIC ? ' STATIC' : '')
		.($modifiers &amp; ReflectionMethod::IS_FINAL ? ' FINAL' : '')
		."\n";
	}

Wynik:

65792 goo PUBLIC
65792 baz PUBLIC
65792 bar PUBLIC
66048 one PROTECTED
65792 two PUBLIC
66560 obj PRIVATE
65792 hash PUBLIC

Co kończy dowód. ;]

Dzisiejszy “research” można uznać za zakończony. Zapraszam do dyskusji w komentarzach, gdzie na pewno dostanę kubeł zimnej wody na głowę za propagowanie “złych praktyk programistycznych”. A może faktycznie uznacie to za ciekawą opcję? ;]