• Willkommen im Zend Framework Forum

    ZF1 Zend Framework 1 + ZF2 Zend Framework 2

    Das Zend Framework Forum ist seit 2006 die erste Anlaufstelle für Zend Framework Entwickler in Deutschland. Mit über 70.000 Beiträgen und einer steigenden Nutzerzahl bietet das Forum hilfreiche Themen und ZF-Tutorials für professionelle Entwickler, fortgeschrittene Programmierer sowie Zend Framework Einsteiger.
    Wenn dies Dein erster Besuch in der Zend Framework Community ist, lies bitte zuerst die Hilfe - FAQ durch. Du musst Dich registrieren, bevor Du Beiträge verfassen kannst. Klicke oben auf 'Registrieren', um die Registrierung zu starten. Du kannst auch jetzt schon Beiträge lesen. Hier im Forum findest Du die Zend Framework Hilfe, die Du suchst!

    Grüße an alle Zend Framework Entwickler. Das Team vom Zend Framework Forum!

    Drupal Agentur

ZF2: Factory testen

tector

New member
Hallo,

ich möchte eine Factory testen die mir eine Entity erstellt. Da ist nicht viel dran aber ich kriege es nicht zum laufen.
Ist es überhaupt notwendig eine Factory zu testen? Ich finde wirklich nirgends im Netz Referenzen dazu... :/

Hier ist was ich bis jetzt habe:

/module/Techtree/Entity/BuildingFactory.php:

PHP:
<?php
namespace Techtree\Entity;

use Zend\Db\ResultSet\HydratingResultSet;
use Zend\Stdlib\Hydrator\Reflection as ReflectionHydrator;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\Db\TableGateway\TableGateway;

class BuildingFactory implements FactoryInterface
{
    /**
     *
     * @param ServiceLocatorInterface $serviceLocator
     * @return \Techtree\Entity\Building
     */
    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        $db = $serviceLocator->get('Zend\Db\Adapter\Adapter');

        $resultSetPrototype = new HydratingResultSet(
            new ReflectionHydrator, new Building()
        );
        $table  = new TableGateway('building', $db, null, $resultSetPrototype);
        $mapper = new Building($table);
        return $mapper;
    }
}
/module/TechtreeTest/Entity/BuildingFactoryTest.php:

PHP:
<?php
namespace TechtreeTest\Service;

use PHPUnit_Framework_TestCase;
use TechtreeTest\Bootstrap;
use Techtree\Entity\BuildingFactory;

class BuildingFactoryTest extends PHPUnit_Framework_TestCase
{
    public function setUp()
    {
        $this->sm = Bootstrap::getServiceManager();
        $this->sm->setAllowOverride(true);

        $servicesToMock = array(
            'Zend\Db\Adapter\Adapter' => 'Zend\Db\Adapter\Adapter'
            #'Techtree\Entity\Building' => 'Techtree\Entity\Building'
        );
        foreach ($servicesToMock as $key => $serviceName) {
            $this->sm->setService(
                $key,
                $this->getMockBuilder($serviceName)
                     ->disableOriginalConstructor()
                     ->getMock()
            );
        }

        $this->sm->setFactory('Techtree\Entity\BuildingFactory', 'Techtree\Entity\BuildingFactory');

    }

    public function testCreateService()
    {
        $this->assertInstanceOf(
            "Techtree\Entity\Building",
            $this->sm->get('Techtree\Entity\Building')
        );
    }

}

Ich versuche also einfach die Entity über den ServiceManager zu erzeugen um die Factory zu testen, denn diese wird dann ja aufgerufen und per createService die Entity erstellt. So sollte es sein..
Vielleicht ist mein Ansatz ja auch totaler Quatsch, idk...

Jedenfalls bekomme ich die Fehlermeldung, dass die Entity nicht vom ServiceManager gefunden/erzeugt werden kann:

Code:
1) TechtreeTest\Service\BuildingFactoryTest::testCreateService
Zend\ServiceManager\Exception\ServiceNotFoundException: Zend\ServiceManager\ServiceManager::get was unable to fetch or create an instance for Techtree\Entity\Building

/home/tector/workspace/nouron/nouron/vendor/zendframework/zendframework/library/Zend/ServiceManager/ServiceManager.php:529
/home/tector/workspace/nouron/nouron/module/Techtree/test/TechtreeTest/Entity/BuildingFactoryTest.php:35
Vielen Dank für jede Hilfe im Voraus :)

EDIT: ich hab den aktuellen Stand in einen neuen GIT-Branch namens 'unittests' geschoben und nach GitHub gepusht: https://github.com/nouron/nouron/tree/unittests
 
Zuletzt bearbeitet:

Kaiuwe

Super-Moderator
Die Aufgabe für den Test beschreibst du wie folgt:
ich möchte eine Factory testen die mir eine Entity erstellt.
Aber dann:
Ich versuche also einfach die Entity über den ServiceManager zu erzeugen…
Warum brauchst du dafür den „ServiceManager“? Dieser ist doch nicht Teil deines zuvor beschriebenen Tests.

Ich kann leider auch nicht deinem Code folgen.
PHP:
$resultSetPrototype = new HydratingResultSet(
    new ReflectionHydrator, new Building()
);
$table  = new TableGateway('building', $db, null, $resultSetPrototype);
$mapper = new Building($table);
return $mapper;
Wieso steht hier „Mapper“, wenn du es in deinem Text aber „Entity“ nennst und auch der „Namespace“ so lautet?

Schaue ich bei Github mal schnell über den Quellcode, dann kommen noch mehr Fragen:
  • Wieso gibt es eine abstrakte „Entity“?
  • Wieso wird hier überhaupt ein „TableGateway“ übergeben, denn ein möglichen Empfang oder Verarbeitung kann ich nicht entdecken. (in „Building“, „AbstractTechnology“ und „AbstractEntity“ ist zumindest nichts zu sehen)
  • Wieso greifst du mit dem Hydrator „Reflection“ direkt auf die Attribute zu, wenn du doch einfach, per „ClassMethods“-Hydrator, die vorhandenen Methoden verwenden kannst? (Da du sogar alle Attribute öffentlich setzt, würde auch „ObjectProperty“ reichen.)
 

tector

New member
Hallo Kaiuwe, vielen Dank für die Antwort. Da stellst du ein paar richtige und wichtige Fragen und zeigst Probleme auf, die ich bisher teilweise einfach übersehen oder hingenommen haben:

Warum brauchst du dafür den „ServiceManager“? Dieser ist doch nicht Teil deines zuvor beschriebenen Tests.
Das ist genau die Frage. Der Service Manager verwendet doch die Factory um die Entity zu erstellen. Ich hatte diesen Umweg gewählt da es mir nicht geglückt ist eine Instanz der Factory zu erzeugen weil der übergebene Parameter an die createService() der Factory-Klasse nicht dem ServiceLocatorInterface entsprach. (Ich hatte versucht den ServiceManager zu übergeben der ja eigentlich das Interface implementieren sollte aber irgendwie ging das nicht).

Wieso steht hier „Mapper“, wenn du es in deinem Text aber „Entity“ nennst und auch der „Namespace“ so lautet?
Das ist nur eine schlechte Wahl des Variablennamens aus irgendwelchen Gründen die ich nicht mehr weiß.. werde den Namen in $entity ändern.

Wieso gibt es eine abstrakte „Entity“?
Hm.. darüber hab ich mir lange keine Gedanken gemacht und wenn ich das jetzt so sehe welche Funktionen in der abstrakten Klasse stehen, dann sehe ich, dass ich diese eigentlich wirklich nicht brauche. Danke für den Hinweis :)
Damals hatte ich diese exchangeArray-Funktion aus einem Tutorial übernommen und fand das sehr unschön in jeder Entity den gleichen Code zu wiederholen.

Wieso wird hier überhaupt ein „TableGateway“ übergeben, denn ein möglichen Empfang oder Verarbeitung kann ich nicht entdecken. (in „Building“, „AbstractTechnology“ und „AbstractEntity“ ist zumindest nichts zu sehen)
Ebenso etwas was ich in einem Tutorial gesehen habe und so übernommen und seither mitgeschliffen habe.. Das muss ich mal überprüfen...
Wieso greifst du mit dem Hydrator „Reflection“ direkt auf die Attribute zu, wenn du doch einfach, per „ClassMethods“-Hydrator, die vorhandenen Methoden verwenden kannst? (Da du sogar alle Attribute öffentlich setzt, würde auch „ObjectProperty“ reichen.)
Das mit den Hydratoren ist so eine Sache.. das hatte ich damals nicht verstanden. Der Reflection-Hydrator wird halt in den gängigen Tutorials und Büchern verwendet - und oft wird nicht auf andere Hydrator-Arten hingewiesen...
Aber du hast recht: dein Vorschlag klingt gut und ich werde mal den ClassMethods-Hydrator versuchen. Dann kann ich die Attribute auch wieder private machen..


EDIT:
Nachdem ich nun einige deiner Hinweise beachtet habe scheint der Test nun zu funktionieren *yippieh*. Ich werd das noch weiter prüfen und andere Tests auch diesbgzl. anpassen und meld mich dann nochmal :)
 
Zuletzt bearbeitet:

Kaiuwe

Super-Moderator
Ich hatte versucht den ServiceManager zu übergeben der ja eigentlich das Interface implementieren sollte aber irgendwie ging das nicht.
Ein Beispiel:
PHP:
public function testCreateService()
{
    $sm = new Zend\ServiceManager\ServiceManager(
        new Zend\Mvc\Service\ServiceManagerConfig(array(
                'abstract_factories' => array(
                    'Zend\Db\Adapter\AdapterAbstractServiceFactory',
                ),
            )
        )
    );

    $sm->setService(
        'Config',
        array(
            'db' => array(
                'adapters' => array(
                    'Zend\Db\Adapter\Adapter' => array(
                        'driver' => 'mysqli',
                    ),
                ),
            ),
        )
    );

    $factory = new \FooFactory();
    $result = $factory->createService($sm);

    $this->assertTrue($result instanceof \Foo);
}
Damals hatte ich diese exchangeArray-Funktion aus einem Tutorial übernommen und fand das sehr unschön in jeder Entity den gleichen Code zu wiederholen.
Das kommt bestimmt aus dem Quick-Start-Tutorial. Verwende lieber einen „Hydrator“, denn das ist einfacher und erspart dir diese Methoden. (Das Quick-Start-Tutorial soll auch in der Richtung überarbeitet werden.)

Ebenso etwas was ich in einem Tutorial gesehen habe und so übernommen und seither mitgeschliffen habe.. Das muss ich mal überprüfen...
Wahrscheinlich ebenfalls aus dem Quick-Start-Tutorial, aber hier wird nicht das „TableGateway“-Objekt an die Entität weitergereicht. Ergibt auch keinen Sinn, denn die Entität muss und soll nicht wissen, wie und ob diese gespeichert wird.

Das mit den Hydratoren ist so eine Sache.. das hatte ich damals nicht verstanden.
Ist ganz einfach: Objekte mit Daten befüllen und Daten aus Objekte extrahieren. Mehr ist es nicht. ;)

Der Reflection-Hydrator wird halt in den gängigen Tutorials und Büchern verwendet - und oft wird nicht auf andere Hydrator-Arten hingewiesen...
Kennst du noch die Quelle, wo du das gelesen hast?
 
Zuletzt bearbeitet:

tector

New member
Danke Kaiuwe nochmal. Deine Fragen und Anmerkungen haben mich in die richtige Richtung gestubst und nun ist mehr als ein Groschen bei mir gefallen ;)

Die diversen EntityFactory-Klassen sind jetzt viel einfacher: die createService()-Methode gibt einfach nur noch new Entity() zurück. (Allerdings stelle ich mir die Frage ob eine so simple Factory überhaupt Sinn macht... ) Somit besteht keinerlei Abhängigkeit mehr zum Datenbankadapter und Co. und die Tests sind somit auch viel einfacher.

Das mit den Hydratoren habe ich jetzt auch gecheckt.. muss das im Code aber noch anpassen.

Kennst du noch die Quelle, wo du das gelesen hast?
Nein, nicht mehr genau. Wenn es im ZF2 Tutorial war wurde es vielleicht in der Zwischenzeit geändert/erweitert. Ich hatte gerade nochmal schnell im ZF2 Praxisbuch vom Ralf Eggert nachgeschaut und da wurde nur der ReflectionHydrator angesprochen (wenn ich nichts übersehen habe). Im EBook "Webentwicklung mit dem ZF2" von Michael Romer steht auch nur was vom ReflectionHydrator. (Das Buch ist eine EarlyAccess-Version und sollte noch erweitert werden aber der Typ hat seit über einem Jahr nichts mehr dran gemacht!)
 
Zuletzt bearbeitet:
Oben