سؤال

هل أنا محق في التفكير في ذلك IteratorAggregate يوفر فقط صفيف يشبه قرأ الوصول إلى كائن؟ إذا كنت بحاجة إلى الكتابة إلى الكائن والرجوع ، فأنا بحاجة إلى استخدام Iterator?

يتبع إظهار كائن iteratoraggregate خطأ فادح عند محاولة إضافة عنصر جديد:

<?

class foo implements IteratorAggregate {

    public $_array = array('foo'=>'bar', 'baz'=>'quux');

    public function getIterator() {
        return new ArrayIterator($this->_array);
    }

} // end class declaration


$objFoo = & new foo();

foreach ( $objFoo as $key => $value ) {
        echo "key: $key, value: $value\n";
}

$objFoo['reeb'] = "roob";

foreach ( $objFoo as $key => $value ) {
        echo "key: $key, value: $value\n";
}

$ php test.php
key: foo, value: bar
key: baz, value: quux

Fatal error: Cannot use object of type foo as array in /var/www/test.php on line 20

Call Stack:
    0.0009      57956   1. {main}() /var/www/test.php:0
هل كانت مفيدة؟

المحلول

لما يستحق ، بالنظر إلى فئة العينة الخاصة بك ، يمكن أن تستفيد للتو من ArrayObject (الذي ينفذ IteratorAggregate و ArrayAccess كما نريد).

class Foo extends ArrayObject
{
    public function __construct()
    {
        parent::__construct(array('foo' => 'bar', 'baz' => 'quux'));
    }
}

$objFoo = new Foo();

foreach ( $objFoo as $key => $value ) {
    echo "$key => $value\n";
}

echo PHP_EOL;

$objFoo['foo'] = 'badger';
$objFoo['baz'] = 'badger';
$objFoo['bar'] = 'badger';

foreach ( $objFoo as $key => $value ) {
    echo "$key => $value\n";
}

مع وجود الإخراج ، كما هو متوقع:

foo => bar
baz => quux

foo => badger
baz => badger
bar => badger

نصائح أخرى

حسنًا ، الآن بعد أن فهمت ما تعنيه بشكل أفضل ، دعني أحاول أن أشرح هنا.

تنفيذ ايتير (إما عبر Iterator أو IteratorAggregate الواجهة) ستضيف وظائف فقط عند التكرار. ماذا يعني ذلك ، هل يؤثر فقط على القدرة على الاستخدام foreach. لا يتغير أو يغير أي استخدام آخر للكائن. لا يوجد أي منهما يدعم مباشرة "الكتابة" إلى المتكرر. أقول مباشرة ، حيث لا يزال بإمكانك الكتابة إلى الكائن الأساسي أثناء التكرار ، ولكن ليس داخل foreach.

هذا يعني أن:

foreach ($it as $value) {
    $it->foo = $value;
}

سيعمل بشكل جيد (لأنه يكتب إلى الكائن الأصلي).

في حين

foreach ($it as &$value) {
    $value = 'bar';
}

على الأرجح لن تعمل لأنها تحاول الكتابة من خلال التكرار (الذي لم يتم دعمه مباشرة. قد يكون من الممكن القيام به ، لكنه ليس التصميم الأساسي).

لمعرفة السبب ، دعونا نلقي نظرة على ما يجري وراء الكواليس مع ذلك foreach ($obj as $value). إنه متطابق بشكل أساسي مع:

إلى عن على Iterator الطبقات:

$obj->rewind();
while ($obj->valid()) {
    $value = $obj->current();
    // Inside of the loop here
    $obj->next();
}

إلى عن على IteratorAggregate الطبقات:

$it = $obj->getIterator();
$it->rewind();
while ($it->valid()) {
    $value = $it->current();
    // Inside of the loop here
    $it->next();
}

الآن ، هل ترى لماذا لا يتم دعم الكتابة؟ $iterator->current() لا يعيد مرجع. لذلك لا يمكنك الكتابة إليها مباشرة باستخدام foreach ($it as &$value).

الآن ، إذا كنت ترغب في القيام بذلك ، ستحتاج إلى تغيير المكرر لإرجاع إشارة من current(). ومع ذلك ، فإن ذلك من شأنه أن يكسر الواجهة (لأنه سيغير توقيع الأساليب). لذلك هذا غير ممكن. وهذا يعني أن الكتابة إلى التكرار غير ممكن.

ومع ذلك ، أدرك أنه يؤثر فقط على التكرار نفسه. لا علاقة له بالوصول إلى الكائن من أي سياق آخر أو في أي مانور آخر. $obj->bar سيكون هو نفسه بالضبط لكائن Iterator كما هو الحال بالنسبة لكائن غير مؤلف.

الآن ، هناك فرق بين Iterator و IteratorAggregate. انظر إلى الوراء في while معادلات ، وقد تكون قادرًا على رؤية الفرق. لنفترض أننا فعلنا هذا:

foreach ($objFoo as $value) {
    $objFoo->_array = array();
    print $value . ' - ';
}

ماذا قد يحدث؟ مع ال Iterator, ، سوف طباعة القيمة الأولى فقط. ذلك لأن المؤلف يعمل مباشرة على الكائن لكل تكرار. وبما أنك غيرت ما يكرره الكائن (الصفيف الداخلي) ، فإن التكرار يتغير.

الآن ، إذا $objFoo هو IteratorAggregate, ، ستطبع جميع القيم التي كانت موجودة في بداية التكرار. ذلك لأنك قمت بنسخة من المصفوفة عند إرجاعك new ArrayIterator($this->_array);. حتى تتمكن من الاستمرار في التكرار على الكائن بأكمله كما كان في بداية التكرار.

لاحظ هذا الفرق الرئيسي. كل تكرار من Iterator سوف يعتمد على حالة الكائن في تلك المرحلة الزمنية. كل تكرار من IteratorAggregate هل تعتمد على حالة الكائن في بداية التكرار.* هل هذا منطقي؟

الآن ، أما بالنسبة لخطأك المحدد. لا علاقة له بالتكرار على الإطلاق. أنت تحاول الوصول إلى (كتابة بالفعل) متغير خارج التكرار. لذلك لا يشمل التكرار على الإطلاق (باستثناء أنك تحاول التحقق من النتائج عن طريق التكرار).

أنت تحاول التعامل مع الكائن كصفيف ($objFoo['reeb'] = 'roob';). الآن ، بالنسبة للكائنات العادية ، هذا غير ممكن. إذا كنت تريد القيام بذلك ، فأنت بحاجة إلى تنفيذ ArrayAccess واجهه المستخدم. لاحظ أنه لا يتعين عليك تحديد جهاز تكرار من أجل استخدام هذه الواجهة. كل ما تفعله هو توفير القدرة على الوصول إلى عناصر محددة لكائن باستخدام مجموعة مثل بناء الجملة.

لاحظ قلت صفيف مثل. لا يمكنك معاملته مثل صفيف بشكل عام. sort الوظائف لن تعمل أبدًا. ومع ذلك ، هناك بعض الواجهات الأخرى التي يمكنك تنفيذها لجعل كائن أكثر مثل الصفيف. واحد هو Countable واجهه المستخدم التي تتيح القدرة على استخدام count() وظيفة على كائن (count($obj)).

للحصول على مثال في قلب الجمع بين كل هذه في فئة واحدة ، تحقق من ArrayObject صف دراسي. توفر كل واجهة في تلك القائمة القدرة على استخدام ميزة محددة من النواة. ال IteratorAggregate يوفر القدرة على الاستخدام foreach. ال ArrayAccess يوفر القدرة على الوصول إلى الكائن مع بناء جملة يشبه الصفيف. ال Serializable توفر الواجهة القدرة على serialize و deserialize البيانات في مانور معين. ال Countable تتيح الواجهة حساب الكائن تمامًا مثل صفيف.

لذلك لا تؤثر هذه الواجهات على الوظيفة الأساسية للكائن بأي شكل من الأشكال. لا يزال بإمكانك فعل أي شيء معه يمكنك القيام به لكائن عادي (مثل $obj->property أو $obj->method()). ومع ذلك ، فإن ما يفعلونه هو توفير القدرة على استخدام الكائن مثل صفيف في أماكن معينة في القلب. يوفر كل منها الوظيفة في نطاق صارم (وهذا يعني أنه يمكنك استخدام أي مجموعة منها ترغب count() هو - هي). هذه هي القوة الحقيقية هنا.

أوه ، و تعيين قيمة الإرجاع new بالرجوع يتم إهماله, ، لذلك ليست هناك حاجة للقيام بذلك ...

لذلك ، بالنسبة لمشكلتك المحددة ، إليك طريقة واحدة حولها. أنا ببساطة نفذت ArrayAccess واجهة في فصلك بحيث $objFoo['reeb'] = 'roob'; سيعمل.

class foo implements ArrayAccess, IteratorAggregate {

    public $_array = array('foo'=>'bar', 'baz'=>'quux');

    public function getIterator() {
        return new ArrayIterator($this->_array);
    }

    public function offsetExists($offset) {
        return isset($this->_array[$offset]);
    }

    public function offsetGet($offset) {
        return isset($this->_array[$offset]) ? $this->_array[$offset] : null;
    }

    public function offsetSet($offset, $value) {
        $this->_array[$offset] = $value;
    }

    public function offsetUnset($offset) {
        if (isset($this->_array[$offset]) {
            unset($this->_array[$offset]);
        }
    }
}

ثم ، يمكنك تجربة الكود الحالي الخاص بك:

$objFoo = & new foo();

foreach ( $objFoo as $key => $value ) {
    echo "key: $key, value: $value\n";
}

$objFoo['reeb'] = "roob";

foreach ( $objFoo as $key => $value ) {
    echo "key: $key, value: $value\n";
}

ويجب أن تعمل بشكل جيد:

key: foo, value: bar
key: baz, value: quux
key: foo, value: bar
key: baz, value: quux
key: reeb, value: roob

يمكنك أيضًا "إصلاحه" عن طريق تغيير $objFoo->_array خاصية مباشرة (تماما مثل OOP العادية). ببساطة استبدال الخط $objFoo['reeb'] = 'roob'; مع $objFoo->_array['reeb'] = 'roob';. سوف ينجز نفس الشيء ....

  • لاحظ أن هذا هو السلوك الافتراضي. يمكنك اختراق معا تكرار داخلي (أعيد المتكرر IteratorAggreagate::getIterator) وهذا يعتمد على حالة الكائنات الأصلية. ولكن هذا ليس كيف يتم ذلك عادة
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top