"Call to a member function get() on null" means the variable to the left of -> is null. PHP never reaches get(). Something earlier handed you null instead of an object: a dependency that was never assigned, a lookup that found nothing, or a row that's missing from the database. Find that earlier step and you've found the bug.
It's a plain PHP error, not a Magento one, so Laravel and Drupal throw the exact same thing. Since PHP 8 it's thrown as an Error, which is why the log line looks like this:
PHP Fatal error: Uncaught Error: Call to a member function get() on null in /var/www/html/app/code/Acme/Store/Controller/Index/Index.php:27What "on null" means
The important part of the message is "on null". This:
$item->get();...fails because get() is being called on $item, and $item is null. That's the whole message.
The method name in the error (get(), getCategoryId(), getParentItem(), whatever) only tells you where PHP stopped. The bug is never in that method. It's in whatever was supposed to put an object into that variable.
The most common Magento cause: a constructor that skips parent::__construct()
When the method is literally get(), the thing on the left is usually a dependency stored on a property: $this->_objectManager->get(), $this->productRepository->get($sku) and so on. The property exists, but nothing ever assigned it.
The classic way to get there is extending a core class, overriding its constructor to add your own dependency, and forgetting the parent call:
<?php
declare(strict_types=1);
namespace Acme\Store\Controller\Index;
use Magento\Framework\App\Action\Action;
use Magento\Framework\App\Action\Context;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\Controller\Result\JsonFactory;
class Index extends Action
{
private JsonFactory $jsonFactory;
public function __construct(Context $context, JsonFactory $jsonFactory)
{
$this->jsonFactory = $jsonFactory;
// parent::__construct($context) is missing
}
public function execute()
{
// $this->_objectManager is null, so this line fatals
$scopeConfig = $this->_objectManager->get(ScopeConfigInterface::class);
return $this->jsonFactory->create()->setData([
'store_name' => $scopeConfig->getValue('general/store_information/name'),
]);
}
}Magento\Framework\App\Action\Action fills $this->_objectManager (plus messageManager, _redirect and friends) from Context inside its own constructor. Skip parent::__construct($context) and every one of those protected properties stays null, so the first ->get() on one of them throws. Blocks and anything else that takes a Context object work the same way.
The quick fix is adding the missing line:
public function __construct(Context $context, JsonFactory $jsonFactory)
{
parent::__construct($context);
$this->jsonFactory = $jsonFactory;
}The better fix is to stop inheriting. Action has been deprecated since framework 103.0.0 in favor of composition, so implement the HTTP action interface and inject exactly what you use:
<?php
declare(strict_types=1);
namespace Acme\Store\Controller\Index;
use Magento\Framework\App\Action\HttpGetActionInterface;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\Controller\Result\Json;
use Magento\Framework\Controller\Result\JsonFactory;
class Index implements HttpGetActionInterface
{
public function __construct(
private readonly ScopeConfigInterface $scopeConfig,
private readonly JsonFactory $jsonFactory,
) {
}
public function execute(): Json
{
return $this->jsonFactory->create()->setData([
'store_name' => $this->scopeConfig->getValue('general/store_information/name'),
]);
}
}With constructor property promotion there's no property that can be left unassigned, and there's no parent constructor to forget. If you're not sure what actually gets injected into a class, bin/magento dev:di:info prints every constructor argument and its resolved type. And if _objectManager->get() calls are all over the codebase you inherited, this article on the object manager explains why constructor injection replaces them.
A null from a lookup: the PHTML example
The other big family is a lookup that came back empty. Here's what it looks like in Magento, from a PHTML template:
<?php
/* @var \Magento\Catalog\Block\Product\View $block */
$product = $block->getProduct();
$category = $product->getCategoryId();Executing this returns:
Call to a member function getCategoryId() on null
Here's what PHP sees when it runs the code:
<?php
/* @var \Magento\Catalog\Block\Product\View $block */
$product = $block->getProduct(); // $product is evaluating to null
$category = $product->getCategoryId(); // null->getCategoryId() throws an errorThe code tries to grab the category ID with getCategoryId(), but since $product is null, it throws. The snippet essentially evaluates to:
$product = null;
$category = $product->getCategoryId(); // error!So the root of the error isn't the call to getCategoryId(). It's that $product is null.
Why does this error happen in a PHTML template?
$block->getProduct() reads the current product from the registry, and the product page controller is what puts it there. Render the same block on a category page, the cart or a CMS page and there's no product to read, so you get null. Either only render the block on product pages (fix the layout handle), or pass the product into the block explicitly.
Issues within looping code
The same thing happens inside loops. A common scenario is loading a collection of objects, then trying to retrieve a child object from each item:
<?php $allShipmentItems = $shipmentItems->getAllItems(); ?>
<?php foreach ($allShipmentItems as $shipmentItem): ?>
<?php $parentItem = $shipmentItem->getOrderItem()->getParentItem(); ?>
<?php if (!$parentItem): ?>
<tbody>
<?= $block->getItemHtml($shipmentItem) ?>
</tbody>
<?php endif; ?>
<?php endforeach; ?>And this returns:
Call to a member function getParentItem() on null
When getParentItem() runs, PHP bails out because $shipmentItem->getOrderItem() evaluated to null. So we need to find out why getOrderItem() returns null, which means inspecting that method.
Stepping into the function
Stepping into getOrderItem() leads to Magento\Sales\Model\Order\Shipment\Item::getOrderItem().
This code checks whether the _orderItem property is set. If it isn't, it grabs the order item in one of two ways:
/**
* Retrieve order item instance
*
* @return \Magento\Sales\Model\Order\Item
*/
public function getOrderItem()
{
if (null === $this->_orderItem) {
if ($this->getShipment()) {
$this->_orderItem = $this->getShipment()->getOrder()->getItemById($this->getOrderItemId());
} else {
$this->_orderItem = $this->_orderItemFactory->create()->load($this->getOrderItemId());
}
}
return $this->_orderItem;
}If the item has a shipment, the first branch runs and asks the shipment's order for the item. Otherwise, it falls back to loading the order item by its ID with the order item factory.
Note that getShipment()->getOrder() can itself return null. Then you get the same error a step earlier in the chain: Call to a member function getItemById() on null. Same diagnosis, different line.
Set a breakpoint with Xdebug
Xdebug is the fastest way to confirm which step returns null. Learning how to create a conditional breakpoint in PhpStorm with Xdebug is especially useful inside a loop, since you only want to stop on the item that breaks. (If you don't have Xdebug wired up, Spatie Ray or Tinkerwell will show you the same values.)
Since we care about the value assigned to _orderItem, set a breakpoint inside this method and inspect what gets assigned. That tells us which branch is executing.
Here, the first branch is the culprit: the shipment exists, but the order hands back null for the item. In the debugger, $this->getOrderItemId() returns a value, so the ID is there. That means the problem lives in getItemById(), so let's step into it:
/**
* Gets order item by given ID.
*
* @param int $itemId
* @return \Magento\Framework\DataObject|null
*/
public function getItemById($itemId)
{
$items = $this->getItems();
$itemId = $itemId ?? '';
if (isset($items[$itemId])) {
return $items[$itemId];
}
return null;
}Walking through this code, $this->getItems() doesn't contain the ID we're asking for. The shipment item points at an order item that the order doesn't have, so getItemById() returns null, and that null is what blew up two calls later.
What was actually wrong: orphaned order data
This one is a data integrity problem. Maybe a migration of the order's data failed, or the order item rows got deleted or corrupted. Either way, $shipmentItem->getOrderItem()->getParentItem() failed because the order item didn't exist in the first place.
How to fix it
Once you've found where the null comes from, there are three ways out, in order of preference.
1. Fix the source. If a dependency was never assigned, assign it (call the parent constructor, or move to constructor injection). If the data is missing, repair the data. This is the real fix, and it's usually the right one.
2. Guard it, when null is a legitimate state. Some lookups are allowed to find nothing. Check for it and bail out early:
$product = $block->getProduct();
if ($product === null) {
return;
}
$categoryId = $product->getCategoryId();3. Use the nullsafe operator. Since PHP 8.0, ?-> short-circuits to null instead of throwing:
$categoryId = $block->getProduct()?->getCategoryId();Can I just use the nullsafe operator?
Only when null is an expected, valid result. ?-> makes the error go away, but it doesn't make the data come back, so on a real bug (orphaned order items, a missing dependency) it just moves the failure somewhere quieter. The same goes for wrapping the call in try { ... } catch (\Throwable $e). You can catch this Error, but you shouldn't, because you'd be hiding the exact signal that told you something upstream is broken.
Nine times out of ten it's missing or orphaned data, or a dependency that was never assigned. The rest of the time a parent method skipped a step, and Xdebug shows you which one.
If you want to see where these objects come from in the first place, the Magento 2 request-response lifecycle walks through what happens between the URL and your template, and the Magento 2 Coding Jumpstart course is the place to start if you're newer to Magento backend code.



