PHP: a fractal of not so bad design
Školení, která pořádám
Whether you like PHP or not, go and read the article PHP: a fractal of bad design. It's well written by someone who really knows the language which is not true for most other articles about this topic. And there are numerous facts why PHP is badly designed on many levels. There is almost no FUD so it is also a great source for someone who wants to learn PHP really well (which is kind of sad).
I am surprised that I am able to live with PHP and even like it. Maybe I am badly designed too so that I am compatible with PHP. I was able to circumvent or mitigate most problems so the language doesn't bother me.
Anyway, there are several topics which are inaccurate or I don't agree with them. Here they are with no context so they probably wouldn't make much sense without reading the original article:
===
compares values and type… except with objects, where ===
is only true if both operands are actually the same object! For objects, ==
compares both value (of every attribute) and type, which is what ===
does for every other type.
This makes sense to me for two reasons:
- It is sometimes useful to know if two objects are the same and reusing
===
for this operations looks like a good idea to me. Comparing properties of two objects with different class is not very useful anyway.
- Internally,
$a = new stdClass
stores something like “object ID 576ab4” into the variable $a
. $b = new stdClass
stores a different object ID. So these two objects are not identical even if they are of the same class and have the same properties. Contrary, $c = $a
stores the same object ID to $c
so it is identical to $a
.
Global variables need a global
declaration before they can be used. This is a natural consequence of the above, so it would be perfectly reasonable, except that globals can’t even be read without an explicit declaration—PHP will quietly create a local with the same name, instead. I’m not aware of another language with similar scoping issues.
This is one of the greatest features of PHP. It is not issue at all as limiting the scope is always a Good Thing™. Really, this feature doesn't limit me at all and leads to programs with better architecture.
There are no references. What PHP calls references are really aliases; there’s nothing that’s a step back, like Perl’s references, and there’s no pass-by-object identity like in Python.
PHP is simpler than other languages in several different areas. But this simplicity doesn't lead to more complex programs harder to understand. It also doesn't lead to longer programs harder to read. So I see no reason to avoid this simplicity.
Constants are defined by a function call taking a string; before that, they don’t exist. (This may actually be a copy of Perl’s use constant
behavior.)
I loved the fact that most idioms in PHP can be expressed by a function call when I learned the language. And I was quite sad that echo, include and several other constructs are not real functions. We also have global const since PHP 5.3 but it's not a big deal.
Appending to an array is done with $foo[] = $bar
.
I endorse this shortcut and it also makes perfect sense to me: If you don't specify the index then use the automatic one. For those more conservative, we also have array_push.
E_STRICT
is a thing, but it doesn’t seem to actually prevent much and there’s no documentation on what it actually does.
Every function issuing E_STRICT
has it written in its documentation.
Fatal errors (e.g., new ClassDoesntExist()
) can’t be caught by anything. A lot of fairly innocuous things throw fatal errors, forcibly ending your program for questionable reasons. Shutdown functions still run, but they can’t get a stack trace (they run at top-level), and they can’t easily tell if the program exited due to an error or running to completion.
new ClassDoesntExist()
can degrade gracefully by defining __autoload.
Telling if program exited due to an error is not that hard: register_shutdown_function and check if error_get_last is fatal inside the callback.
Function arguments can have “type hints”, which are basically just static typing. But you can’t require that an argument be an int
or string
or object
or other “core” type, even though every builtin function uses this kind of typing, probably because int
is not a thing in PHP. (See above about (int)
.) You also can’t use the special pseudo-type decorations used heavily by builtin functions: mixed
, number
, or callback
.
It's possible to use callable
type hint since PHP 5.4.
Closures require explicitly naming every variable to be closed-over. Why can’t the interpreter figure this out? Kind of hamstrings the whole feature. (Okay, it’s because using a variable ever, at all, creates it unless explicitly told otherwise.)
Again, I consider limiting scope as a good thing.
Closed-over variables are “passed” by the same semantics as other function arguments. That is, arrays and strings etc. will be “passed” to the closure by value. Unless you use &
.
Which is perfectly consistent with the rest of the language (passing arguments, assigning variables).
Because closed-over variables are effectively automatically-passed arguments and there are no nested scopes, a closure can’t refer to private methods, even if it’s defined inside a class. (Possibly fixed in 5.4? Unclear.)
It is really fixed in PHP 5.4 and properly documented.
Exceptions in __autoload
and destructors cause fatal errors.
As all other uncaught exceptions. What else should uncaught exception cause?
fork
and exec
are not built in. They come with the pcntl extension, but that isn’t included by default.
exec is built in.
Negative indexing doesn’t work, since -1
is just as valid a key as 0
.
Sure, all arrays in PHP are associative. So not only negative indexing doesn't work, positive indexing doesn't work either. [0]
isn't the first element in the array, it's an element with index 0
. You can get elements with ordered indexes by array_values.
Despite how heavily PHP code relies on preserving key order:
array("foo", "bar") != array("bar", "foo")
array("foo" => 1, "bar" => 2) == array("bar" => 2, "foo" => 1);
I leave it to the reader to figure out what happens if the arrays are mixed. (I don’t know.)
Again, all arrays in PHP are associative, there's nothing like mixed array. And the algorithm for comparing arrays is properly documented.
To make sure nobody uploads PHP files, you just check that they don’t have a .php
extension. All an attacker has to do is upload a file named foo.php.txt
; your uploader won’t see a problem, but Apache will recognize it as PHP, and it will happily execute.
Disabling PHP execution by blacklisting extension is extremely stupid. Anybody can allow other extensions in future. Disable engine
instead.
While the PHP docs suggest using SetHandler
to make .php
files run as PHP, AddHandler
appears to work just as well, and in fact Google gives me twice as many results for it. Here’s the problem.
This is one of the weakest parts of the article. To rephrase the complaint: “If you use other Apache directive than recommended by PHP documentation which works coincidentally too then you will experience some side-effects conforming to Apache documentation. And PHP is to blame.” Really?
No generic standard database API. Stuff like PDO has to wrap every individual database’s API to abstract the differences away.
PDO is the standard generic database API. It doesn't wrap other APIs (such as MySQLi), it is on the same level as them. It just uses the same low-level library (libmysql or MySQLnd), pretty much same as any other programming language.
include
accepting HTTP URLs. Likewise.
I agree that it is an embarrassment. Just to clarify that it can be (and it is by default) disabled by allow_url_include
since PHP 5.2.
Comments
ad exceptions in __autoload: Exceptions thrown in function __autoload causes a fatal error, there is no way to catch it. Fixed in PHP 5.3.
I think one of the weakest parts of the article is about OOP:
- „abstract classes, private, public, protected, static, etc. Trying to win over Java developers?“
Great features.
- Subclasses cannot override private methods
Well done.
- There is no method you can call on a class to allocate memory
Did author hear about automatic memory management?
- et trying to convert a built-in or user-defined object (even a Closure) to a string causes an error if it doesn’t define __toString
That's good.
- Static variables inside instance methods are global; they share the same value across all instances of the class.
Of course!
Jirkop:
To je Google Translator?
Napiš to radši česky, Jakub ti to přeloží...
Je tomu bez problémů rozumět. U anglického článku je lepší nedokonalá angličtina než jakákoliv čeština. A překládat samozřejmě nic nebudu.
Vena:
> Great features.
As the author said, this is more personal taste. I kind of agree with him - PHP creators obviously couldn't come up with something better, so they just designed OOP like Java. And Java OOP is not perfect.
For example - static members don't belong in OOP design, imho. Scala and Ruby came up with a much neater solution.
Another example - final keyword is verbose.
Don't know what's wrong with the others.
> Well done.
Agreed.
> Did author hear about automatic memory management?
Well, I think he's trying to say that PHP doesn't have constructors, if it had, there would be available some sort of memory management.
> That's good.
It's not - it's inconsistent. All primitive types, arrays and even null can be converted to string by default. Objects can't. Default implementation of __toString would solve this, much like in any other OO language.
> Of course!
Wouldn't it be better and more logical if they shared the same value across method calls of one instance? Static attributes share the same value across all instancies.
(#d-13392)
#d-13398 reply
Vojtěch Dobeš:
> Wouldn't it be better and more logical if they shared the same value across method calls of one instance? Static attributes share the same value across all instancies.
Well, then they would be exactly the same thing as instance properties, wouldn't?
Vena:
Nope, they wouldn't.
Instance properties are declared within the class scope. Local variables, on the other hand, are declared within the method scope.
Ad „- et trying to convert a built-in or user-defined object (even a Closure) to a string causes an error if it doesn’t define __toString
That's good.“
Např. v Javě dědí všechny třídy metodu toString() z předka java.lang.Object – sice se z ní toho člověk moc nedozví (jen název třídy + hashCode v hexu), ale pořád lepší, než aby to vyhazovalo chybu. A pokud chci lepší výstup, napíši si vlastní toString();
Zároveň ale funguje typová kontrola (takže nelze libovolný objekt strčit do metody, která očekává String) a k přetypování resp. zavolání toString() dojde až ve chvíli, kdy udělám třeba: String a = "abc" + objekt;
K čemu by bylo dobré v takové situaci vyhazovat výjimku?(#d-13392)
#d-13407 reply
David Grudl :
Preferuju, když chyba způsobí výjimku. Když třeba omylem použiju closure tam, kde má být string.
Obecně souhlasím – je lepší, když dojde k chybě, než když to v tichosti pokračuje, jazyk/platforma si něco domýšlí a pak to nějak záhadně ne/funguje.
Ale když někam napíši:
String a = "abc" + objekt;
tak to považuji za dostatečně explicitní vyjádření toho, že chci objekt převést na text (resp. zavolat jeho toString() metodu) a připojit k jinému textu.
V Javě ano. V PHP to často znamená, že si myslím, že v proměnné není objekt.
Je tu i druhá stránka věci, že schopnost převést se na text by měla být výsada objektu, u kterého to dává smysl, nikoliv standard zděděný od java.lang.Object.
Built-in PHP exec() is totally different from pcntl_exec() that original blogpost complains about that is not in core PHP. However, because fork() and exec() (in PHP pcntl_fork() and pcntl_exec()) are Unix-only functions, it is obvious why they are not in core, why they come only in extension.
> Whether you like PHP or not, go and read the article PHP: a fractal of bad design. It's well written by someone who really knows the language which is not true for most other articles about this topic.
Actually, the original article is full of FUD and inaccuracies. The author didn't bother investigating many of the problems in PHP he pointed out, and some of them were just flat out wrong.
This article does a decent job of explaining why the original article is wrong, but stating the original article was well written by a knowledgeable PHP dev is just laughable.
Vena:
> This article does a decent job of explaining why the original article is wrong
I think you've read a different article.
Can you please be more factual? Can you list the points that are FUD in your opinion? It's possible that I overlooked something.(#d-13397)
#d-13403 reply
Vladimir Belohradsky:
I read original article and I must admit he have right in some circumstances. For example foreach($foo as &$bar) is really a trap for newbies. Using foreach in recursive function or on very large array is fantastic bottleneck. We all must agree that PHP is nice but yet have a dirty foundation.
Can you please elaborate why do you think that foreach is a bottleneck. Can you give us some examples?
pras:
Prosím o help, převzal jsem to po předchozím programátorovi takto a hlásí to: Parse error: syntax error, unexpected '}'
<h2 class="pagetitle">Obchod: čtyřkolky, motorky a dopl�ky</h2>
<?phpif(!$_GET['cat']) }?><div class="content"> <p>NacházĂte se v online katalogu produktov zameranĂ©ho pro ÄŤtyĹ™kolky, motorky a dalšà motodoplĹ�ky. NabĂzĂme vĂ˝hodnĂ© zbožà pro všechny typy ÄŤtyĹ™kolek. ÄŚtyĹ™kolky jsou od autorizovanĂ©ho dovozce a všechny produkty (helmy, pĹ™ilby i samotnĂ© ÄŤtyĹ™kolky) jsou v plnĂ© záruce. <br/> Pokud máte zájem o ojazdenĂ© ÄŤtyĹ™olky nebo motorky, navštĂvne sekci <a href="motoinzerce">motoinzerce</a></p></div>
<?php} if($_GET['cat']){ $cattext = text2url($_GET['cat']); $sql = mysql_query("SELECT id,nazov,popis FROM shop_categories WHERE url LIKE '".$cattext."'"); if(@mysql_num_rows($sql)){ $cat = mysql_fetch_array($sql); $sql2products = mysql_query("SELECT id,name,cena,popisek FROM shop_produkty WHERE cat_id = '".$cat['id']."' ORDER BY priorita DESC"); if(($pocet = @mysql_num_rows($sql2products)) > 0){ echo '<h3>'.$cat["nazov"].'</h3><p>'.$cat["popis"].'</p ><p><a href="prodej" title="obchod">Obchod </a> » </p>'; if(str_replace("ctyrkolky","",$cattext) != $cattext) $zobrazit = 1; if($zobrazit == 1){ // 1 na jeden riadok echo '<div class="shoptable"><table class="box-long">'; while($p = mysql_fetch_array($sql2products)){ $img = (file_exists($img_file = "uploads/products/".$p['id'].".png")) ? "<img align='left' src='image.php?type=product&id=".$p['id']."' title='".$p['name']."' />":""; echo '<tr> <td><a href="'.produkt_url($p['id'],$p['name']).'">'.$img .'</a></td> <td><h3><a href="'.produkt_url($p['id'],$p['name']).'">'.$p[' name'].'</a></h3> <p>'.$p['popisek'].' </p></td> <td width="60px"><p> '.$p['cena'].' ,- </p></td> </div>'; } echo "</table></div>"; } else { echo '<div class="shoptable">'; while($p = mysql_fetch_array($sql2products)){ $img = (file_exists($img_file = "uploads/products/".$p['id'].".png")) ? "<img align='left' src='image.php?type=product&id=".$p['id']."' title='".$p['name']."' />":""; echo '<div class="box"> <h3>'.$p['name'].'</h3> <a href="'.produkt_url($p['id'],$p['name']).'">'.$img .'</a> <p><b>Cena: '.$p['cena'].' kÄŤ</b></p> <p><a href="'.produkt_url($p['id'],$p['name']).'">'.$p[' name'].'</a></p> <p>'.$p['popisek'].' </p> </div>'; } echo "</div>"; } } else echo "<p>Žádne produkty v kategorii</p>"; }else echo "Kategoria nenajdena";}else{ $res2cat = "SELECT id,nazov,popis,position,url FROM shop_categories WHERE 1 ORDER BY position ASC"; $sql2cat = mysql_query($res2cat); if($pocet_kategorii = @mysql_num_rows($sql2cat)){ echo '<div class="shoptable">'; while($c = mysql_fetch_array($sql2cat)){ if($c['position'] != '1') $up = "<a href='?menu=".$_GET['menu']."&page=".$_GET['pa ge']."&cat_id=".$c['id']."&from=".$c['posi tion']."&to=".($c['position']-1)."'><img src='admin/grafika/arrow_up.png' alt='icon' class='floatright' /></a>"; else $up = " "; if($c['position'] != $pocet_kategorii) $down = "<a href='?menu=".$_GET['menu']."&page=".$_GET['pa ge']."&cat_id=".$c['id']."&from=".$c['posi tion']."&to=".($c['position']+1)."'><img src='admin/grafika/arrow_down.png' alt='icon' class='floatleft' /></a>"; else $down = " "; echo "<div class='box'> <h3><a href='".$c['url']."-prodej'>".$c['nazov']."</a></h 3> <br/><p> ".$c['popisek']." </p> <br/><p> <a href='".$c['url']."-prodej'>".$c['nazov']." » </a></p> </div>"; } echo "</div>"; }}?>
averygrayson:
Today we can have both of the worlds. We can catch all errors in our own way and frameworks are often configured to error out on PHP errors and be strict about them. Plus, PHP 7 is changing things with exceptions replacing errors. Also got some plane on host buddy.com.
Schiwy:
Yes many of the things in the original article are outdated. However, there are many things that are messed up with PHP and they cannot be made "un-messed up", for example the handling of ternary operators.
$foo = 1;
print(($foo === 1) ? "Yes, it's 1" : ($foo === 2) ? "Yes, it's 2" : "It's neither");
will print out "Yes, it's 2" in PHP, while it would print out "Yes, it's 1" in EVERY OTHER LANGUAGE THAT SUPPORTS TERNARY OPERATORS.
mirv:
ternary operators are shit regardless of which language you're using. code is supposed to be readable, not short. whitespace is cheap.
Diskuse je zrušena z důvodu spamu.