PHP的问题:全面阐述PHP网站设计的问题(4)
错误处理
PHP的一个独特操作符是@(实际上从DOS借用过来的),它隐藏错误。
PHP错误不提供栈轨迹。你不得不安装一个处理器生成它们。(但fatalerrors不行—见下文。)
PHP的解析错误通常只抛出解析的状态,没其它东西了,使得调试很糟糕。
PHP的解析器所指的例如。::内部作为T_PAAMAYIM_NEKUDOTAYIM,而<<操作符作为T_SL.我说“内部的”,但像上面说的,给程序员显示的::或<<出现在了错误的位置。
大多数错误处理打印给服务器日志打印一行错误日志,没人看到而一直进行。
E_STRICT看起来像那么回事,但它实际上没多少保护,没有文档显示它实际上是做什么的。
E_ALL包含了所有的错误类别—除了E_STRICT.
关于什么允许而什么不允许是古怪而不一致的。我不知道E_STRICT是怎样适用于这里的,但这些却是正确的:
试图访问不存在的对象属性,如,$foo->x.(warning)
使用变量做为函数名,或者变量名,或者类名。(silent)
试图使用未定义常量。(notice)
试图访问非对象类型的属性。(notice)
试图使用不存在的变量名。(notice)
2<“foo”(隐藏)
foreach(2as$foo);(warning)
而下面这些不行:
试图访问不存在的类常量,如$foo::x.(fatalerror)
使用字符串常量作为函数名,或变量名,或类名。(parseerror)
试图调用一个示定义函数。(fatalerror)
Leavingoffasemicolononthelaststatementinablockorfile.(parseerror)
使用list和其它准内建宏作为方法名。(parseerror)
用下标访问函数的返回值,如:foo()[0]。(parseerror;已在5.4中修复)
在列表的其他地方也有几个关于其它怪异解析错误的好例子
__toString方法不能抛出异常。如果你尝试,PHP將…呃,抛出一个异常。(实际上是个fatalerror,可以被通过的,除了…)
PHP错误和PHP异常是完全不同的物种。它们不能相互作用。
PHP错误(内部,称为trigger_error)不能被try/catch捕获。
同样,异常不能通过set_error_handler安装的错误处理器触发错误。
作为替代,有一个单独的set_exception_handler可以处理未捕获的异常,因为用try块包装你程序入口在mod_pho模块中是不可能的。
Fatal错误(例如,newClassDoesntExist())不能被任何东西捕获。大量的完全无害的操作会抛出fatal错误,由于一些有争议的原因被迫终结你的程序。关闭函数仍然运行,但它们无法获取栈轨迹(它们运行在上层),它们很难告知该程序是由一个错误还是程序的正常运行结束。
没有finally结构,使得包装代码(注册处理器,运行代码,注销处理器;monkeypatch,运行测试,unmonkeypatch)很难看,很难写。尽管OO和异常大量的复制了Java的模式,这是故意的,因为finally“在PHP上下文中,只得其形不得其神”.Huh?
函数
函数调用似乎相当昂贵。
一些内建函数与reference-returning函数交互,呃,一种奇怪的方式。
正如在别处提到的,很多看起来像函数或者看起来它们应该是函数的东西实际上是语言的构成部分,因此无法像正常函数一样的工作。
函数参数可以具有“类型提示”,基本上只是静态类型。你不能要求某个参数是int或是string或是对象或其它“核心”类型,即使每个内建函数使用这种类型,可能因为int在PHP中不是个东西吧。(查看上面关于(int)的讨论)。你也不能使用特殊的被大量内建函数使用的伪类型装饰:mixed,number,orcallback.
因此,下面:
- function foo(string $s) {}
- foo("hello world");
产生错误theerror:
PHPCatchablefatalerror:Argument1passedtofoo()mustbeaninstanceofstring,stringgiven,calledin…
你可能会注意到“类型提示”实际上并不存在;在程序中没有string类。如果你试图使用ReflectionParameter::getClass()动态测试类型提示,將会得到类型不存在,使得实际上不可能取得该类型名。
函数的返回值不能被推断
將当前函数的参数传给另一个函数(分派,不罕见)通过call_user_func_array(‘other_function’,func_get_args())完成。但func_get_args在运行时抛出一个fatal错误,抱怨它不能作为函数参数。为什么为什么这是个类型错误?(已在PHP5.3中修复)
闭包需要显示的命名每个变量为closed-over.为什么解析器不想办法解决?(Okay,it’sbecauseusingavariableever,atall,createsitunlessexplicitlytoldotherwise.)
Closed-over变量,通过和其它函数参数相同的语义”传递”。这样的话,数组和字符串等等,將以传值方式传给闭包。除非使用&.
因为闭包变量会自动传递参数,没有嵌套范围,闭包不能指向私有方法,不管是否定义在类中。(可能在5.4中修复?不清楚。)
函数没有命名参数。实际上被devs显示拒绝,因为它“会导致代码臭味”。
Functionargumentswithdefaultscanappearbeforefunctionargumentswithout,eventhoughthedocumentationpointsoutthatthisisbothweirdanduseless.(Sowhyallowit?)
向函数传递额外的参数会被忽略(除了内建函数,会抛出异常)。丢失的参数被假定为null.
”可变”函数需要func_num_args,func_get_arg,和func_get_args.这类事情没有语法。
OO
PHP的函数部分被设计成类似C,但面向对象(hoho)被设计成类似Java.我不想过分强调这有多不合谐。我还没有发现一个有大写字母的全局函数,重要的内建类使用驼峰式方法命名,并有getFoo的Java风格的属性访问器。这是门动态语言,对吗?Perl,Python,和Ruby都有一些通过代码访问”属性”的概念;PHP仅仅有笨重的__get之类的东西。类型系统围绕着低层的Java语言设计,Java和PHP’s处一时代,Java有意的做了更多限制,照搬Java,我百思不得其解。
类不是对象。元编程不得不通过字符串名指向它们,就像函数一样。
内建的类型不是对象,(不像Perl)也无法使得看起来像对象。
instanceof是个操作符,尽管很晚才增加进来,而大多数语言都建有专门的函数和语法。受Java影响吗?类不是第一类?(我不知道它们是不是。)
但有一个is_a函数。它有个可选参数指定是否允许对象实际是一个字符串命名的类。
get_class是函数;没有typeof操作符。同样有is_subclass_of.
然而,这对于内建类型无法工作,(再一次,int不是个东西)。这样,你需要is_int等等。
右值必须是变量或字面量;不能是表达式。不然会导致…一个解析错误。
clone是一个操作符?!
OO的设计是一只混合Perl和Java的怪物。
对象属性通过$obj->foo,但类属性是$obj::foo.我没见过任何其它语言这样做,或者这样做有什么用。
而,实例方法仍然能通过静态的(Class::method)调用。如果从其它方法中这么调用,会在当前$this上被看成常规的方法调用。我认为吧。
new,private,public,protected,static,等等。试图虏获Java开发者的芳心?我知道这更多是个人的品位,但我不知道为什么这些东西在一门动态语言中是必要的—在C++中,它们中的大多数是有关汇编和编译时的命名决议。
子类不能覆盖private方法。子类覆盖的公共方法也不可见,单独调用,超类的私有方法。会有问题,如在测试mocks对象时。
方法无法命名为,例如“list”,因为list()是特殊的语法(不是个函数),而解析器会被搞晕。如此暧昧的原因无从得知,而类工作得就很好。($foo->list()不是语法错误。)
如果当解析构造函数参数时抛出异常(如,newFoo(bar())而bar()抛出),构造函数不会被调用,但析构函数会。(已在PHP5.3中修复)
在__autoload和解析函数中的异常会导致fatal错误。
没有构造器或析构器。__construct是个初始化函数,像Python的__init__.无法通过调用类申请内存和创建对象。
没有默认的初始化函数。调用parent::__construct()的时候,如果父类没定义它自己的__construct方法会导致fatal错误。
OO带来了个迭代器接口,是语言规范的部分(如…as…),但该接口实际上没有内建实现(如数组)。如果你想要个数组迭代器,你必须用ArrayIterator包装它。没有内建方式能够让迭代器將其作为第一类对像工作。
类可以重载它们转化成字符串的方式,但不能重载怎样转换成数字或任何其它内建类型的方式。
字符串,数字,和数组都有字符串转换方式;语言很依赖于此。函数和类都是字符串。然而,如果没定义__toString,试图將换内建或自定义对像(甚至于一个闭包)转换成字符串会导致错误,甚至连echo都可能出错。
无法重载相等或比较操作。
实例方法中的静态变量是全局的;它们的值跨越该类的多个实例共享。