Ray is Spatie's desktop debugging app. You call ray($anything) from PHP and the value shows up in a separate window, instead of in a log file or the browser. Getting it working with Magento in Docker takes three steps: composer require --dev spatie/ray, a ray.php that points at host.docker.internal, and a bind mount for that file.
Magento debugging typically involves a combination of writing output to log files, dump & die statements, and Xdebug. Every one of them makes you pay for it somewhere.
Disadvantages of typical debugging tools
For one, if the class you are debugging doesn't have a constructor, needing to create one just to be able to write to a log file is quite a verbose task. You'll also need to search the filesystem for the specific log files you wrote data to, and may not be able to find it. If you do find the data, it could be mixed in and confused with other debugging output.
<?php
declare(strict_types=1);
class MyClass
{
public function __construct(
private readonly \Psr\Log\LoggerInterface $logger,
) {
}
public function execute()
{
try {
// Code goes here
} catch (\Exception $e) {
$this->logger->critical("Error message", ["exception" => $e]);
}
}
}It's also very possible, and likely, that you will eventually wind up accidentally committing files to production that contain these debug statements. This has the undesired effect of having information outputted to the user, possibly exposing security vulnerabilities. Lots of writes to the log files can also consume increasing amounts of disk space, and over time can even negatively affect performance.
Using var_dump along with die is actually quite efficient, but again, you risk committing the updated files with
these calls to production. It's also hard to get the desired info out of var_dump when you are knee-deep debugging a
complex task.
echo "<pre>";
var_dump($exception);
die();Xdebug works great, but can be a hassle to set up. The debugger also sometimes slows down your workflow, notching away at your productivity. It also requires specific knowledge of the Xdebug debugging tools.
Say hello to Ray
Spatie built Ray to fix exactly this, and it works in Magento as well as it does in Laravel. Ray is a desktop app that runs on your machine and catches everything your code sends to it.
To use Ray, you'd just call the ray() function directly from your code, like so:
<?php
declare(strict_types=1);
class MyClass
{
public function execute()
{
try {
// Code goes here
} catch (\Exception $e) {
ray($e)->red();
}
}
}Ray is paid software. The desktop app (Ray 3) is sold as a 1-year license or a lifetime license, and the trial caps you at 20 messages per session, so check the current pricing before you lean on it mid-debug.
Install the Ray package
The first step of using Ray is to install it with Composer, as a dev dependency:
composer require --dev spatie/rayThis installs the Ray PHP package within your development environment. It's the part that sends your debug data over to the Ray app.
If you'd rather keep Ray out of the project's composer.json entirely, Spatie also has a global install
(composer global require spatie/global-ray, then global-ray install).
Configure Ray
Before using Ray, you may need to configure it if you are using a development environment such as Docker. If you have a locally-installed version of Magento, you can skip this step.
Since I maintain docker-magento, I will cover the configuration of Ray
within it. Other Docker setups need the same two things: a ray.php and a way to get it into the container.
You can specify a ray.php file which
contains configuration for Ray. With docker-magento, I would
create a file on the host at src/ray.php with the contents:
<?php
declare(strict_types=1);
return [
/*
* The host used to communicate with the Ray app.
*/
"host" => "host.docker.internal",
/*
* Absolute base path of your project inside the container.
*/
"remote_path" => "/var/www/html",
/*
* Absolute base path of your project on your local computer, where your IDE or code editor is running.
*/
"local_path" => "/Users/markshust/Sites/magento/src",
];Note that only configurations that differ from default values need to be included in this file. This avoids redundant data, and makes this file easier to process and maintain.
Be sure to replace local_path with your local project path, as surely your username is not markshust 😛.
Copy configuration to Docker
Since most files within my src directory are not bind-mounted into Docker for performance reasons, I still need to make
this file available to Ray, so it is aware of it. docker-magento overwrites compose.yaml and compose.dev.yaml when you
run bin/update, so I'll add the mount to a compose.override.yaml file in the project root instead. It loads last and
wins, and bin/update never touches it. If you need to run commands in your Docker setup, learning how to
execute commands in containers in Docker will be helpful:
# compose.override.yaml
services:
phpfpm:
volumes:
- ./src/ray.php:/var/www/html/ray.php:cached
phpfpm-xdebug:
volumes:
- ./src/ray.php:/var/www/html/ray.php:cachedExposing this file to /var/www/html/ray.php makes Ray aware of this configuration. You'll also need to restart your
Docker containers for the changes to take effect. This can be done within docker-magento by running bin/restart.
Need a Magento development environment? Check out this Docker setup that I've been maintaining since 2016:
Install the Ray app
In order to catch requests sent with Ray, you will need to install the client software. This involves downloading the Ray app for your specific operating system. After installing the client, you can open up the app.
Call ray() within your code
To see ray() calls in the Ray GUI, you'll need to add some!
Within Magento, we can open up pub/index.php, which is the initial bootstrap file which kicks off the application
process. Anywhere after the call to bootstrap.php, which loads the initial Composer dependencies (including Ray), we
can add a call to ray(), like so:
<?php
...
try {
require __DIR__ . "/../app/bootstrap.php";
...
}
// Here is our call to ray()!
ray("hi there!");
$bootstrap = Bootstrap::create(BP, $_SERVER);
...If you then execute any request in Magento, you will see the result:
It's important to note that if you are using docker-magento, you cannot make this file change on your host machine and expect to see the changes, as the
pub/index.phpfile is not bi-directionally synced from the host to the container. So to test this, you can runbin/bash, and then modify the file within the container.
Ray gives you Xdebug-level detail on a variable without setting a breakpoint. For example, calling:
ray($app)->blue();...within the pub/index.php file outputs the entire $app variable. The call to the ->blue() function adds
some categorization to the debug call, so you can filter results to only show logs matching that criteria:
Toggling that little icon in the top right of the window can also ensure this window persistently floats above all other windows on your desktop.
Is it safe to leave ray() calls in production?
No, so don't plan on it. What happens depends on how Ray got installed:
- Installed with
--dev(as above): production runscomposer install --no-dev, soray()doesn't exist there. A stray call is aCall to undefined function ray()fatal error. - Installed as a regular dependency: every
ray()call quietly tries to reach the Ray app on port 23517 from your production server. Nothing shows up anywhere, but you're paying for the attempt on every request.
Keep Ray as a dev dependency, and catch stray calls before they ship with a grep in CI (for example
! grep -rnE "\bray\(" app/code). If Ray must be installed on a server, ship a ray.php there with "enable" => false.
Filtering, colors and pausing execution
Colors are just the tip of the iceberg. Ray can also filter by type, measure execution time, and pause execution until you hit continue in the app. Experiment and find more by reading the Ray documentation.
Drop a ray() into the next plugin you're debugging and see how much log-grepping it saves you. If you'd rather poke at
Magento objects interactively, Tinkerwell is the other tool I reach for.




