0

ファイルをインクルードする前に、いくつかの事前定義されたグローバルを必要とする PHP クラスがあります。

ファイル: includes/Product.inc.php

if (class_exists('Product')) {
    return;
}

// This class requires some predefined globals
if ( !isset($gLogger) || !isset($db) || !isset($glob) ) {
    return;
}

class Product
{
   ...
}

上記は、require_onceを使用して製品を使用する必要がある他の PHP ファイルに含まれています。ただし、製品を使用したい人は誰でも、それらのグローバルが利用可能であることを確認する必要があります。少なくともそれが考え方です。

$gLogger が null であるために発生した、Product クラス内の関数の問題を最近デバッグしました。上記の Product.inc.php を必要とするコードは、わざわざ $gLogger を作成していませんでした。問題は、$gLogger が null の場合、このクラスがどのように含まれていたかということです。

私はコード (NetBeans では xdebug) をデバッグしようとしましたが、Product.inc.php の先頭にブレークポイントを置いて調べてみました。したがって、グローバルチェックに到達することはありません。では、どのようにして初めて含まれたのでしょうか?

これは MAMP (Apache/MySQL) で動作する PHP 5.1+ です。自動ローダーが定義されていません。

有益な回答をありがとう。私の考えでは、ファイルをインクルードすると、PHP は 1 行目から 1 行ずつ実行を開始するため、グローバルが定義されていない場合はファイルをインクルードできません。チェックをコンストラクターに移動します。元の質問に基づいて、@deceze からの回答を受け入れます

4

2 に答える 2

4

ファイルは実行前に解析されます。クラスは解析によって「ロード」されますが、関数は解析後に実行されます。関数呼び出しをクラスと同じファイルに配置することにより、クラスはその関数が実行される前に常に解析され、「ロード」されるため、常にtrue.

を使用して常にファイルをインクルードしている場合require_once(これは良いことです)、とにかくそのチェックには意味がありません。クラス定義は、一部のグローバル変数に条件付きで依存するべきではありません。ここで何をしているかを考え直してください。

于 2012-11-07T10:58:43.753 に答える
2

ここに主な問題があります:

// This class requires some predefined globals

それはあなたにとって驚くべきことかもしれませんが、あなたが実際にやりたいことは、その場合、クラスを定義するときにそれをチェックするのではなく、それをインスタンス化するときにチェックすることだと思います。

クラスがインスタンス化されると、そのコンストラクター関数が呼び出されます。これは私にとってそれをチェックするのに最適な場所のようです:

class Product
{
    public function __construct() {
        // This class requires some predefined globals
        $this->needGlobal('gLogger', 'db', 'glob');
    }

    private function needGlobal() {
        foreach (func_get_args() as $global) {
            if (!isset($GLOBALS[$global])) {
                throw new RuntimeException(sprintf('Global %s needed but not set.', $global));
            }
        }
    }

    ...
}

インスタンス化するProductと、前提条件が満たされているかどうかが自動的にチェックされます。

$blueShoes = new Product();

前提条件が満たされていない場合、これは機能しませんが、の場合は機能します。

しかし、それはあなたの問題を部分的に解決しているだけです。コードの本当の問題は、グローバル変数が機能するProduct 必要があることです。

代わりに、製品が動作するために必要なものだけを使用するようにします。

class Product
{
    private $gLogger;
    private $db;
    private $glob;

    public function __construct(LoggerInterface $gLogger, DbInterface $db, GlobInterface $glob) {
        $this->gLogger = $gLogger;
        $this->db      = $db;
        $this->glob    = $glob;
    }    

    ...
}

使用法:

$redShoes = new Product($gLogger, $db, $glob);

そして、あなたはもはや内部のグローバルなものを気にする必要はありませProductん。


コードを徐々に改善したいとコメントしました。あなたはそうすることができます、ここに方法があります。書かれているように、上記の2番目のバリアントが進むべき道ですが、現在、レガシーコードはそれと互換性がありません。いずれにせよ、Productクラスが新しいコードである場合は、依存性注入を使用して記述する必要があります。これは、レガシーコードを新しいコードから分離するために重要です。新しいコードにレガシーなものを飲み込ませたくはありません。これにより、新しいコードがレガシーコードになるため、徐々に改善することはできません。新しいレガシーコードを追加するだけです。

したがって、依存性注入を使用してクラス定義を取得します。レガシーのニーズについては、これを保護する2番目のクラスを作成します。

class ProductLegacy extends Product
{
    public function __construct() {
        // This class requires some predefined globals
        list($gLogger, $db, $glob) = $this->needGlobal('gLogger', 'db', 'glob');
        parent::__construct($gLogger, $db, $glob);
    }

    private function needGlobal() {
        $variables = array();
        foreach (func_get_args() as $global) {
            if (!isset($GLOBALS[$global])) {
                throw new RuntimeException(sprintf('Global %s needed but not set.', $global));
            }
            $variables[] = $GLOBALS[$global];
        }
        return $variables;
    }
}

ご覧のとおり、この小さなスタブは、新しい方法で物事を行うグローバルな方法をまとめています。新しいコードでクラスを使用できますProduct。古いコードとインターフェイスする必要がある場合は、ProductLegacyクラスのインスタンス化にグローバル変数で機能するクラスを使用します。

これを実行するヘルパー関数を作成して、さまざまなクラスで使用することもできます。あなたのニーズに少し依存します。古いコードと新しいコードの間に明確な線を引くことができる境界線を見つけるだけです。

于 2012-11-07T11:06:41.413 に答える