Class Declaration and Properties
Basic Class Declaration
<?php
namespace Vendor\Module\Model;
class Product
{
// Properties with type declarations (PHP 7.4+)
public int $id;
public string $name;
public float $price;
public string $sku;
public int $status; // 1=enabled, 2=disabled
public ?string $description = null;
private ?\DateTimeImmutable $createdAt = null;
protected array $metadata = [];
// Constructor (PHP 8.0+ promoted properties)
public function __construct(
public string $name,
public float $price,
public string $sku,
) {
$this->createdAt = new \DateTimeImmutable();
$this->status = 1; // Default to enabled
}
// Methods
public function getFormattedPrice(): string
{
return '\$' . number_format($this->price, 2);
}
public function isActive(): bool
{
return $this->status === 1;
}
// Getter pattern
public function getDescription(): string
{
return $this->description ?? 'No description available';
}
// Setter pattern
public function setPrice(float $price): self
{
if ($price < 0) {
throw new \InvalidArgumentException('Price cannot be negative');
}
$this->price = $price;
return $this; // Enable method chaining
}
// Method chaining
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function setSku(string $sku): self
{
$this->sku = $sku;
return $this;
}
}
// Usage with method chaining
$product = (new Product('Widget', 29.99, 'WDG-001'))
->setName('Widget Pro')
->setPrice(39.99);
echo $product->getFormattedPrice(); // '$39.99'
PHP 8 Constructor Promotion
<?php
// Before PHP 8 - verbose
class ProductOld
{
private string $name;
private float $price;
private string $sku;
public function __construct(string $name, float $price, string $sku)
{
$this->name = $name;
$this->price = $price;
$this->sku = $sku;
}
}
// PHP 8+ - constructor promotion
class Product
{
public function __construct(
public readonly string $name,
public readonly float $price,
public readonly string $sku,
) {
// Constructor body for additional logic
}
}
// readonly properties can only be set once (in constructor)
$product = new Product('Widget', 29.99, 'WDG-001');
// $product->price = 49.99; // Error! readonly property
Property Types and Defaults
<?php
class ProductConfig
{
// Typed properties with defaults
public string $name = '';
public float $price = 0.0;
public int $stock = 0;
public bool $active = true;
public array $tags = [];
public ?string $image = null;
// Readonly properties (PHP 8.1+)
public readonly int $id;
public readonly \DateTimeImmutable $createdAt;
public function __construct(int $id)
{
$this->id = $id;
$this->createdAt = new \DateTimeImmutable();
}
}
Key Takeaway
PHP classes use properties with type declarations and visibility modifiers. PHP 8 constructor promotion reduces boilerplate code. Use readonly for immutable properties.
Visibility Modifiers
Public, Private, Protected
<?php
namespace Vendor\Module\Model;
class Product
{
// PUBLIC - accessible from anywhere
public string $name;
public float $price;
// PRIVATE - accessible only within this class
private string $internalCode;
private array $priceHistory = [];
// PROTECTED - accessible within this class and child classes
protected int $status;
protected array $metadata = [];
public function __construct(string $name, float $price)
{
$this->name = $name;
$this->price = $price;
$this->internalCode = $this->generateCode($name);
$this->status = 1;
}
// Private method - only called within this class
private function generateCode(string $name): string
{
return strtoupper(substr($name, 0, 3)) . '-' . rand(100, 999);
}
// Private method for internal tracking
private function recordPrice(float $oldPrice, float $newPrice): void
{
$this->priceHistory[] = [
'old' => $oldPrice,
'new' => $newPrice,
'date' => date('Y-m-d H:i:s')
];
}
// Public method that uses private internals
public function setPrice(float $newPrice): self
{
$oldPrice = $this->price;
$this->price = $newPrice;
$this->recordPrice($oldPrice, $newPrice);
return $this;
}
// Protected method - accessible by children
protected function validateStatus(int $status): bool
{
return in_array($status, [1, 2, 3]);
}
// Public interface
public function setStatus(int $status): self
{
if (!$this->validateStatus($status)) {
throw new \InvalidArgumentException('Invalid status');
}
$this->status = $status;
return $this;
}
}
Visibility Rules
<?php
// Access from outside the class
$product = new Product('Widget', 29.99);
$product->name = 'Gadget'; // OK - public
$product->price = 49.99; // OK - public
// $product->internalCode = 'X'; // ERROR - private
// $product->status = 2; // ERROR - protected
// Access from child class
class DiscountedProduct extends Product
{
public function applyDiscount(float $percent): self
{
// $this->name = 'Discounted Widget'; // OK - public
// $this->status = 1; // OK - protected
// $this->internalCode = 'DISC-001'; // ERROR - private
$this->price *= (1 - $percent / 100);
return $this;
}
}
// Access from same namespace (NO - PHP doesn't have package-private)
class AnotherProduct
{
public function test(Product $product): void
{
// $product->internalCode; // ERROR - private
// $product->status; // ERROR - protected
$product->name; // OK - public
}
}
Best Practices
<?php
class Product
{
// PUBLIC: API surface - what consumers need
public function __construct(
public readonly string $name,
public readonly float $price,
) {}
// PRIVATE: Internal implementation details
private ?string $cachedFormattedPrice = null;
// PROTECTED: Extension points for child classes
protected function calculateDiscountedPrice(float $percent): float
{
return $this->price * (1 - $percent / 100);
}
// Public method uses private cache
public function getFormattedPrice(): string
{
if ($this->cachedFormattedPrice === null) {
$this->cachedFormattedPrice = '\$' . number_format($this->price, 2);
}
return $this->cachedFormattedPrice;
}
}
| Modifier | Same Class | Child Class | Outside |
|---|---|---|---|
| public | Yes | Yes | Yes |
| protected | Yes | Yes | No |
| private | Yes | No | No |
Key Takeaway
Use public for the API surface, protected for extension points, and private for implementation details. This encapsulation protects internal state and makes code maintainable.
Static Methods, Magic Methods, and Magento Patterns
Static Methods and Properties
<?php
namespace Vendor\Module\Model;
class ProductCounter
{
// Static property - shared across all instances
private static int $count = 0;
public function __construct()
{
self::$count++;
}
// Static method - called without instantiation
public static function getCount(): int
{
return self::$count;
}
public static function reset(): void
{
self::$count = 0;
}
}
// Usage
$a = new ProductCounter();
$b = new ProductCounter();
echo ProductCounter::getCount(); // 2
Magic Methods
<?php
namespace Vendor\Module\Model;
class Product
{
private array $data = [];
// __get - called when accessing undefined property
public function __get(string $name)
{
return $this->data[$name] ?? null;
}
// __set - called when setting undefined property
public function __set(string $name, mixed $value): void
{
$this->data[$name] = $value;
}
// __isset - called with isset() or empty()
public function __isset(string $name): bool
{
return isset($this->data[$name]);
}
// __unset - called with unset()
public function __unset(string $name): void
{
unset($this->data[$name]);
}
// __toString - called when object is used as string
public function __toString(): string
{
return $this->data['name'] ?? 'Unknown Product';
}
// __call - called when invoking undefined method
public function __call(string $method, array $args)
{
// Dynamic method handling
if (str_starts_with($method, 'get')) {
$property = lcfirst(substr($method, 3));
return $this->data[$property] ?? null;
}
throw new \BadMethodCallException("Method $method not found");
}
}
// Usage
$product = new Product();
$product->name = 'Widget'; // Uses __set
echo $product->name; // Uses __get
echo $product; // Uses __toString
echo $product->getName(); // Uses __call
Magento OOP Patterns
<?php
namespace Vendor\Module\Model;
use Magento\Catalog\Model\Product as MagentoProduct;
class ProductRepository
{
// Dependency injection via constructor
public function __construct(
private \Magento\Catalog\Model\ResourceModel\Product\Collection $collection,
private \Magento\Framework\Event\ManagerInterface $eventManager,
private \Psr\Log\LoggerInterface $logger
) {}
// Repository pattern
public function getById(int $id): ?MagentoProduct
{
try {
return $this->collection->load($id);
} catch (\Exception $e) {
$this->logger->error('Product load failed: ' . $e->getMessage());
return null;
}
}
// Factory pattern
public function create(array $data): MagentoProduct
{
$product = $this->productFactory->create();
$product->setData($data);
return $product;
}
// Save with event dispatch
public function save(MagentoProduct $product): void
{
$this->eventManager->dispatch('catalog_product_save_before', [
'product' => $product
]);
$product->save();
$this->eventManager->dispatch('catalog_product_save_after', [
'product' => $product
]);
}
}
Object Comparison
<?php
$a = new Product('Widget', 29.99);
$b = new Product('Widget', 29.99);
// == compares all properties
echo $a == $b; // true (same values)
// === compares identity (same instance)
echo $a === $b; // false (different objects)
$b = $a;
echo $a === $b; // true (same reference)
Key Takeaway
Static methods belong to the class, not instances. Magic methods enable dynamic behavior (getters/setters). Magento uses dependency injection, repository, and factory patterns extensively.
Quiz
1. What is the difference between public, private, and protected?
2. What does PHP 8 constructor promotion do?
3. What is a static method?
4. What does the __get magic method do?
5. What is the readonly keyword (PHP 8.1)?
Flashcards
Question
What are the three visibility modifiers in PHP?
Click to reveal answer
Answer
public (accessible anywhere), protected (accessible in class and children), private (accessible only in the class itself).
Question
What is constructor promotion (PHP 8)?
Click to reveal answer
Answer
Shorthand for declaring and assigning properties in the constructor. Example: public readonly string $name creates property and assigns it.
Question
What is a static method?
Click to reveal answer
Answer
A method that belongs to the class, not instances. Called without instantiation: ClassName::method(). Shared state via static properties.
Question
What does __toString() do?
Click to reveal answer
Answer
Magic method called when an object is used as a string. Must return a string. Enables: echo $object; or 'text ' . $object.
Question
What is method chaining?
Click to reveal answer
Answer
Returning $this from methods allows calling multiple methods in sequence. Example: $obj->setName('X')->setPrice(10)->save();
Question
What is the readonly keyword?
Click to reveal answer
Answer
Makes a property settable only once (usually in constructor). Prevents modification after initialization. Useful for immutable data.
Question
What does == vs === mean for objects?
Click to reveal answer
Answer
==: compares all properties (value equality). ===: compares object identity (same instance in memory).
Question
What magic method is called for undefined property access?
Click to reveal answer
Answer
__get is called for reading, __set for writing, __isset for isset()/empty(), __unset for unset().
Revision Notes
Key Takeaways
- 1. Public: accessible from anywhere. Protected: class + children. Private: class only.
- 2. PHP 8 constructor promotion reduces boilerplate for property assignment
- 3. Static methods/properties belong to the class, not instances
- 4. Magic methods (__get, __set, __toString, __call) enable dynamic behavior
- 5. readonly properties can only be set once (usually in constructor)
- 6. Method chaining returns $this for fluent API design
- 7. Magento uses DI, Repository, and Factory patterns extensively
Interview Tips
- • Explain the difference between public, private, and protected
- • Describe how constructor promotion works in PHP 8
- • Know when to use static vs instance methods
- • Explain magic methods and their use cases
- • Understand Magento's DI and repository patterns
Cheat Sheet
PHP Classes Cheat Sheet
Class Declaration:
class Product {
public string $name;
private float $price;
protected int $status;
}
Constructor Promotion (PHP 8):
public function __construct(
public readonly string $name,
public readonly float $price,
) {}
Visibility:
- public: anywhere
- protected: class + children
- private: class only
Static:
static $count = 0;
static function getCount(): int { return self::$count; }
Magic Methods:
__get, __set, __isset, __unset
__toString, __call, __construct, __destruct
Method Chaining:
return $this; // Enables $obj->a()->b()->c();