Jak mogę uruchomić Magento 2 w skrypcie test.php?

93

W magento 1 mogłem stworzyć plik, w którym wystarczyło tylko utworzyć instancję Mage_Core_Model_Appklasy, a następnie dodać „brudny” kod do celów testowych.
Coś takiego 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.

Potem mogłem zadzwonić test.phpw przeglądarce i zobaczyć, co robię.

Jak mogę zrobić to samo dla Magento 2?

Marius
źródło
4
Jak działa Magento 2 CRON? Czy możesz zastosować to samo podejście?
Amasty,
4
Dobry pomysł, ale ... Kod z cron.php: $app = $bootstrap->createApplication('Magento\Framework\App\Cron', ['parameters' => ['group::']]);. Czy powinienem stworzyć własny model aplikacji?
Marius
1
napisz test jednostkowy
Kristof w Fooman
2
@Fooman. Napisz odpowiedź jako odpowiedź, ale podaj przykład. Jestem trochę nowy w testowaniu jednostkowym.
Marius

Odpowiedzi:

86

Opierając się na odpowiedzi @ Flyingmana, zrobiłem małe kopanie i wymyśliłem rozwiązanie. Wydaje mi się, że dla mnie działa.
Najpierw moje rozwiązanie, a potem kilka wyjaśnień.
Utworzyłem plik o nazwie test.phpw katalogu głównym mojej instancji magento.

<?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);

Następnie utworzyłem plik o TestApp.phptej samej nazwie o tym samym miejscu.

<?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;
    }

}

Teraz mogę po prostu wywołać test.phpw przeglądarce i wszystko, co jest umieszczone w TestApp :: launch () zostanie wykonane.

Dlaczego to działa:
Najważniejsza jest metoda createApplicationz klasy bootstrap. Tworzy instancję klasy aplikacji. Metoda createApplicationoczekuje implementacji \Magento\Framework\AppInterfacezawierającej 2 metody.
Więc stworzyłem własną klasę, TestAppktóra implementuje ten interfejs. Zrobiłem sposób catchExceptionpowrócić falsezawsze, bo nie chcę mojej aplikacji do obsługi wyjątków. Jeśli coś jest nie tak, po prostu wydrukuj to na ekranie.
Następnie wdrożyłem metodę launch. ten jest nazywany przez \Magento\Framework\App\Bootstrap::run. Ta runmetoda robi prawie to samo, bez względu na to, co aplikacja przekazała jako parametr.
Jedyne, co zależy od aplikacji, to ta linia:

$response = $application->launch();

Oznacza to, że wywołanie \Magento\Framework\App\Bootstrap::runzainicjuje env Magento (może zrobię trochę innych szalonych rzeczy ... jeszcze nie wszystko sprawdziłem), a następnie wywoła launchmetodę z aplikacji.
Dlatego musisz umieścić cały brudny kod w tej metodzie.
Następnie \Magento\Framework\App\Bootstrap::runrozmowy $response->sendResponse();gdzie $responsejest to, co launchzwraca metoda.
Dlatego return $this->_response;jest potrzebny. Zwraca tylko pustą odpowiedź.

Sprawiłem, że moja klasa aplikacji została rozszerzona, \Magento\Framework\App\Httpwięc będę już mieć parametry żądania i odpowiedzi (i inne), ale możesz sprawić, że twoja klasa nic nie rozszerzy. Następnie musisz skopiować konstruktor z \Magento\Framework\App\Httpklasy. W razie potrzeby dodaj więcej parametrów do konstruktora.

Marius
źródło
2
Oczywiście TestAppklasa mogła zostać zdefiniowana w tym samym test.phppliku, ale nie chcę, aby była tak brudna :)
Marius
Musiałem dodać parent::launch();jako pierwszy wiersz launch()metody, ponieważ dawał mi błąd „Nie ustawiono kodu kierunkowego”
OSdave
@OSdave. Działało bez tego, kiedy testowałem. Najprawdopodobniej coś się zmieniło w najnowszych wersjach.
Marius
@Marius, zainstalowałem magento poprzez szybką instalację serwera. I nie mam bootstrap.php w aplikacji
er.irfankhan11
1
@ Butterfly Nie musisz uwzględniać go w niestandardowym kontrolerze. Plik zostanie dołączony do pliku index.php, zanim dotrze do kontrolera.
Marius
54

Do szybkich / krótkich / brudnych testów użyłem czegoś takiego:

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
źródło
4
to działa. inne odpowiedzi nie.
ahnbizcad
1
powoduje to HTTP 500 po mojej stronie.
Maks.
Nadal działa w 2.1.2. Musiałem zmodyfikować wymaganą ścieżkę
simonthesorcerer
nie działało dla mnie
Sarfaraj Sipai
20

Na podstawie odpowiedzi @ Marius wymyśliłem to.

Działa zarówno za pomocą wiersza polecenia, jak i przeglądarki, co uważam za przydatne.

Oto przykładowy skrypt do programowego usuwania kategorii.

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);

Potem po prostu to uruchamiam php scripts/delete-category.php

Luke Rodgers
źródło
2
działa dobrze dla interfejsu, jeśli chcę uzyskać dostęp do kodu administratora, to pokazuje błąd dostępu lub problem z obszarem, czy możesz powiedzieć, jak zadzwonić do obszaru administratora
Pradeep Kumar
Gdy próbuje nazwać coś, mam: Magento\Framework\Exception\LocalizedException: Area code is not set. Jak mogę to ustawić? Potrzebuję fronend.
Maks.
Obawiam się, że nie patrzyłem na M2 dużo od czasu, gdy napisałem ten kod. Obawiam się, że zmiany w frameworku mogły spowodować, że był on nieprawidłowy lub wymagał poprawek, przepraszam!
Luke Rodgers
18

Zgodnie z prośbą bardzo krótki przykład, w jaki sposób można napisać test (bez umieszczania go w strukturze rozszerzeń folderów). Niestety jest to wszystko wiersz poleceń, a nie do konsumpcji za pośrednictwem przeglądarki.

Utwórz plik

dev/tests/unit/quicktest.php

z

<?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');

    }
}

następnie z katalogu dev/tests/unit/uruchom, phpunit quicktest.phpktóry wykona twój kod. To wszystko działa, ponieważ plik dev/tests/unit/phpunit.xml.distjest automatycznie ładowany i przygotowuje środowisko.

W wielu przypadkach może być konieczne podanie danych wejściowych do konstruktora klas. Zobacz istniejące testy poniżej, dev/tests/unit/testsuite/aby uzyskać dalsze przykłady tego, jak to może wyglądać, w tym kpiące obiekty.

Kristof w Fooman
źródło
1
Poprosiłem o „brudny” plac zabaw. Dałeś tutaj czysty :). Ciekawy pomysł. Spróbuję.
Marius
7
Uważam, że w czasach, gdy w przeszłości tworzyłem test.php, równie dobrze można było napisać test, który przyniesie ciągłe korzyści.
Kristof at Fooman
15

Oto lepszy sposób niż podpięcie się do systemu testowego: użyj interfejsu wiersza poleceń Magento 2.

Oznacza to, że będziesz musiał zintegrować kod piaskownicy z rzeczywistym modułem (lub utworzyć go do tego celu), ale i tak powinieneś to zrobić.

Po Twojej moduł skonfigurować , dodając polecenie jest całkiem proste. Wszystko czego potrzebujesz to klasa i DI, aby ją zarejestrować.

1. {moduł} /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. {moduł} /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>');
    }
}

Przykład pochodzący z https://github.com/magento/magento2-samples/tree/master/sample-module-command - zobacz tam kompletny moduł zawierający tę funkcjonalność. Zawiera mniej trywialne przykłady .

Zgodnie z konwencją klasa poleceń powinna zawsze znajdować się na {module}/Console/Commandkońcu i kończyć się nią Command.php.

Po dodaniu tych dwóch bitów kodu (i wypiekami na Magento cache, etc.), wykonywać swoje polecenia z nazwy w SSH: php bin/magento example:greeting.

W tym kontekście można użyć wstrzykiwania zależności, dzięki czemu można uruchomić dowolny kod, który ma się znaleźć execute().

Interfejs ten oparty jest na komponencie konsoli Symfony , dzięki czemu masz również pełny dostęp do całego tego szerokiego zakresu funkcji, w tym opcji / argumentów , tabel i bardzo łatwych pasków postępu .

Jeśli napotkasz jakiekolwiek problemy z konfiguracją polecenia lub opcji, zwykle możesz uruchomić polecenie „lista”, aby uzyskać lepszy wgląd w to, co się dzieje: php bin/magento list

Cieszyć się.

Ryan Hoerr
źródło
Miły! z paskami postępu Symfony dla skryptów z dużym eksportem. dzięki
urbansurfers
13

Ważną częścią jest \Magento\Framework\App\Bootstrap::create

ale ponieważ Bootstrap::init()metoda jest prywatna i zdarza się wiele ważnych rzeczy, potrzebne są publiczne metody nazywające ją.

Z jednej strony jest createApplication()to run()metoda podążająca za nią , ale także metoda getDirList()i getObjectManager(), które nie wymagają argumentów.

Więc aplikacja nie jest potrzebna, wady są takie, że moduł obsługi błędów nie został zainicjowany.

Flyingmana
źródło
6

Być może nie na temat, ale zawsze używam pliku kontrolnego indeksu Kontakty w Magento 1 do testowania rzeczy (metoda IndexAction). To tak proste, jak wejście na example.com/contacts. Musisz tylko upewnić się, że nie wprowadzisz tych zmian;)

Jestem pewien, że możesz zrobić coś podobnego w Magento 2. Nie musisz tworzyć nowego pliku z kodem bootstrap.

Erfan
źródło
1
Niebiosa zabraniają zapomnienia lub robienia tego na produkcji! Nie modyfikuj kodu podstawowego.
Ryan Hoerr
@RyanH. Nie stanie się Kontrola wersji, automatyczne kompilacje, statyczna analiza kodu, przegląd kodu równorzędnego, testowanie / testowanie akceptacji użytkownika / itp. Ale tak, jeśli tego nie masz, istnieje szansa, że ​​skończy się na produkcji: P
Erfan
1
To świetnie dla ciebie, ale większość ludzi, którzy tu patrzą, nie będzie miała takich kontroli. Zawsze lepiej uczyć (i robić) właściwy sposób robienia rzeczy.
Ryan Hoerr
5

Ta odpowiedź jest niewielką modyfikacją powyższej odpowiedzi Mariusza

Ponieważ w Magento 2.1 dostał błąd jak Area code not setprzy użyciu tego kodu.So the intension of this answer is to fix that error on Magento 2.1

Aby naprawić ten błąd, musisz zdefiniować obszar w swoim test.php file. (patrz zmodyfikowany plik poniżej).

<?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);

I TestApp.phpzłoży pozostaje taka sama.

<?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
źródło
To również nie działa dla mnie w 2.1.6, dostajęUncaught TypeError: Argument 2 passed to Magento\\Framework\\App\\Http::__construct() must be an instance of Magento\\Framework\\Event\\Manager, none given
Guerrilla
5

Możesz skierować skrypt na root Magento z dodaniem poniższego kodu, a bootstrap zostanie dołączony .. [Utwórz test.php na folderze głównym Magento i dołącz poniższy kod]

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');

Mam nadzieję, że to będzie pomocne.

Jogesh
źródło
2

Możesz uruchomić bezpośredni skrypt z katalogu głównego Magento 2, używając poniższego kodu. Utwórz nowy plik w katalogu głównym Magento 2 i dodaj ten kod, a następnie dodaj skrypt do pliku.

<?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 Development
źródło
1

Oto, co zrobiłem, aby zainicjować Magento w moim skrypcie niestandardowym poza katalogiem magento.

//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);

Jest to zalecany sposób według dokumentów Magento. http://devdocs.magento.com/guides/v2.0/config-guide/bootstrap/magento-bootstrap.html

MagentoMan
źródło