Skip to content
intermediate Phase 28 · DI Advanced

Shared vs Non-Shared Objects

Understanding Magento 2 shared vs non-shared objects: singleton behavior, state management, and configuration patterns

45m
0 problems
Topic Progress 0%

Shared vs Non-Shared Fundamentals

Shared Objects (Singletons)

By default, ObjectManager returns the same instance:

$service1 = $objectManager->get(WarrantyService::class);
$service2 = $objectManager->get(WarrantyService::class);
// $service1 === $service2 (same object)

Non-Shared Objects

Creates new instance each time:

$warranty1 = $objectManager->create(Warranty::class);
$warranty2 = $objectManager->create(Warranty::class);
// $warranty1 !== $warranty2 (different objects)

Configuration

<!-- Default: shared -->
<type name="Amazon\Prep\Service\WarrantyService"/>

<!-- Explicitly non-shared -->
<type name="Amazon\Prep\Model\Warranty" shared="false"/>

<!-- Override to shared -->
<type name="Amazon\Prep\Helper\Data" shared="true"/>

Visual Comparison

Shared (Singleton):
  ObjectManager -> [Same Instance] -> All consumers
  Consumer A --+
               +--> Same WarrantyService
  Consumer B --+

Non-Shared:
  ObjectManager -> [New Instance] -> Each consumer
  Consumer A -> New Warranty
  Consumer B -> New Warranty (different)

When to Use Each Pattern

Use Shared Objects When

  1. Stateless services
class WarrantyService
{
    public function process(Warranty $warranty): void
    {
        // Only uses passed objects, no internal state
    }
}
  1. Configuration holders
class ConfigService
{
    public function __construct(
        private ScopeConfigInterface $config
    ) {}

    public function getValue(string $path): mixed
    {
        return $this->config->getValue($path);
    }
}
  1. Factories and helpers
// Factory itself is shared, objects it creates are new
$factory = $objectManager->get(WarrantyFactory::class);
  1. Cache and session managers
// Must be shared to maintain state
$session = $objectManager->get(CustomerSession::class);

Use Non-Shared Objects When

  1. Models with data
$warranty1 = $warrantyFactory->create();
$warranty1->setName('Warranty 1');

$warranty2 = $warrantyFactory->create();
$warranty2->setName('Warranty 2');
// Different objects with different data
  1. Collections
$collection1 = $collectionFactory->create();
$collection1->addFieldToFilter('status', 'active');

$collection2 = $collectionFactory->create();
$collection2->addFieldToFilter('status', 'inactive');
  1. Request/response objects
$request = $this->requestFactory->create();
  1. Data transfer objects
$dto = $this->dtoFactory->create(['data' => $specificData]);

State Management

Stateful Shared Objects

Shared objects can accumulate state within a process:

// BAD: Shared object with mutable state
class BadService
{
    private $cache = [];  // Accumulates over time

    public function process($key, $value)
    {
        $this->cache[$key] = $value;  // Never cleared!
    }
}

Stateless Shared Objects

// GOOD: Shared object without mutable state
class GoodService
{
    public function process($key, $value)
    {
        return $this->doWork($key, $value);
    }
}

Resetting Shared State

class CacheService
{
    private $cache = [];

    public function reset(): void
    {
        $this->cache = [];
    }
}

// Call periodically or in tests
$cacheService->reset();

Request Lifecycle

In web requests, shared instances are typically reset between requests:

Request 1 -> Shared instance created -> Response -> Instance destroyed
Request 2 -> New shared instance created -> Response -> Instance destroyed

Exception: Long-running processes (Swoole, queues) keep instances alive.

Thread Safety and Best Practices

Thread Safety

In multi-threaded environments (Swoole, etc.):

// Each thread gets its own ObjectManager instance
// Shared instances are per-thread, not global

// Thread 1:
$service1 = ObjectManager::getInstance()->get(Service::class);

// Thread 2:
$service2 = ObjectManager::getInstance()->get(Service::class);
// $service1 !== $service2 (different threads)

Best Practices

Default Patterns

<!-- Services: shared (default) -->
<type name="Amazon\Prep\Service\WarrantyService"/>

<!-- Models: non-shared -->
<type name="Amazon\Prep\Model\Warranty" shared="false"/>

<!-- Collections: non-shared -->
<type name="Amazon\Prep\Model\ResourceModel\Warranty\Collection" shared="false"/>

<!-- Factories: shared -->
<type name="Amazon\Prep\Model\WarrantyFactory"/>

Quick Reference

Pattern Shared? Why
Services Yes Stateless, no side effects
Helpers Yes Stateless utilities
Factories Yes Factory itself is singleton
Registries Yes Must persist state
Models No Each holds different data
Collections No Each is independent query
DTOs No Each holds different data
Request No Each request is unique

Debugging Shared Instances

// Check if same instance
$ref1 = spl_object_hash($service1);
$ref2 = spl_object_hash($service2);
echo $ref1 === $ref2 ? 'Same' : 'Different';

// List shared instances in ObjectManager
// (Debug tool required)

Common Mistakes

  1. Mutable state in shared objects
  2. Using shared for models (data bleeds between requests)
  3. Using non-shared for services (wasted resources)
  4. Forgetting factory is shared (creates objects, doesn't become object)

Quiz

1. What is the default sharing behavior in Magento DI?

Question 1 options

2. When should models be non-shared?

Question 2 options

3. What is a risk of mutable state in shared objects?

Question 3 options

Flashcards

Question

What is the default sharing in Magento DI?

Answer

Shared (singleton) - same instance returned

Question

When use non-shared objects?

Answer

Models, collections, DTOs - anything holding per-instance data

Question

When use shared objects?

Answer

Services, helpers, factories, registries - stateless or persistent state

Question

How do you make an object non-shared?

Answer

Set shared='false' on the type in di.xml

Question

What is the risk of mutable state in shared objects?

Answer

State persists between requests causing bugs

Revision Notes

Key Takeaways

  • 1. Default is shared (singleton); use shared='false' for new instances
  • 2. Services and factories should be shared; models and collections should not
  • 3. Avoid mutable state in shared objects to prevent data leaks
  • 4. Factories are shared but the objects they create are new
  • 5. Consider thread safety in multi-process environments

Interview Tips

  • Explain shared vs non-shared trade-offs
  • Know the default patterns for services, models, factories
  • Be ready to identify mutable state issues
  • Discuss request lifecycle and instance management

Cheat Sheet

Shared (default):
  Services, helpers, factories, registries
  Same instance returned

Non-shared:
  Models, collections, DTOs, requests
  New instance each time

Config: <type name="..." shared="false"/>
Check: spl_object_hash($obj1) === spl_object_hash($obj2)