Wie kann ich Magento 2 in einem test.php Skript booten?

93

In Magento 1 konnte ich eine Datei erstellen, in der ich nur die Mage_Core_Model_AppKlasse instanziieren musste , und dann meinen "schmutzigen" Code zu Testzwecken hinzufügen.
So etwas in der Art test.php:

<?php
//some settings
error_reporting(E_ALL | E_STRICT); 
define('MAGENTO_ROOT', getcwd()); 
$mageFilename = MAGENTO_ROOT . '/app/Mage.php'; 
require_once $mageFilename; 
Mage::setIsDeveloperMode(true); 
ini_set('display_errors', 1); 
umask(0);
//instantiate the app model
Mage::app(); 
//my toy code in here.

Dann konnte ich test.phpden Browser aufrufen und sehen, was ich tue.

Wie kann ich dasselbe für Magento 2 tun?

Marius
quelle
4
Wie funktioniert der Magento 2 Cron? Vielleicht können Sie den gleichen Ansatz verwenden?
Amasty
4
Gute Idee, aber ... Code aus cron.php: $app = $bootstrap->createApplication('Magento\Framework\App\Cron', ['parameters' => ['group::']]);. Soll ich mein eigenes App-Modell erstellen?
Marius
1
schreibe einen
Komponententest
2
@Fooman. Fühlen Sie sich frei, dies als Antwort zu schreiben, aber geben Sie bitte ein Beispiel. Ich bin ein bisschen neu in Unit-Tests.
Marius

Antworten:

86

Basierend auf der Antwort von @ Flyingmana habe ich ein wenig gegraben und eine Lösung gefunden. Es scheint, für mich zu arbeiten.
Erst meine Lösung, dann ein paar Erklärungen.
Ich habe eine Datei test.phpim Stammverzeichnis meiner Magento-Instanz erstellt.

<?php
require __DIR__ . '/app/bootstrap.php';
$bootstrap = \Magento\Framework\App\Bootstrap::create(BP, $_SERVER);
/** @var \Magento\Framework\App\Http $app */
$app = $bootstrap->createApplication('TestApp');
$bootstrap->run($app);

Dann habe ich eine Datei erstellt, TestApp.phpdie an derselben Stelle wie dieser Inhalt aufgerufen wird.

<?php
class TestApp
    extends \Magento\Framework\App\Http
    implements \Magento\Framework\AppInterface {
    public function launch()
    {
        //dirty code goes here. 
        //the example below just prints a class name
        echo get_class($this->_objectManager->create('\Magento\Catalog\Model\Category'));
        //the method must end with this line
        return $this->_response;
    }

    public function catchException(\Magento\Framework\App\Bootstrap $bootstrap, \Exception $exception)
    {
        return false;
    }

}

Jetzt kann ich einfach test.phpden Browser aufrufen und alles, was in TestApp :: launch () steht, wird ausgeführt.

Nun, warum das so ist:
Die Methode createApplicationaus der Bootstrap-Klasse ist der wichtigste Teil. Es wird eine Instanz einer Anwendungsklasse erstellt. Die Methode createApplicationerwartet eine Implementierung der \Magento\Framework\AppInterface, die 2 Methoden enthält.
Also habe ich meine eigene Klasse erstellt TestApp, die diese Schnittstelle implementiert. Ich habe die Methode catchExceptionzurückgeben falseimmer , weil ich möchte , dass meine App nicht um Ausnahmen zu behandeln. Falls etwas nicht stimmt, drucken Sie es einfach auf dem Bildschirm aus.
Dann habe ich die Methode implementiert launch. Dieser wird von angerufen \Magento\Framework\App\Bootstrap::run. Diese runMethode führt fast das gleiche aus, unabhängig davon, welche Anwendung als Parameter übergeben wurde.
Das einzige, was von der Anwendung abhängt, ist diese Zeile:

$response = $application->launch();

Dies bedeutet, dass der Aufruf \Magento\Framework\App\Bootstrap::rundas Magento-Env auslöst (vielleicht machen Sie ein paar andere verrückte Sachen ... Ich habe noch nicht alles überprüft) und dann die launchMethode aus der Anwendung aufruft .
Aus diesem Grund müssen Sie Ihren gesamten Dirty-Code in diese Methode einfügen.
Dann werden die \Magento\Framework\App\Bootstrap::runAufrufe $response->sendResponse();where $responseist, was die launchMethode zurückgibt.
Deshalb return $this->_response;wird es gebraucht. Es wird nur eine leere Antwort zurückgegeben.

Ich habe meine App-Klasse erweitert, \Magento\Framework\App\Httpsodass ich bereits Anforderungs- und Antwortparameter (und andere) habe, aber Sie können Ihre Klasse dazu bringen, nichts zu erweitern. Dann müssen Sie den Konstruktor aus der \Magento\Framework\App\HttpKlasse kopieren . Fügen Sie ggf. weitere Parameter im Konstruktor hinzu.

Marius
quelle
2
Natürlich TestAppkönnte die Klasse in derselben test.phpDatei definiert worden sein , aber ich möchte es nicht so schmutzig machen :)
Marius
Ich musste parent::launch();als erste Zeile der launch()Methode hinzufügen , da es mir einen Fehler "Vorwahl nicht gesetzt" gab
OSdave
@OSdave. Ohne das hat es geklappt, als ich getestet habe. Wahrscheinlich hat sich in den neuesten Versionen etwas geändert.
Marius
@Marius, ich hatte Magento durch die schnelle Installation des Servers installiert. Und habe keine bootstrap.php in app
er.irfankhan11
1
@Butterfly Sie müssen es nicht in Ihren benutzerdefinierten Controller einfügen. Die Datei wird in index.php aufgenommen, bevor sie Ihren Controller erreicht.
Marius
54

Für schnelle / kurze / schmutzige Tests habe ich Folgendes verwendet:

use Magento\Framework\App\Bootstrap;
require __DIR__ . '/../app/bootstrap.php';

$bootstrap = Bootstrap::create(BP, $_SERVER);

$obj = $bootstrap->getObjectManager();

$state = $obj->get('Magento\Framework\App\State');
$state->setAreaCode('frontend');

$quote = $obj->get('Magento\Checkout\Model\Session')->getQuote()->load(1);
print_r($quote->getOrigData());
carco
quelle
4
das funktioniert. die anderen antworten nicht.
Ahnbizcad
1
dies löst HTTP 500 an meiner Seite aus.
Max
Funktioniert immer noch in 2.1.2. Ich musste den
Bedarfspfad
hat bei mir nicht funktioniert
Sarfaraj Sipai
20

Aufgrund der Antwort von @ Marius habe ich mir das ausgedacht .

Es funktioniert sowohl über die Kommandozeile als auch über den Browser, was ich nützlich finde.

Hier ist ein Beispielskript zum programmgesteuerten Löschen einer Kategorie.

scripts/abstract.php

<?php
use \Magento\Framework\AppInterface as AppInterface;
use \Magento\Framework\App\Http as Http;

use Magento\Framework\ObjectManager\ConfigLoaderInterface;
use Magento\Framework\App\Request\Http as RequestHttp;
use Magento\Framework\App\Response\Http as ResponseHttp;
use Magento\Framework\Event;
use Magento\Framework\Filesystem;
use Magento\Framework\App\AreaList as AreaList;
use Magento\Framework\App\State as State;

abstract class AbstractApp implements AppInterface
{
    public function __construct(
        \Magento\Framework\ObjectManagerInterface $objectManager,
        Event\Manager $eventManager,
        AreaList $areaList,
        RequestHttp $request,
        ResponseHttp $response,
        ConfigLoaderInterface $configLoader,
        State $state,
        Filesystem $filesystem,
        \Magento\Framework\Registry $registry
    ) {
        $this->_objectManager = $objectManager;
        $this->_eventManager = $eventManager;
        $this->_areaList = $areaList;
        $this->_request = $request;
        $this->_response = $response;
        $this->_configLoader = $configLoader;
        $this->_state = $state;
        $this->_filesystem = $filesystem;
        $this->registry = $registry;
    }

    public function launch()
    {
        $this->run();
        return $this->_response;
    }

    abstract public function run();

    public function catchException(\Magento\Framework\App\Bootstrap $bootstrap, \Exception $exception)
    {
        return false;
    }
}

scripts/delete-category.php

<?php
require dirname(__FILE__) . '/../app/bootstrap.php';
$bootstrap = \Magento\Framework\App\Bootstrap::create(BP, $_SERVER);
require dirname(__FILE__) . '/abstract.php';

class CreateCategoriesApp extends AbstractApp
{

    public function run()
    {
        $this->_objectManager->get('Magento\Framework\Registry')
            ->register('isSecureArea', true);

        $category = $this->_objectManager->create('\Magento\Catalog\Model\Category');
        $category = $category->load(343);

        $category->delete();
    }
}

/** @var \Magento\Framework\App\Http $app */
$app = $bootstrap->createApplication('CreateCategoriesApp');
$bootstrap->run($app);

Dann lasse ich es einfach so laufen php scripts/delete-category.php

Luke Rodgers
quelle
2
wenn ich arbeiten gut für Frontend, admin möchten Code für den Zugriff dann zeigen sie Fehler Zugriff oder Bereich Ausgabe, können Sie sagen , wie für Admin - Bereich nennen
Pradeep Kumar
Beim Versuch , etwas zu nennen, die ich erhalten: Magento\Framework\Exception\LocalizedException: Area code is not set. Wie kann ich es einstellen? Ich brauche die Frontpartie.
Max
Ich habe M2 nicht viel angeschaut, seit ich diesen Code geschrieben habe. Ich fürchte, Änderungen im Framework haben ihn möglicherweise ungültig gemacht oder müssen geändert werden. Entschuldigung!
Luke Rodgers
18

Auf Anfrage ein sehr kurzes Beispiel, wie Sie einen Test schreiben können (ohne ihn in Ihrer Ordnererweiterungsstruktur abzulegen). Leider ist dies alles Kommandozeile und nicht zum Verzehr über einen Browser.

Erstellen Sie die Datei

dev/tests/unit/quicktest.php

mit

<?php

class QuickTest extends \PHPUnit_Framework_TestCase
{
    public function testExample()
    {
        //instantiate your class
        $context = new Magento\Framework\Object();

        $context->setData('param', 'value');

        //test whatever you want to test
        $this->assertEquals('value', $context->getData('param'));

        //you could even output to console
        echo $context->getData('param');

    }
}

dann aus dem Verzeichnis dev/tests/unit/laufen , phpunit quicktest.phpdie Ihren Code ausgeführt werden. Dies alles funktioniert, da die Datei dev/tests/unit/phpunit.xml.distautomatisch geladen wird und die Umgebung vorbereitet.

In vielen Fällen müssen Sie möglicherweise Eingaben an den Konstruktor der Klassen senden. In den vorhandenen Tests unter finden Sie dev/tests/unit/testsuite/weitere Beispiele, wie dies aussehen könnte, einschließlich der Verspottung von Objekten.

Kristof bei Fooman
quelle
1
Ich bat um einen "schmutzigen" Spielplatz. Du hast hier einen sauberen gegeben :). Interessante Idee. Ich werde es versuchen.
Marius
7
Wenn ich in der Vergangenheit eine test.php erstellt hätte, hätte die Mühe genauso gut darin bestehen können, einen Test zu schreiben, der einen dauerhaften Nutzen bringt.
Kristof bei Fooman
15

Hier ist eine bessere Möglichkeit, als sich in das Testsystem einzuklinken: Verwenden Sie die Befehlszeilenschnittstelle von Magento 2.

Das bedeutet, dass Sie Ihren Sandbox-Code in ein tatsächliches Modul integrieren müssen (oder zu diesem Zweck eines erstellen müssen), aber Sie sollten dies trotzdem tun.

Sobald Sie Ihr Modul eingerichtet haben , ist das Hinzufügen eines Befehls ziemlich einfach. Alles, was Sie brauchen, ist die Klasse und DI, um sie zu registrieren.

1. {module} /etc/di.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Framework\Console\CommandList">
        <arguments>
            <argument name="commands" xsi:type="array">
                <item name="greeting_command" xsi:type="object">Magento\CommandExample\Console\Command\GreetingCommand</item>
            </argument>
        </arguments>
    </type>
</config>

2. {module} /Console/Command/GreetingCommand.php

<?php

namespace Magento\CommandExample\Console\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Class GreetingCommand
 */
class GreetingCommand extends Command
{
    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        $this->setName('example:greeting')
             ->setDescription('Greeting command');

        parent::configure();
    }

    /**
     * {@inheritdoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $output->writeln('<info>Hello world!</info>');
    }
}

Beispiel abgeleitet von https://github.com/magento/magento2-samples/tree/master/sample-module-command - ein vollständiges Modul mit dieser Funktionalität finden Sie dort. Es gibt weniger triviale Beispiele .

Konventionell sollte Ihre Befehlsklasse immer in sein {module}/Console/Commandund mit enden Command.php.

Sobald man diese zwei Bits von Code (und gerötet Magento Cache, etc.) hinzugefügt haben, führen Sie Ihren Befehl mit Namen in SSH: php bin/magento example:greeting.

In diesem Kontext können Sie die Abhängigkeitsinjektion verwenden, sodass Sie jeden Code ausführen können, den Sie möchten execute().

Diese Benutzeroberfläche basiert auf der Symfony- Konsolenkomponente , sodass Sie auch vollen Zugriff auf alle Funktionen haben, einschließlich Optionen / Argumente , Tabellen und einfacher Fortschrittsbalken .

Wenn Sie beim Einrichten Ihres Befehls oder Ihrer Optionen auf Probleme stoßen, können Sie normalerweise den Befehl 'list' ausführen, um einen besseren Überblick über die Fehlerursache zu erhalten: php bin/magento list

Genießen.

Ryan Hoerr
quelle
Nett! mit den Fortschrittsbalken des Symfony für Skripte mit großem Export. danke
urbansurfers
13

Der wichtige Teil ist der \Magento\Framework\App\Bootstrap::create

Da die Bootstrap::init()Methode privat ist und viele wichtige Dinge vorkommen, werden öffentliche Methoden benötigt, die sie aufrufen.

Das ist auf der einen Seite die createApplication()und folgende run()Methode, aber auch die getDirList()und getObjectManager()Methode, die beide kein Argument benötigen.

Eine Anwendung wird also nicht benötigt, die Nachteile sind, dass der Fehlerhandler nicht initialisiert wird.

Flyingmana
quelle
6

Möglicherweise nicht zum Thema gehörend, aber ich verwende immer die Contacts-Index-Controller-Datei in Magento 1, um Dinge zu testen (IndexAction-Methode). Es ist so einfach wie das Aufrufen von example.com/contacts. Sie müssen nur sicherstellen, dass diese Änderungen nicht übernommen werden.

Ich bin sicher, dass Sie in Magento 2 etwas Ähnliches tun können. Sie müssen keine neue Datei mit dem Bootstrap-Code erstellen.

Erfan
quelle
1
Der Himmel verbietet dir, es zu vergessen, oder mach es in der Produktion! Bitte ändern Sie den Core-Code nicht.
Ryan Hoerr
@ RyanH. Wird nicht passieren. Versionskontrolle, automatisierte Builds, statische Code-Analyse, Peer-Code-Überprüfung, Staging / User Acceptance Testing / etc. Aber ja, wenn Sie das nicht haben, besteht die Möglichkeit, dass es in Produktion geht: P
Erfan
1
Das ist großartig für Sie, aber die meisten Leute, die hier suchen, werden diese Art von Steuerelementen nicht haben. Es ist immer besser, die richtige Art zu lehren (und zu tun), Dinge zu tun.
Ryan Hoerr
5

Diese Antwort ist eine leichte Modifikation der obigen Antwort von Marius

Denn in Magento 2.1 kam der Fehler wie Area code not setbei der Verwendung dieses Codes.So the intension of this answer is to fix that error on Magento 2.1

Um diesen Fehler zu beheben, müssen Sie den Bereich in Ihrem Netzwerk definieren test.php file. (siehe die modifizierte Datei unten).

<?php
require __DIR__ . '/app/bootstrap.php';
$bootstrap = \Magento\Framework\App\Bootstrap::create(BP, $_SERVER);
$obj = $bootstrap->getObjectManager();

$state = $obj->get('Magento\Framework\App\State');
$state->setAreaCode('frontend');
/** @var \Magento\Framework\App\Http $app */
$app = $bootstrap->createApplication('TestApp');
$bootstrap->run($app);

Und die TestApp.phpDatei bleibt gleich.

<?php

class TestApp
    extends \Magento\Framework\App\Http
    implements \Magento\Framework\AppInterface {
    public function launch()
    {
        //dirty code goes here.
        $objectManager = \Magento\Framework\App\ObjectManager::getInstance();
        $product = $objectManager->get('Magento\Catalog\Model\Product')->load(71);
        var_dump($product->getData());

        return $this->_response;
    }

    public function catchException(\Magento\Framework\App\Bootstrap $bootstrap, \Exception $exception)
    {
        return false;
    }

}
Sukeshini
quelle
Dies funktioniert auch nicht für mich in 2.1.6, ich bekommeUncaught TypeError: Argument 2 passed to Magento\\Framework\\App\\Http::__construct() must be an instance of Magento\\Framework\\Event\\Manager, none given
Guerilla
5

Sie können Skripte auf Magento-Root ausführen, indem Sie den folgenden Code hinzufügen, und Bootstrap wird mitgeliefert.

ini_set('display_errors', 1);
ini_set('max_execution_time', 0);
ini_set("memory_limit", "-1");
set_time_limit(0);
error_reporting(E_ALL);
require './app/bootstrap.php';
$bootstrap = \Magento\Framework\App\Bootstrap::create(BP, $_SERVER);
$objectManager = $bootstrap->getObjectManager();
$state = $objectManager->get('Magento\Framework\App\State');
$state->setAreaCode('admin');

Hoffe das wird hilfreich sein.

Yogesh
quelle
2

Sie können das Direktskript von Magento 2 root aus mit dem folgenden Code ausführen. Erstellen Sie eine neue Datei im Magento 2-Stammverzeichnis, fügen Sie diesen Code hinzu und fügen Sie anschließend Ihr Skript in die Datei ein.

<?php
    use Magento\Framework\App\Bootstrap;
    include('app/bootstrap.php');
    $bootstrap = Bootstrap::create(BP, $_SERVER);

    $objectManager = $bootstrap->getObjectManager();

    $state = $objectManager->get('Magento\Framework\App\State');
    $state->setAreaCode('frontend');
Evince-Entwicklung
quelle
1

Folgendes habe ich getan, um Magento außerhalb des Magento-Verzeichnisses in mein benutzerdefiniertes Skript zu initialisieren.

//Required to include Magento functions.
$magento_dir "your/path/to/the/magento/installation/directory/";
require $magento_dir . 'app/bootstrap.php';
$bootstrap = \Magento\Framework\App\Bootstrap::create(BP, $_SERVER);
//$app = $bootstrap->createApplication('Magento\Framework\App\Http');
$app = $bootstrap->createApplication('MyClass');
$bootstrap->run($app);

Dies ist der empfohlene Weg gemäß den Magento-Dokumenten. http://devdocs.magento.com/guides/v2.0/config-guide/bootstrap/magento-bootstrap.html

MagentoMan
quelle