On Sep 16, 2015, at 14:09, Rowan Collins <rowan.coll...@gmail.com> wrote: > I can certainly sympathise with wanting to run without any notices - they are > generally hints for writing better code. In the vast majority of cases, > though, that means working out *why* the variable is undefined, otherwise you > might as well just use the @ operator to say "I don't care about this notice”.
Agree completely, and for that reason, we also ban @ except in those rare cases where PHP forces the issue (that’s for other discussions...). Indeed, this often comes up with short-lived issues that are resolved quickly. For example, a config file is missing a variable, so the user is told what it is, what kind of value to supply, and they goes off to fix it, or the user is warned and the app goes on with a default. But, all this happens without throwing errors and without having to detour through the global error handler. In other cases, the fix may not be so available, like with the template system that creates variables in the view’s scope; here, the goal is just to gracefully catch the condition and then define a default (and maybe complain to the author). > This still implies that there's a common problem to fix, which I just don't > believe. The number of places where the following all hold true must be > vanishingly small: > > - the third-party code is dumping bare variables into your scope (globals, or > a file-level include are the only mechanisms I can think of) This seems to be pretty common with template systems, especially older ones. It’s also common with config files. > - they do so unreliably, in the sense that different variables will be set > under different circumstances More common than different variables in different circumstances is the missing variable. Consider the user who’s setting up an instance of an application that uses a config file with bare variables — very easy to forget something, or type the name wrong, or whatever. Or the programmer writing a plugin for a poorly documented application who wants to be sure the runtime environment of the plugin is what’s expected. Really, a lot of the same situations where one might need to check the existence of an array element or object property, except that the item to be checked happens to be a plain old variable. > - you/they have used null as a value which needs to be positively asserted, > not taken as the default state, so that isset() does not meet your needs Well, it’s not so important that null represents a non-default state, as that it represents any state. Often, null could just signal that a default should be used, but with an existence check, the programmer can be confidant that something/someone explicitly chose that option. In the case of a config, for example, you can be sure the user chose a particular value (which may be the default) versus having missed it altogether. There are cases where that knowledge is very important to safe programming. Consider that you’ve refactored a delete function to add support for non-permanent deletion. You’ve done this by adding an optional parameter: function DeleteFile($file, $permanentlyDelete = null) {} If $permanentlyDelete is null, the default action specified in a config is used. The problem is, when the param is null, there’s no way for the function to know for sure what the programmer of the caller intended. There are two possibilities: 1 The programmer intentionally passed null because the config should be used 2 The programmer forgot to consider that case after the refactor, so null is automatically being used Ultimately, the function just follows orders, of course, but doing it this way is dangerous because it’s all too easy to hit situation 2, which means files could be permanently deleted in error. As the programmer doing the refactor, it’s better to make the parameter mandatory, which forces a revisit to every caller to make the true/false/null decision explicit. In this case, the extra bit of intelligence comes from forcing the caller to pass something. If the param is optional, we have no idea. It’s the same thing with exists() on a variable: it gives us extra intelligence about *why* a variable is null, whether it’s because of an actual decision somewhere or because of an error or oversight. And it provides that intelligence without fussing with PHP errors. > - you have no way of presetting the variable to some other terminal value to > detect when it is explicitly set If the variables are injected before your code runs, it’s too late. At that point, if you try to set a magic sentinel value, you’re just overwriting the real value if there is one. It would work, however, if you can pre-initialize before the injection: $foo = 'hope this gets overwritten'; require('/some/config.php'); if ($foo === 'hope this gets overwritten') { //uh-oh } I’m not really a fan of this pattern, though. If there’s going to be a magic value to indicate that no better value was set, null is the ideal choice: $foo = null; require('/some/config.php'); if ($foo === null) { //uh-oh } Here, we’ve used null exactly as intended. Unfortunately, this breaks if null already has meaning, even if it's just "go with a default”. And, of course, it also throws the annoying undefined error that we’re trying to avoid :-). This is where exists() comes in and cures all: require('/some/config.php'); if (!exists($foo)) { //uh-oh } It’s cleaner and shorter, but it also lets us again use null as intended, rather than coming up with some whacky magic value that by necessity is completely unrelated to the variable’s purpose yet is hopefully understandable in meaning to future programmers passing through (not *too* hard with a string, as above, but much tricker with non-strings). >> In a dynamic >> language like PHP, I really don’t understand how folks can *not* see >> that as an oversight. > > I've not found evidence that any other language treats variables in the > proposed way, so it really doesn't feel like an "oversight" to me, but a > request for a new and quite unusual feature. In JavaScript, there’s this: if (typeof never_heard_of_me === 'undefined') { //uh-oh } albeit with a couple of minor caveats. > But yes, from a completeness point of view, it does make some sense. My fear > is that the people who don't understand why isset() does what it does will > think it's the answer to some imaginary problem, and end up more confused > than ever when their code breaks. Haven’t we already arrived here with isset() confusion? ;-) Seriously, I think updating the docs on the isset() page would help mitigate this. From this: "Determine if a variable is set and is not NULL. "If a variable has been unset with unset(), it will no longer be set. isset() will return FALSE if testing a variable that has been set to NULL. Also note that a null character ("\0") is not equivalent to the PHP NULL constant." to something like this: "Determine if a variable exists in the current scope and is not NULL. To test only whether a variable exists, use exists(). "If unset() is called on a variable, it will no longer exist. isset() will return FALSE if testing a variable that has been set to NULL. Also note that a null character ("\0") is not equivalent to the PHP NULL constant." Obviously, other related pages could be updated similarly. > If the function had been proposed as a debugging aid, maybe filed under > Reflection or debug_variable_is_initialised, I might have more sympathy, but > that's not how it's been presented. Again, I don’t see this as something that directly controls business logic. I see it as something that supports defensive programming, improved error handling, and improved user experience. Regardless, as we all know, every language feature can be abused, but that doesn’t automatically mean we shouldn’t have those features. Even something as widely derided as goto has its niche, after all (albeit, a very, very small niche). In this case, not only is the feature useful, but it nicely complements the array of related functionality to provide a more complete feature set. -Bob
signature.asc
Description: Message signed with OpenPGP using GPGMail