Objects Namespaces And Application Architecture

Classes and Objects

A class defines a kind of object that can exist in a PHP program. It gives that concept a name and describes its properties and methods. An object is one runtime instance created from that class, with its own state.

This distinction matters. Product is a definition; $keyboard and $mouse are separate objects created from it. The class says what every product object can hold and do, while each object supplies the actual values.

Classes are most useful when data and behavior belong together. They replace an implied array shape with an explicit model, put operations beside the state they use, and create a place to protect rules as an application grows.

Define A Class And Create An Object

Use the class keyword to define a class and new to create an object.

PHP example
<?php

declare(strict_types=1);

class Product
{
    public string $sku;
    public string $name;
    public int $pricePennies;
}

$keyboard = new Product();
$keyboard->sku = 'KB-101';
$keyboard->name = 'Keyboard';
$keyboard->pricePennies = 2499;

echo $keyboard->name . ' costs ' . $keyboard->pricePennies . ' pennies';
echo PHP_EOL;

// Prints:
// Keyboard costs 2499 pennies

The -> operator accesses an object's property or method. PHP uses ->, not the dot syntax used by some other languages. The dot operator in PHP concatenates strings.

Class names conventionally use StudlyCaps, while property, method, and variable names usually use camelCase. Choose names from the problem domain: Invoice, BasketLine, and PasswordResetToken communicate more than DataObject or Manager.

Each Object Has Its Own State

Objects created from the same class share the class definition, not their instance property values.

PHP example
<?php

declare(strict_types=1);

class Counter
{
    public int $value = 0;

    public function increment(): void
    {
        $this->value++;
    }
}

$downloads = new Counter();
$logins = new Counter();

$downloads->increment();
$downloads->increment();
$logins->increment();

echo 'downloads: ' . $downloads->value . PHP_EOL;
echo 'logins: ' . $logins->value . PHP_EOL;

// Prints:
// downloads: 2
// logins: 1

$this means the object on which the current method was called. When $downloads->increment() runs, $this is $downloads. When $logins->increment() runs, $this is $logins.

A method that uses $this works with that object's state. This is different from a standalone function receiving a loose array and relying on callers to provide the expected keys.

Typed Properties Need A Value Before Reading

A declaration such as public string $name; gives the property a type but not a value. Reading it before assignment causes an error.

PHP example
<?php

declare(strict_types=1);

class Job
{
    public string $status;
}

$job = new Job();
$job->status = 'queued';

echo $job->status . PHP_EOL;

// Prints:
// queued

A property may instead have a valid default:

PHP example
<?php

declare(strict_types=1);

class ImportProgress
{
    public int $processedRows = 0;
    public bool $complete = false;
}

$progress = new ImportProgress();

echo $progress->processedRows . PHP_EOL;

// Prints:
// 0

Do not invent a misleading default just to avoid initialization. An empty email address is not a useful default if every real object requires an email. Constructors, covered in the next lessons, let creation require the values necessary for a valid object.

Typed properties reject incompatible values. Under normal object assignment, PHP will not let an array be stored in a property declared as Product, or null in a property that does not include null. Types protect the representation; domain checks still protect rules such as a positive quantity.

Put Behavior Beside The State It Uses

A class is more than a named record. Methods let the object answer questions or perform changes using its own data.

PHP example
<?php

declare(strict_types=1);

class BasketLine
{
    public int $unitPricePennies;
    public int $quantity;

    public function lineTotalPennies(): int
    {
        return $this->unitPricePennies * $this->quantity;
    }

    public function increaseQuantity(): void
    {
        $this->quantity++;
    }
}

$line = new BasketLine();
$line->unitPricePennies = 1299;
$line->quantity = 2;
$line->increaseQuantity();

echo $line->lineTotalPennies() . PHP_EOL;

// Prints:
// 3897

The total calculation belongs with the price and quantity it uses. If every caller calculates the total independently, rounding rules or later changes can become inconsistent.

Methods should reflect meaningful operations. increaseQuantity() says more than exposing a generic setQuantity() if increasing is the actual workflow. As you learn constructors and visibility, methods can reject invalid changes and keep the object valid throughout its lifetime.

Objects Versus Associative Arrays

An associative array can represent the same product data:

PHP example
<?php

declare(strict_types=1);

$product = [
    'sku' => 'KB-101',
    'name' => 'Keyboard',
    'pricePennies' => 2499,
];

echo $product['name'] . PHP_EOL;

// Prints:
// Keyboard

The array is not inherently wrong. Arrays are natural at boundaries where data is decoded from JSON, fetched as a database row, parsed from CSV, or prepared for a template. The problem is allowing an unverified boundary shape to travel everywhere.

With an array, the expected keys and value types are implied. A misspelling such as pricePenneis creates a separate key, and behavior has no obvious home. A class gives the shape a name, allows native property types, and lets methods operate on it.

A common application flow is:

  1. receive an array or string at an external boundary
  2. validate and parse it
  3. create application objects
  4. work with those objects in core code
  5. serialize the result back to a boundary format

Do not convert every small local array into a class. Use a class when the concept recurs, has behavior or rules, crosses several functions, or benefits from a stable named contract.

Object Variables Refer To Objects

Assigning an object variable to another variable does not create an independent copy. Both variables refer to the same object.

PHP example
<?php

declare(strict_types=1);

class Attempt
{
    public int $count = 0;
}

$first = new Attempt();
$second = $first;

$second->count++;

echo $first->count . PHP_EOL;
echo $second->count . PHP_EOL;

// Prints:
// 1
// 1

This surprises developers who expect object assignment to behave like copying a scalar. The variables are separate, but they point to the same object identity. A change through either variable is visible through the other.

Use clone when an independent object copy is genuinely required:

PHP example
<?php

declare(strict_types=1);

class FilterOptions
{
    public string $sort = 'newest';
}

$default = new FilterOptions();
$popular = clone $default;
$popular->sort = 'popular';

echo $default->sort . PHP_EOL;
echo $popular->sort . PHP_EOL;

// Prints:
// newest
// popular

Cloning is shallow by default. Nested object properties can still be shared unless cloning behavior handles them explicitly. A later lesson covers object cloning in depth.

Identity And Equal State Are Different

PHP provides two useful object comparisons. === asks whether two variables refer to the same object instance. == asks whether objects are instances of the same class with equal property values.

PHP example
<?php

declare(strict_types=1);

class Coordinate
{
    public int $x;
    public int $y;
}

$first = new Coordinate();
$first->x = 4;
$first->y = 9;

$alias = $first;

$second = new Coordinate();
$second->x = 4;
$second->y = 9;

echo $first === $alias ? 'same identity' : 'different identity';
echo PHP_EOL;
echo $first == $second ? 'equal state' : 'different state';
echo PHP_EOL;
echo $first === $second ? 'same identity' : 'different identity';
echo PHP_EOL;

// Prints:
// same identity
// equal state
// different identity

For important domain values, an explicit method such as Money::equals() can communicate the intended comparison more clearly than relying on every property PHP happens to see.

Objects Can Collaborate

One object can receive another object and use its public behavior. This is how larger applications are assembled from focused responsibilities.

PHP example
<?php

declare(strict_types=1);

class PriceFormatter
{
    public function pennies(int $amount): string
    {
        return 'GBP ' . number_format($amount / 100, 2);
    }
}

class ProductLabel
{
    public string $name;
    public int $pricePennies;

    public function display(PriceFormatter $formatter): string
    {
        return $this->name . ': ' . $formatter->pennies($this->pricePennies);
    }
}

$product = new ProductLabel();
$product->name = 'Keyboard';
$product->pricePennies = 2499;

echo $product->display(new PriceFormatter()) . PHP_EOL;

// Prints:
// Keyboard: GBP 24.99

This example uses a type declaration to require a PriceFormatter. Later lessons improve this design with constructors, interfaces, composition, and dependency injection. The important starting point is that objects can delegate work instead of one class doing everything.

Model Responsibilities, Not Database Tables Automatically

Some classes represent domain concepts such as Order, Money, or Subscription. Others perform work, such as CsvImporter, PasswordHasher, or InvoiceRenderer. A class does not need to correspond to a database table.

Avoid classes with vague ownership and unrelated methods. A UserManager that registers users, exports reports, sends emails, updates invoices, and clears caches is several responsibilities hidden behind one name. Smaller classes are easier to understand when their boundaries follow actual behavior.

At the other extreme, do not create empty wrapper classes with no clearer contract than the value they contain. A class earns its place by making the model, rules, or collaboration easier to understand.

Protecting Valid State

Public properties make introductory examples visible, but they allow incomplete or invalid objects:

PHP example
<?php

declare(strict_types=1);

class OrderLine
{
    public int $unitPricePennies;
    public int $quantity;
}

$line = new OrderLine();
$line->unitPricePennies = -500;
$line->quantity = 0;

echo 'The types are valid, but the business state is not.' . PHP_EOL;

// Prints:
// The types are valid, but the business state is not.

Both values satisfy the int type. Neither makes sense for this order line. Constructors can require initial data, visibility can prevent unrestricted writes, and methods can enforce allowed transitions. Those tools build on the class-and-object foundation established here.

What You Should Be Able To Do

After this lesson, you should be able to define a class, instantiate several independent objects, access typed properties and methods, and explain what $this refers to. You should know that an uninitialized typed property cannot be read and that assigning an object to another variable shares the same object rather than copying it.

You should also be able to choose between an array and a class based on the role of the data, distinguish object identity from equal state, and identify when a class is accumulating unrelated responsibilities. The next OOP lessons add the controls needed to construct valid objects and protect their internal state.

Practice

Task: Model a basket line

Create a small class for a basket line.

Requirements

  • Use declare(strict_types=1);.
  • Create a BasketLine class.
  • Give it public properties for sku, unitPricePennies, and quantity.
  • Add a lineTotalPennies() method.
  • Create two different BasketLine objects.
  • Print each line total.
  • Print the combined total.
  • Include the expected output as comments in the same PHP code block.

The goal is to show that two objects can come from the same class while holding different state.

Show solution
PHP example
<?php

declare(strict_types=1);

class BasketLine
{
    public string $sku;
    public int $unitPricePennies;
    public int $quantity;

    public function lineTotalPennies(): int
    {
        return $this->unitPricePennies * $this->quantity;
    }
}

$keyboard = new BasketLine();
$keyboard->sku = 'KB-101';
$keyboard->unitPricePennies = 2499;
$keyboard->quantity = 1;

$mouse = new BasketLine();
$mouse->sku = 'MS-202';
$mouse->unitPricePennies = 1299;
$mouse->quantity = 2;

$combinedTotal = $keyboard->lineTotalPennies() + $mouse->lineTotalPennies();

echo $keyboard->sku . ': ' . $keyboard->lineTotalPennies() . PHP_EOL;
echo $mouse->sku . ': ' . $mouse->lineTotalPennies() . PHP_EOL;
echo 'Total: ' . $combinedTotal . PHP_EOL;

// Prints:
// KB-101: 2499
// MS-202: 2598
// Total: 5097

Both objects use the same BasketLine class, but each object has its own SKU, price, and quantity. The method keeps the line-total calculation next to the data it uses.

Practice: Trace Object Handles And Identity

Build a small example that proves assignment and cloning have different effects for objects.

Task

Create a DownloadCounter class with a public integer count property and an increment() method. Then:

  • create one original object
  • assign it to an alias variable
  • clone it into a third variable
  • increment through the alias twice
  • increment the clone once
  • print all three counts
  • compare the original with the alias and clone using ===

Include the expected output in comments.

Check Your Work

Explain why the original changes when the alias changes, why the clone has independent top-level state, and what === checks for objects.

Show solution

The alias refers to the original object, while clone creates another object identity.

PHP example
<?php

declare(strict_types=1);

class DownloadCounter
{
    public int $count = 0;

    public function increment(): void
    {
        $this->count++;
    }
}

$original = new DownloadCounter();
$alias = $original;
$copy = clone $original;

$alias->increment();
$alias->increment();
$copy->increment();

echo 'original: ' . $original->count . PHP_EOL;
echo 'alias: ' . $alias->count . PHP_EOL;
echo 'copy: ' . $copy->count . PHP_EOL;
echo $original === $alias ? 'original and alias are identical' : 'different';
echo PHP_EOL;
echo $original === $copy ? 'original and copy are identical' : 'original and copy differ';
echo PHP_EOL;

// Prints:
// original: 2
// alias: 2
// copy: 1
// original and alias are identical
// original and copy differ

$original and $alias refer to one object, so mutations through either name affect the same state. The clone has a separate top-level object and === reports that its identity differs.

Practice: Refactor A Reading Array

Replace an implied associative-array shape with a class that owns its display behavior.

Starting Data

PHP example
$reading = [
    'station' => 'office',
    'celsius' => 21.4,
];

Task

Create a TemperatureReading class with public typed properties for station and celsius. Add:

  • a fahrenheit(): float method
  • a summary(): string method that includes the station and both temperatures

Create two separate readings with different values and print both summaries. Round displayed temperatures to one decimal place and include expected output comments.

Check Your Work

Explain what information was only implied by the array, what the class makes explicit, and why conversion behavior belongs beside the temperature state.

Show solution

The class gives the data shape a name and provides one implementation of the conversion rule.

PHP example
<?php

declare(strict_types=1);

class TemperatureReading
{
    public string $station;
    public float $celsius;

    public function fahrenheit(): float
    {
        return ($this->celsius * 9 / 5) + 32;
    }

    public function summary(): string
    {
        return sprintf(
            '%s: %.1f C / %.1f F',
            $this->station,
            $this->celsius,
            $this->fahrenheit(),
        );
    }
}

$office = new TemperatureReading();
$office->station = 'office';
$office->celsius = 21.4;

$warehouse = new TemperatureReading();
$warehouse->station = 'warehouse';
$warehouse->celsius = 8.0;

echo $office->summary() . PHP_EOL;
echo $warehouse->summary() . PHP_EOL;

// Prints:
// office: 21.4 C / 70.5 F
// warehouse: 8.0 C / 46.4 F

The array only implied its required keys and value types. The class makes those properties explicit and keeps the Celsius-to-Fahrenheit rule consistent for every reading object.