PHP OO模型的鲜为人知的“功能”

The vast majority of today’s applications written in PHP are object-oriented, and in general the core OOP concepts are pretty well understood by PHP developers. This article pushes the boundary of your understanding and shows you some tricks, or potential pitfalls depending on your perspective, of OOP in PHP.

如今,用PHP编写的大多数应用程序都是面向对象的,并且通常,PHP开发人员已经很好地理解了核心OOP概念。 本文超越了您的理解范围,并向您展示了PHP中OOP的一些技巧或可能的陷阱,具体取决于您的观点。

接口和特性的继承 (Inheritance for Interfaces and Traits)

Let’s begin in familiar territory: interfaces. An interface in PHP allows us to define a contract that any object implementing this interface must itself implement. But did you know that interfaces can also inherit other interfaces, and that either the parent or child interface can be implemented by a class?

让我们从熟悉的领域开始:接口。 PHP中的接口允许我们定义一个契约,任何实现此接口的对象都必须自己实现。 但是您知道接口也可以继承其他接口,并且父接口或子接口可以由类实现吗?

Consider this code, which defines an interface, another which extends it, and a class that implements the child interface:

考虑以下代码,该代码定义一个接口,另一个对它进行扩展,以及一个实现子接口的类:

<?php
interface Reversible
{
    function reverse($target);
}

interface Recursible extends Reversible
{
    function recurse($target);
}

class Tricks implements Recursible
{
    public function recurse($target) {
        // something cool happens here
    }

    public function reverse($target) {
        // something backward happens here
    }
}

I’ve defined an interface named Reversible, and another one named Recursible that extends it. When I implement Recursible in the Tricks class, both the recurse() method from the Recursible interface and the reverse() method from the Reversible interface must be present.

我定义了一个名为Reversible的接口,另一个名为Recursible的接口Recursible了扩展。 当我实施RecursibleTricks类,无论是recurse()从方法Recursible接口和reverse()从方法Reversible接口必须存在。

This is a useful technique to employ when an interface will contain methods that are used across one set of classes, but another set of classes need these and an additional set of methods. You can make a composite interface as shown here rather than implement two interfaces.

当接口包含在一组类中使用的方法,而另一组类需要这些方法和另一组方法时,这是一种有用的技术。 您可以按如下所示制作一个复合接口,而不是实现两个接口。

Traits offer a similar pattern. If you haven’t had an opportunity to use traits yet, these look like classes and can contain complete method definitions that can be applied to any class(es) in your application, without the need to inherit from a common location. We’re often taught that it’s better to move code to a common parent class than to copy-and-paste between classes, but sometimes classes aren’t related and the inheritance is false. Traits are a great feature because they allow us to re-use code even where objects aren’t similar enough to justify inheritance.

性状提供了类似的模式。 如果您还没有使用特质的机会,那么它们看起来就像类,并且可以包含完整的方法定义,这些定义可以应用于应用程序中的任何类,而无需从公共位置继承。 我们经常被告知,将代码移至公共父类比在类之间进行复制和粘贴更好,但有时类不相关且继承是错误的。 特性是一个很棒的功能,因为它使我们即使在对象之间的相似度不足以证明继承合理性的情况下也可以重用代码。

Let’s look at a simple trait example. (Warning: the zany naming scheme from the previous example is still in evidence.)

让我们看一个简单的特征示例。 (警告:上一个示例中的滑稽名字命名方案仍然很明显。)

<?php
trait Reversible
{
    public function reverse($target) {
        return array_reverse($target);
    }
}

The syntax for traits looks very much like a class, and indeed traits can contain both properties and methods – including abstract methods. These are then applied in a class that brings the trait into it by using the use keyword… or they can also be applied to a trait, as we see here:

特质的语法看起来非常像类,并且特质实际上可以包含属性和方法,包括抽象方法。 然后,将它们应用到use关键字将特征引入其中的类中……或者也可以将它们应用于特征,如我们在此处看到的:

<?php
trait RecursivelyReversible
{
    use Reversible;

    public function reverseRecursively($target) {
        foreach($target as $key => $item) {
            if(is_array($item)) {
                $target[$key] = $this->reverseRecursively($item);
            }
        }
        return $this->reverse($target);
    }
}

Now we’ve got a trait that uses another trait and adds a method of its own. This method calls a method in the first trait. At this point, we can apply the trait to the class, and since the traits contain the feature I want to illustrate here, the class doesn’t contain anything else.

现在,我们有了一个使用另一个特征的特征,并添加了自己的方法。 此方法调用第一个特征中的方法。 此时,我们可以将特征应用于类,并且由于特征包含我要在此处说明的功能,因此该类不包含其他任何内容。

<?php
class Mirror
{
    use RecursivelyReversible;
}

$array = [1, "green", "blue", ["cat", "sat", "mat", [0,1,2]]];
$reflect = new Mirror();
print_r($reflect->reverseRecursively($array));

If you run the code, you’ll see that not only does the top-level of the array get reversed, but that PHP also drills into and reverses all of the child elements as well.

如果运行代码,您不仅会看到数组的顶层被反转,而且PHP还将钻取并反转所有子元素。

私有财产有多私有? (How Private is a Private Property?)

So you thought that a private property was only accessible from within the current object? Not quite true! Actually the restriction is only on class name, so objects of the same class can access one another’s private properties and methods. To illustrate this, I’ve created a class with a private property and a public method that accepts an instance of the same class of object as an argument:

因此,您认为只能从当前对象内部访问私有属性吗? 不太正确! 实际上,限制仅针对类名,因此同一类的对象可以访问彼此的私有属性和方法。 为了说明这一点,我创建了一个具有私有属性和公共方法的类,该方法接受同一对象类的实例作为参数:

<?php
class Storage
{
    private $things = [];

    public function add($item) {
        $this->things[] = $item;
    }

    public function evaluate(Storage $container) {
        return $container->things;
    }
}

$bucket = new Storage();
$bucket->add("phone");
$bucket->add("biscuits");
$bucket->add("handcream");

$basket = new Storage();
print_r($basket->evaluate($bucket));

You might think that $basket would not have access to $bucket‘s private data, but actually the code above works just fine! Asking whether this behavior is a gotcha or a feature is like asking if a plant is a flower or a weed; it depends on your intention and perspective.

您可能会认为$basket无法访问$bucket的私有数据,但是实际上上面的代码可以正常工作! 询问这种行为是陷阱还是特征,就像询问植物是花朵还是杂草一样。 这取决于您的意图和观点。

抽象类是什么样的? (What Does an Abstract Class Look Like?)

An abstract class is usually thought of as being an incomplete class; we only define partial functionality and use the abstract keyword to stop anything from attempting to instantiate it.

抽象类通常被认为是不完整的类。 我们仅定义部分功能,并使用abstract关键字阻止尝试实例化任何功能。

<?php
class Incomplete
{
    abstract public function notFinished();
}

If you try to instantiate Incomplete, you’ll see the following error:

如果尝试实例化Incomplete ,将看到以下错误:

PHP Fatal error: Class Incomplete contains 1 abstract method
and must therefore be declared abstract or implement the
remaining methods (Incomplete::notFinished) in /home/lorna/
sitepoint/oop-features/incomplete.php on line 5

The message is self-explanatory, but now consider another class:

该消息是不言自明的,但是现在考虑另一个类:

<?php
abstract class PerfectlyGood
{
    public function doCoolStuff() {
        // cool stuff
        return true;
    }
}

It’s a perfectly valid class aside from the abstract keyword. In fact, you can mark any class as abstract if you wish. Other classes can extend it, but it can’t itself be instantiated. This can be a useful device for library designers who want developers to extend their classes rather than making use of them directly. Zend Framework has a rich tradition of abstract classes for exactly this reason.

除了abstract关键字,这是一个非常有效的类。 实际上,您可以根据需要将任何类标记为抽象。 其他类可以扩展它,但是它本身不能被实例化。 对于希望开发人员扩展其类而不是直接使用它们的类的库设计人员来说,这可能是一个有用的设备。 正是由于这个原因,Zend Framework具有丰富的抽象类传统。

类型提示不会自动加载 (Type Hints Don’t Autoload)

We use type hints to ensure that an incoming parameter to a method meets certain requirements by giving the name of a class or interface that it must be (or be related to). However, PHP does not call the autoloader if the class or interface given in a type hint hasn’t been declared yet; we’ll just see the missing class declaration error.

我们使用类型提示来确保方法的传入参数通过提供必须(或与之相关)的类或接口的名称来满足某些要求。 但是,如果尚未声明类型提示中给出的类或接口,则PHP不会调用自动加载器。 我们只会看到缺少的类声明错误。

<?php
namespace MyNamespace;

class MyException extends Exception
{
}

class MyClass
{
    public function doSomething() {
        throw new MyException("you fool!");
    }
}

try {
    $myclass = new MyClass();
    $myclass->doSomething();
    echo "that went well";
}
catch (Exception $e) {
    echo "uh oh... " . $e->getMessage();
}

The class name in the catch clause is actually a type hint, but since we didn’t indicate that the Exception class was in the top-level namespace, PHP thinks we mean MyNamespaceException which doesn’t exist. The missing class doesn’t raise an error, but our exception now misses the catch clause. You might expect that we’d see a missing MyNamespaceException message, but instead we get the unspeakably ugly “Uncaught Exception” error:

catch子句中的类名称实际上是一个类型提示,但是由于我们没有指出Exception类位于顶级名称空间中,因此PHP认为我们的意思是MyNamespaceException不存在。 缺少的类不会引发错误,但是我们的异常现在缺少catch子句。 您可能希望我们会看到一条丢失的MyNamespaceException消息,但是相反,我们收到了难以形容的难看的“未捕获异常”错误:

Fatal error: Uncaught exception 'MyNameSpaceMyException'
with message 'you fool!' in /home/lorna/sitepoint/
oop-features/namespaced_typehints.php:11

This behavior makes complete sense if you think about it – if something named in a type hint isn’t already loaded, then by definition the incoming parameter cannot match it. I had to make this mistake myself before I really thought about it, and it’s just a typo! If you catch Exception rather than just Exception, this works as I had originally intended.

如果您考虑一下,则此行为完全有意义-如果尚未加载类型提示中命名的内容,则根据定义,传入的参数无法与之匹配。 在我真正考虑之前,我必须自己犯这个错误,这只是一个错字! 如果您捕获到Exception而不是Exception ,那么它将按照我最初的意图工作。

最后 (And Finally)

The finally clause is a feature worth knowing about that was recently introduced in PHP 5.5. If you’ve used other programming languages with exceptions then you may have seen this construct before. There’s only ever one try block, we can have as many catch blocks as we please, and from this version of PHP we can also add a finally.

finally子句是一个值得了解的功能,最近在PHP 5.5中引入了该功能。 如果您曾经使用过其他带有例外的编程语言,那么您以前可能已经看过这种结构。 只有一个try块,我们可以根据需要拥有任意数量的catch块,并且从此版本PHP中我们还可以添加一个finally

Here’s the previous code example again, with finally added:

这又是前面的代码示例, finally添加了:

<?php
namespace MyNameSpace;

class MyException extends Exception
{
}

class MyClass
{
    public function doSomething() {
        throw new MyException("you fool!");
    }
}

try {
    $myclass = new MyClass();
    $myclass->doSomething();
    echo "that went well";
}
catch (Exception $e) {
    echo "uh oh ... " . $e->getMessage();
}
finally {
    echo "move along, nothing to see here";
}

The finally clause will always happen, regardless of whether we reached the end of the try block, entered any of the catch blocks, or if there are more uncaught exceptions on the way. In this example, the output from the finally block actually appears before the error about the uncaught exception because it’s not uncaught until we’ve ended the try/catch/finally section.

无论我们是否到达try块的末尾,是否输入了任何catch块,或者途中还有更多未捕获的异常, finally子句都将始终存在。 在此示例中, finally块的输出实际上出现在有关未捕获的异常的错误之前,因为直到我们结束了try / catch / finally部分,它才被catch

结论 (Conclusion)

Certainly some of the examples I’ve shown here are quite far-fetched, and hopefully you wouldn’t run into them every day, but I do think it’s worth knowing what edge cases look like so that you can design and build systems correctly. Understanding the building blocks can certainly help us to get our jobs done and to debug problems quickly, and I love sharing ideas to make that easier!

当然,我在这里显示的一些示例牵强附会,希望您不会每天都遇到它们,但是我确实值得知道边缘情况是什么样的,以便您可以正确地设计和构建系统。 理解构建基块无疑可以帮助我们完成工作并快速调试问题,我喜欢分享想法,以简化工作!

What gotchas have you found in PHP? Please share with the rest of us in the comments section.

您在PHP中找到了哪些陷阱? 请在评论部分与我们其他人分享。

Image via Fotolia

图片来自Fotolia

翻译自: https://www.sitepoint.com/lesser-known-features-of-phps-oo-model/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值