103

In magento 1 I could create a file where I only needed to instantiate the Mage_Core_Model_App class and then I could add my "dirty" code for test purposes.
Something like this 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.

Then I was able to call test.php in the browser and see what I'm doing.

How can I do the same for Magento 2?

4
  • 4
    How does the magento 2 cron works? May be you can use the same approach?
    – Amasty
    Oct 20, 2014 at 12:27
  • 4
    Good idea, but...code from cron.php: $app = $bootstrap->createApplication('Magento\Framework\App\Cron', ['parameters' => ['group::']]);. Should I create my own app model?
    – Marius
    Oct 20, 2014 at 12:32
  • 1
    write a unit test Oct 21, 2014 at 10:34
  • 2
    @Fooman. Feel free to write this as an answer, but please provide an example. I'm kind of new to unit testing.
    – Marius
    Oct 21, 2014 at 10:36

13 Answers 13

98

Based on @Flyingmana's answer I did a little digging and come up with a solution. It seams to work for me.
First my solution, then some explanations.
I've created a file called test.php in the root of my magento instance.

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

Then I created a file called TestApp.php in the same place with this content.

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

}

Now I can just call test.php in the browser and everything that is placed in TestApp::launch() will be executed.

Now, why this works:
The method createApplication from the bootstrap class is the most important part. It creates an instance of an application class. The method createApplication expects an implementation of the \Magento\Framework\AppInterface that contains 2 methods.
So I created my own class in TestApp that implements that interface. I made the method catchException return false always because I don't want my app to handle exceptions. In case something is wrong, just print it on the screen.
Then I implemented the method launch. this one is called by \Magento\Framework\App\Bootstrap::run. This run method does almost the same thing no matter what the application passed as a parameter is.
The only thing that depends on the application is this line:

$response = $application->launch();

This means that calling \Magento\Framework\App\Bootstrap::run will init the Magento env (maybe do some other crazy stuff...I haven't checked everything yet) then calls the launch method from the application.
That's why you need to put all your dirty code inside that method.
Then the \Magento\Framework\App\Bootstrap::run calls $response->sendResponse(); where $response is what the launch method returns.
That's why return $this->_response; is needed. It just returns an empty response.

I made my app class extend \Magento\Framework\App\Http so I will already have request and response parameters (and others), but you can make your class extend nothing. Then you need to copy the constructor from the \Magento\Framework\App\Http class. Maybe add more parameters in the constructor if you need it.

15
  • 2
    Of course the TestApp class could have been defined in the same test.php file, but I don't want to make it that dirty :)
    – Marius
    Oct 21, 2014 at 11:44
  • I've had to add parent::launch(); as the first line of launch() method as it was giving me a "Area code not set" error
    – OSdave
    Aug 7, 2015 at 10:19
  • 1
    @Butterfly You don't need to include it in your custom controller. The file gets included in index.php before it reaches your controller.
    – Marius
    Feb 18, 2019 at 14:31
  • 2
    just got this working as is running commerce ver. 2.3.3, thanks Marius. Note, you may need to require __DIR__ . 'TestApp.php'; as well in test.php Jan 20, 2020 at 22:14
  • 1
    In Magento 2.3.4 the catchException method declaration has changed, the : bool was added.
    – Zsolti
    May 5, 2020 at 19:47
63

For quick/short/dirty tests, I used something like this:

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

$bootstrap = \Magento\Framework\App\Bootstrap::create(BP, $_SERVER);

$obj = $bootstrap->getObjectManager();

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

$quote = $obj->get(Magento\Checkout\Model\Session::class)->getQuote()->load(1);
print_r($quote->getOrigData());
6
  • 4
    this works. the other answers don't.
    – ahnbizcad
    Sep 5, 2016 at 21:09
  • 1
    this triggers HTTP 500 at my side.
    – Max
    Oct 19, 2016 at 12:42
  • Still works in 2.1.2. I had to modify the require-path tho Mar 3, 2017 at 15:20
  • did not work for me Jan 2, 2018 at 12:53
  • The code $state->setAreaCode('frontend') throws the following: error PHP Fatal error: Uncaught Magento\Framework\Exception\LocalizedException: Area code is already set Jan 24, 2022 at 10:02
26

Based on @Marius's answer I came up with this.

It works via both the command line as well as the browser, which I find useful.

Here's a sample script to programmatically delete category.

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

Then I just run it like php scripts/delete-category.php

5
  • 2
    working fine for frontend , if i want to access admin code then it show error access or area issue, can you tell how to call for admin area Dec 3, 2015 at 12:41
  • When trying to call something, I get: Magento\Framework\Exception\LocalizedException: Area code is not set. How can I set it? I need the fronend.
    – Max
    Oct 19, 2016 at 12:59
  • I've not looked at M2 much since I wrote this code i'm afraid, changes in the framework may have made it invalid or needing amended, sorry! Oct 19, 2016 at 14:44
  • I tried this code in Magento 2.4.1 and when I di-compile the code I get the following error Fatal error: Cannot declare class AbstractApp, because the name is already in use in /var/www/html/magento/app/code/SDM/Altapay/scripts/abstract.php on line 13
    – Bushra
    Jul 2, 2021 at 5:11
  • Nowadays I mostly use the answer supplied by carco. However this'll be because someone has committed in that code declaring an AbstractApp in the file you have specificed which is being included by the composer autoloader, as its in app/code its probably your namespace with your code? Jul 2, 2021 at 10:07
19

As requested a very short example of how you could write a test (without placing it in your folder extension structure). Alas this is all command line and not for consumption via a browser.

Create the file

dev/tests/unit/quicktest.php

with

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

    }
}

then from the directory dev/tests/unit/ run phpunit quicktest.php which will execute your code. This all works since the file dev/tests/unit/phpunit.xml.dist is automatically loaded and prepares the environment.

In a lot of cases you might have to supply input to the classes' constructor. Please see the existing tests under dev/tests/unit/testsuite/ for further examples of how this could look, including mocking objects.

2
  • 1
    I asked for a "dirty" playground. You gave here a clean one :). Interesting idea. I will give it a try.
    – Marius
    Oct 21, 2014 at 11:31
  • 7
    I find that the times I would have created a test.php in the past the effort might as well have gone into writing a test which will have an ongoing benefit. Oct 21, 2014 at 11:34
17

Here's a better way than hooking into the test system: Use Magento 2's command-line interface.

This does mean you'll have to integrate your sandbox code into an actual module (or create one for the purpose), but you should be doing that anyway.

Once you have your module set up, adding a command is pretty easy. All you need is the class, and DI to register it.

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

Example derived from https://github.com/magento/magento2-samples/tree/master/sample-module-command - see there for a complete module incorporating this functionality. There are less-trivial examples included.

By convention, your command class should always be in {module}/Console/Command, and end with Command.php.

Once you've added those two bits of code (and flushed Magento cache, etc.), execute your command by name in SSH: php bin/magento example:greeting.

You can use dependency injection in this context, so you can run any code you want to within execute().

This interface is built on Symfony's Console component, so you also have full access to all of that wide range of functionality, including options/arguments, tables, and very easy progress bars.

If you run into any problems setting up your command or options, you can usually run the 'list' command to get better visibility into what's going wrong: php bin/magento list

Enjoy.

1
  • Nice! with the Symfony's progress bars for scripts with big export. thanks
    – cap340
    Mar 23, 2018 at 18:13
13

The Important part is the \Magento\Framework\App\Bootstrap::create

but as the Bootstrap::init() method is private, and there happens a lot of important stuff, public methods calling it are needed.

Thats on the one side the createApplication() and following the run() method, but also the getDirList() and getObjectManager() method, which both need no argument.

So an Application is not needed, the downsides are, that the error Handler is not initialized.

6

Possibly off-topic, but I always use the Contacts index controller file in Magento 1 to test things (IndexAction method). It's as simple as going to example.com/contacts. You just have to make sure not to commit those changes ;)

I'm sure you can do something similar in Magento 2. Spares you from having to create a new file with the bootstrap code.

3
  • 1
    Heaven forbid you forget, or do it on production! Please don't modify core code.
    – Ryan Hoerr
    Feb 23, 2016 at 13:42
  • @RyanH. Won't happen. Version control, automated builds, static code analysis, peer code review, staging/user acceptance testing/etc. But yeah, if you don't have that, there's a chance it could end up on production :P
    – Erfan
    Feb 23, 2016 at 14:33
  • 1
    That's great for you, but most people looking here won't have those kinds of controls. Better to teach (and do) the right way of doing things, always.
    – Ryan Hoerr
    Feb 23, 2016 at 14:55
6

You can direct script on magento root with adding below code and bootstrap will be included..[Create test.php on magento root folder and include below code]

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

Hope this will be helpful.

1
  • This works! thanks
    – Sukeshini
    Aug 2, 2020 at 16:10
5

This answer is a slight modification to the answer above by Marius

Because in Magento 2.1 got the error as Area code not set when using that code. So the intension of this answer is to fix that error on Magento 2.1

What you have to do in order to fix this error is define the area in your test.php file. (see the modified file below).

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

And the TestApp.php file will remains the same.

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

}
1
  • This also doesn't work for me in 2.1.6, i get Uncaught TypeError: Argument 2 passed to Magento\\Framework\\App\\Http::__construct() must be an instance of Magento\\Framework\\Event\\Manager, none given
    – Guerrilla
    Aug 1, 2017 at 10:32
2

You can run the direct script from Magento 2 root using below code. Create a new file in Magento 2 root directory and add this code and after this add your script in the file.

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

Here is what I did to bring initialize Magento into my custom script outside of the magento directory.

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

This is the recommended way according to the Magento docs. http://devdocs.magento.com/guides/v2.0/config-guide/bootstrap/magento-bootstrap.html

1

I had issues with the accepted answer, so I debugged a bit further and came to the conclusion that a single-file approach works best for me.

Like this:

pub/dirty.php:

<?php
require __DIR__ . '/../app/bootstrap.php';

class TestMyClass
    extends \Magento\Framework\App\Http
    implements \Magento\Framework\AppInterface
{
    public function launch()
    {
        $this->_state->setAreaCode(\Magento\Framework\App\Area::AREA_ADMINHTML);
        $myClass = $this->_objectManager->create('NameSpace\YourClassName');
        $myClass->execute(); // In cron jobs we usually use the execute() method as the entry point
        return $this->_response;
    }
}

$bootstrap = \Magento\Framework\App\Bootstrap::create(BP, $_SERVER);
/** @var \Magento\Framework\App\Http $app */
$app = $bootstrap->createApplication('TestMyClass');
$bootstrap->run($app);
0

In this way you can write scripts in Mangento 2

<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
ini_set('memory_limit', '5G');
error_reporting(E_ALL);

use Magento\Framework\App\Bootstrap;
use Magento\Store\Model\ScopeInterface;

//require '../app/bootstrap.php';
require './app/bootstrap.php';
//echo "start";
$bootstrap = Bootstrap::create(BP, $_SERVER);

$objectManager = $bootstrap->getObjectManager();
$resource = $objectManager->get('Magento\Framework\App\ResourceConnection');
$connection = $resource->getConnection();
$state = $objectManager->get('Magento\Framework\App\State');
$state->setAreaCode('frontend');

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.