Comment amorcer Magento 2 dans un script test.php?

93

Dans magento 1, je pouvais créer un fichier dans lequel je n'avais besoin que d'instancier la Mage_Core_Model_Appclasse, puis ajouter mon code "sale" à des fins de test.
Quelque chose comme ça 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.

Ensuite, j'ai pu appeler test.phple navigateur et voir ce que je fais.

Comment puis-je faire la même chose pour Magento 2?

Marius
la source
4
Comment fonctionne le magento 2 cron? Peut-être que vous pouvez utiliser la même approche?
Amasty
4
Bonne idée, mais ... Code de cron.php: $app = $bootstrap->createApplication('Magento\Framework\App\Cron', ['parameters' => ['group::']]);. Devrais-je créer mon propre modèle d'application?
Marius
1
écrivez un test unitaire
Kristof chez Fooman
2
@Fooman. N'hésitez pas à écrire ceci comme réponse, mais veuillez donner un exemple. Je suis un peu nouveau pour les tests unitaires.
Marius

Réponses:

86

Basé sur la réponse de @ Flyingmana, j'ai creusé un peu et trouvé une solution. Cela semble fonctionner pour moi.
D'abord ma solution, puis quelques explications.
J'ai créé un fichier appelé test.phpà la racine de mon instance de 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);

Ensuite, j'ai créé un fichier appelé TestApp.phpau même endroit avec ce contenu.

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

}

Maintenant, je peux simplement appeler test.phpdans le navigateur et tout ce qui est placé dans TestApp :: launch () sera exécuté.

Maintenant, pourquoi cela fonctionne-t-il:
La méthode createApplicationde la classe bootstrap est la partie la plus importante. Il crée une instance d'une classe d'application. La méthode createApplicationattend une implémentation de la \Magento\Framework\AppInterfacequi contient 2 méthodes.
J'ai donc créé ma propre classe dans TestApplaquelle implémente cette interface. J'ai toujours catchExceptionrenvoyé la méthode falsecar je ne veux pas que mon application gère les exceptions. Si quelque chose ne va pas, imprimez-le simplement à l'écran.
Ensuite, j'ai implémenté la méthode launch. celui-ci s'appelle par \Magento\Framework\App\Bootstrap::run. Cette runméthode fait presque la même chose, quelle que soit l'application transmise en tant que paramètre.
La seule chose qui dépend de l'application est cette ligne:

$response = $application->launch();

Cela signifie que l'appel \Magento\Framework\App\Bootstrap::runinitiera l'envoie de Magento (peut-être fera-t-il d'autres choses folles ... je n'ai pas encore tout vérifié) puis appelle la launchméthode à partir de l'application.
C'est pourquoi vous devez mettre tout votre code sale à l'intérieur de cette méthode.
Ensuite, les \Magento\Framework\App\Bootstrap::runappels $response->sendResponse();$responseest ce que la launchméthode retourne.
C'est pourquoi return $this->_response;est nécessaire. Cela renvoie simplement une réponse vide.

J'ai fait étendre ma classe d'applications, \Magento\Framework\App\Httpdonc je vais déjà avoir des paramètres de requête et de réponse (et d'autres), mais vous pouvez faire en sorte que votre classe ne s'étende pas. Ensuite, vous devez copier le constructeur de la \Magento\Framework\App\Httpclasse. Peut-être ajouter plus de paramètres dans le constructeur si vous en avez besoin.

Marius
la source
2
Bien sûr, la TestAppclasse aurait pu être définie dans le même test.phpfichier, mais je ne veux pas la rendre aussi sale :)
Marius
J'ai dû ajouter parent::launch();la première ligne de launch()méthode car elle me donnait l'erreur "Zone code not set"
OSdave
@OSdave. Cela a fonctionné sans que lorsque j'ai testé. Très probablement, quelque chose a changé dans les dernières versions.
Marius
@Marius, j'avais installé magento en installant l'installation rapide du serveur. Et ne pas avoir bootstrap.php dans l'application
er.irfankhan11
1
@ Butterfly Vous n'avez pas besoin de l'inclure dans votre contrôleur personnalisé. Le fichier est inclus dans index.php avant d’atteindre votre contrôleur.
Marius
54

Pour les tests rapides / courts / sales, j'ai utilisé quelque chose comme ceci:

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
la source
4
cela marche. les autres réponses ne le font pas.
Ahnbizcad
1
cela déclenche HTTP 500 à mes côtés.
Max
Fonctionne toujours en 2.1.2. J'ai dû modifier le require-path
th
n'a pas fonctionné pour moi
Sarfaraj Sipai
20

Basé sur la réponse de @ Marius, je suis arrivé à cela.

Cela fonctionne à la fois via la ligne de commande et le navigateur, ce que je trouve utile.

Voici un exemple de script pour supprimer une catégorie par programme.

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

Puis je le lance comme php scripts/delete-category.php

Luke Rodgers
la source
2
fonctionne bien pour le front-end, si je veux accéder au code d'administrateur alors il montre l'accès d'erreur ou un problème de zone, pouvez-vous dire comment appeler pour la zone d'admin
Pradeep Kumar
Lorsque vous essayez d'appeler quelque chose, je reçois: Magento\Framework\Exception\LocalizedException: Area code is not set. Comment puis-je le définir? J'ai besoin du fronend.
Max
Je n'ai pas beaucoup regardé M2 depuis que j'ai écrit ce code. J'ai peur que des changements dans le framework l'aient rendu invalide ou qu'il faille le modifier, désolé!
Luke Rodgers
18

Comme demandé, un très bref exemple de la façon dont vous pourriez écrire un test (sans le placer dans votre structure d’extension de dossier). Hélas, tout cela est en ligne de commande et non pour la consommation via un navigateur.

Créer le fichier

dev/tests/unit/quicktest.php

avec

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

    }
}

puis à partir du répertoire dev/tests/unit/run phpunit quicktest.phpqui exécutera votre code. Tout cela fonctionne puisque le fichier dev/tests/unit/phpunit.xml.distest chargé automatiquement et prépare l'environnement.

Dans de nombreux cas, vous devrez peut-être fournir des entrées au constructeur des classes. Veuillez consulter les tests existants ci-dessous dev/tests/unit/testsuite/pour obtenir d'autres exemples de ce que cela pourrait être, y compris des objets moqueurs.

Kristof chez Fooman
la source
1
J'ai demandé un terrain de jeu "sale". Vous en avez donné un ici propre :). Idée intéressante. Je vais essayer.
Marius
7
Je trouve que les fois où j'aurais créé un test.php dans le passé, l'effort aurait tout aussi bien pu être fait pour écrire un test qui aura un avantage permanent.
Kristof chez Fooman
15

Voici un meilleur moyen que de vous connecter au système de test: Utilisez l'interface de ligne de commande de Magento 2.

Cela signifie que vous devrez intégrer votre code sandbox dans un module réel (ou en créer un à cet effet), mais vous devriez le faire de toute façon.

Une fois votre module configuré , ajouter une commande est assez facile. Tout ce dont vous avez besoin, c'est de la classe et de l'inspecteur d'enregistrement

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

Exemple dérivé de https://github.com/magento/magento2-samples/tree/master/sample-module-command - consultez-le pour un module complet intégrant cette fonctionnalité. Il y a des exemples moins triviaux inclus.

Par convention, votre classe de commande doit toujours être dans {module}/Console/Commandet se terminer par Command.php.

Une fois que vous avez ajouté ces deux bits de code (et rincée cache de Magento, etc.), exécutez votre commande par son nom en SSH: php bin/magento example:greeting.

Vous pouvez utiliser l'injection de dépendance dans ce contexte pour pouvoir exécuter le code de votre choix execute().

Cette interface est basée sur le composant Console de Symfony . Vous disposez ainsi d'un accès complet à toutes ces fonctionnalités, notamment les options / arguments , les tableaux et les barres de progression très simples .

Si vous rencontrez des problèmes lors de la configuration de votre commande ou de vos options, vous pouvez généralement exécuter la commande "list" pour obtenir une meilleure visibilité de ce qui ne va pas: php bin/magento list

Prendre plaisir.

Ryan Hoerr
la source
Agréable! avec les barres de progression de Symfony pour les scripts avec une exportation importante. merci
urbansurfers
13

La partie importante est la \Magento\Framework\App\Bootstrap::create

mais comme la Bootstrap::init()méthode est privée, et qu'il se passe beaucoup de choses importantes, des méthodes publiques l'appelant sont nécessaires.

Cest sur un côté le createApplication()et suivant la run()méthode, mais aussi getDirList()et getObjectManager()méthode, les deux ont besoin aucun argument.

Donc, une application n'est pas nécessaire, mais le gestionnaire d'erreurs n'est pas initialisé.

Flyingmana
la source
6

Peut-être hors sujet, mais j'utilise toujours le fichier du contrôleur d'index de Contacts dans Magento 1 pour tester des choses (méthode IndexAction). C'est aussi simple que d'aller à exemple.com/contacts. Vous devez juste vous assurer de ne pas appliquer ces modifications;)

Je suis sûr que vous pouvez faire quelque chose de similaire dans Magento 2. Cela vous évite de créer un nouveau fichier avec le code d'amorçage.

Erfan
la source
1
Que le ciel vous interdise d'oublier ou de le faire en production! Veuillez ne pas modifier le code principal.
Ryan Hoerr
@RyanH. Ça n'arrivera pas. Contrôle de version, versions automatisées, analyse de code statique, révision de code par des pairs, test de transfert / d’acceptation d’utilisateur / etc. Mais oui, si vous n'avez pas ça, il y a une chance pour que ça finisse en production: P
Erfan
1
C'est bien pour vous, mais la plupart des gens qui cherchent ici n'auront pas ce genre de contrôle. Mieux vaut enseigner (et faire) la bonne façon de faire les choses, toujours.
Ryan Hoerr
5

Cette réponse est une légère modification de la réponse ci-dessus de Marius.

Parce que dans Magento 2.1, l'erreur se produisait comme Area code not setlors de l'utilisation de ce code.So the intension of this answer is to fix that error on Magento 2.1

Ce que vous devez faire pour corriger cette erreur est de définir la zone dans votre fichier test.php file. (voir le fichier modifié ci-dessous).

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

Et le TestApp.phpfichier restera le même.

<?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
la source
Cela ne marche pas non plus pour moi en 2.1.6, je reçoisUncaught TypeError: Argument 2 passed to Magento\\Framework\\App\\Http::__construct() must be an instance of Magento\\Framework\\Event\\Manager, none given
Guerrilla,
5

Vous pouvez diriger le script sur la racine magento en ajoutant le code ci-dessous et l'initialisation sera incluse. [Créez test.php sur le dossier racine magento et incluez le code ci-dessous]

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

J'espère que cela vous sera utile.

Yogesh
la source
2

Vous pouvez exécuter le script direct à partir de la racine de Magento 2 en utilisant le code ci-dessous. Créez un nouveau fichier dans le répertoire racine de Magento 2 et ajoutez ce code, puis ajoutez votre script dans le fichier.

<?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');
Développement Evince
la source
1

Voici ce que j'ai fait pour introduire Initialisation Magento dans mon script personnalisé en dehors du répertoire 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);

C'est la méthode recommandée selon la documentation de Magento. http://devdocs.magento.com/guides/v2.0/config-guide/bootstrap/magento-bootstrap.html

MagentoMan
la source