Builder Pattern

The Builder Pattern is a creational design pattern used to construct complex objects step by step. This article explains how the Builder Pattern works, when to use it, how it differs from Factory Pattern, and how to implement it in PHP with practical examples.

Jun 10, 2026
Builder Pattern

Builder Pattern

The Builder Pattern is a creational design pattern used to construct complex objects step by step. Instead of creating an object with a long constructor or many optional parameters, the Builder Pattern separates the construction process from the final object.

This pattern is useful when an object has many configuration options, optional values, nested parts, or different construction steps. It helps developers create readable, flexible, and maintainable object creation logic without making constructors too large or confusing.

Introduction

In previous articles of this Design Patterns series, we discussed the Factory Pattern and Abstract Factory Pattern. Both patterns focus on object creation, but they solve different problems.

The Factory Pattern is useful when the application needs to decide which object type to create. The Abstract Factory Pattern is useful when the application needs to create families of related objects. The Builder Pattern is different because it focuses on how to build one complex object step by step.

In real software projects, some objects cannot be created easily with a simple constructor. For example, a report, invoice, email message, user profile, HTTP request, query object, or configuration object may contain many optional fields and different setup steps. Builder Pattern helps manage this complexity.

What Is the Builder Pattern?

The Builder Pattern is a creational design pattern that allows developers to build complex objects gradually. It provides a builder class with methods for setting different parts of the object, then returns the final object when construction is complete.

In simple terms, instead of creating an object in one large step, the Builder Pattern creates it through several smaller steps.

For example, creating an email message may require a recipient, subject, body, CC list, attachments, priority, and template options. Passing all of these values into one constructor can make the code difficult to read. A builder allows the object to be created in a more expressive way.

Why the Builder Pattern Is Important

The Builder Pattern is important because constructors can become difficult to manage when objects have many parameters. This problem is often called the telescoping constructor problem.

A constructor with many parameters is hard to read because it is not always clear what each value means. It is also easy to pass values in the wrong order, especially when several parameters have the same data type.

The Builder Pattern improves readability by using named methods for each construction step. This makes object creation clearer and safer.

The Problem of Large Constructors

Imagine a class that represents a user profile. It may include name, email, phone, address, avatar, biography, social links, notification settings, privacy settings, and language preferences.

Without Builder Pattern, the constructor may look like this:

$profile = new UserProfile(
    'Adnan Mehrat',
    'adnan@example.com',
    '+905000000000',
    'Samsun',
    'Turkey',
    'Developer',
    true,
    false,
    'en'
);

This code is difficult to understand because the meaning of each argument is not obvious. If more options are added later, the constructor becomes even harder to maintain.

Builder Pattern solves this by making the construction process more readable.

Builder Pattern Basic Example in PHP

The following example shows a simple Builder Pattern implementation for creating a user profile:

class UserProfile
{
    public function __construct(
        public string $name,
        public string $email,
        public ?string $phone = null,
        public ?string $city = null,
        public ?string $country = null,
        public ?string $bio = null,
        public bool $isPublic = true
    ) {
    }
}

class UserProfileBuilder
{
    private string $name;
    private string $email;
    private ?string $phone = null;
    private ?string $city = null;
    private ?string $country = null;
    private ?string $bio = null;
    private bool $isPublic = true;

    public function setName(string $name): self
    {
        $this->name = $name;

        return $this;
    }

    public function setEmail(string $email): self
    {
        $this->email = $email;

        return $this;
    }

    public function setPhone(string $phone): self
    {
        $this->phone = $phone;

        return $this;
    }

    public function setLocation(string $city, string $country): self
    {
        $this->city = $city;
        $this->country = $country;

        return $this;
    }

    public function setBio(string $bio): self
    {
        $this->bio = $bio;

        return $this;
    }

    public function makePrivate(): self
    {
        $this->isPublic = false;

        return $this;
    }

    public function build(): UserProfile
    {
        return new UserProfile(
            $this->name,
            $this->email,
            $this->phone,
            $this->city,
            $this->country,
            $this->bio,
            $this->isPublic
        );
    }
}

In this example, the builder stores construction values and returns the final UserProfile object through the build method.

Using the Builder Pattern

After creating the builder, object construction becomes more readable:

$profile = (new UserProfileBuilder())
    ->setName('Adnan Mehrat')
    ->setEmail('adnan@example.com')
    ->setPhone('+905000000000')
    ->setLocation('Samsun', 'Turkey')
    ->setBio('Backend-focused full stack developer')
    ->makePrivate()
    ->build();

This code is easier to understand because each method name explains what value is being set. The construction steps are clear and organized.

This style is often called a fluent interface because each method returns the builder itself and allows method chaining.

Main Parts of the Builder Pattern

The Builder Pattern usually includes the following parts:

  • Product: The final object being created.

  • Builder: The class responsible for setting the parts of the object step by step.

  • Build method: The method that returns the final object.

  • Director: An optional class that controls a predefined construction process.

Not every implementation needs a Director. In many PHP applications, a simple builder class is enough.

Builder Pattern with a Director

A Director is a class that defines common construction sequences. It uses the builder to create objects in a standard way.

For example, a report system may have different predefined report types. A director can create a monthly report or yearly report using the same builder.

class Report
{
    public function __construct(
        public string $title,
        public string $period,
        public array $sections,
        public bool $includeCharts
    ) {
    }
}

class ReportBuilder
{
    private string $title = '';
    private string $period = '';
    private array $sections = [];
    private bool $includeCharts = false;

    public function setTitle(string $title): self
    {
        $this->title = $title;

        return $this;
    }

    public function setPeriod(string $period): self
    {
        $this->period = $period;

        return $this;
    }

    public function addSection(string $section): self
    {
        $this->sections[] = $section;

        return $this;
    }

    public function includeCharts(): self
    {
        $this->includeCharts = true;

        return $this;
    }

    public function build(): Report
    {
        return new Report(
            $this->title,
            $this->period,
            $this->sections,
            $this->includeCharts
        );
    }
}

class ReportDirector
{
    public function createMonthlyReport(ReportBuilder $builder): Report
    {
        return $builder
            ->setTitle('Monthly Sales Report')
            ->setPeriod('Monthly')
            ->addSection('Summary')
            ->addSection('Sales')
            ->addSection('Expenses')
            ->includeCharts()
            ->build();
    }
}

In this example, the ReportDirector defines a standard way to create a monthly report. This can be useful when the same construction process is repeated in multiple places.

When the Director Is Useful

The Director is useful when the application has predefined object construction workflows. Instead of repeating the same builder steps in many places, the Director centralizes the process.

For example, a document system may have standard templates for invoices, contracts, reports, and certificates. A director can create each document type using the same builder but with different predefined steps.

However, if object creation is simple or each object is built differently, using a Director may be unnecessary.

Real-World Example: Email Message Builder

An email message is a practical example of the Builder Pattern. Emails can have many optional parts such as CC, BCC, attachments, priority, template, and metadata.

class EmailMessage
{
    public function __construct(
        public string $to,
        public string $subject,
        public string $body,
        public array $cc = [],
        public array $bcc = [],
        public array $attachments = [],
        public string $priority = 'normal'
    ) {
    }
}

class EmailMessageBuilder
{
    private string $to;
    private string $subject;
    private string $body;
    private array $cc = [];
    private array $bcc = [];
    private array $attachments = [];
    private string $priority = 'normal';

    public function to(string $email): self
    {
        $this->to = $email;

        return $this;
    }

    public function subject(string $subject): self
    {
        $this->subject = $subject;

        return $this;
    }

    public function body(string $body): self
    {
        $this->body = $body;

        return $this;
    }

    public function cc(string $email): self
    {
        $this->cc[] = $email;

        return $this;
    }

    public function bcc(string $email): self
    {
        $this->bcc[] = $email;

        return $this;
    }

    public function attach(string $filePath): self
    {
        $this->attachments[] = $filePath;

        return $this;
    }

    public function highPriority(): self
    {
        $this->priority = 'high';

        return $this;
    }

    public function build(): EmailMessage
    {
        return new EmailMessage(
            $this->to,
            $this->subject,
            $this->body,
            $this->cc,
            $this->bcc,
            $this->attachments,
            $this->priority
        );
    }
}

This builder makes email creation clear and flexible.

Using the Email Builder

$email = (new EmailMessageBuilder())
    ->to('user@example.com')
    ->subject('Welcome to our platform')
    ->body('Thank you for joining us.')
    ->cc('manager@example.com')
    ->attach('/files/welcome.pdf')
    ->highPriority()
    ->build();

This approach is more readable than passing many arrays and optional parameters into one constructor.

Builder Pattern and Immutable Objects

The Builder Pattern works well with immutable objects. An immutable object is an object whose state cannot be changed after it is created.

In this design, the builder is mutable during construction, but the final product is immutable. This means the object can be safely used after creation without unexpected changes.

For example, configuration objects, value objects, request objects, and DTOs can benefit from this approach. The builder collects values, validates them, and then creates a stable final object.

Validation Inside the Builder

A builder can also validate required values before creating the final object. This prevents incomplete or invalid objects from being created.

public function build(): EmailMessage
{
    if (empty($this->to)) {
        throw new InvalidArgumentException('Recipient email is required.');
    }

    if (empty($this->subject)) {
        throw new InvalidArgumentException('Email subject is required.');
    }

    if (empty($this->body)) {
        throw new InvalidArgumentException('Email body is required.');
    }

    return new EmailMessage(
        $this->to,
        $this->subject,
        $this->body,
        $this->cc,
        $this->bcc,
        $this->attachments,
        $this->priority
    );
}

This ensures that the object is complete and valid before it is used by the application.

Builder Pattern vs Factory Pattern

The Builder Pattern and Factory Pattern are both creational patterns, but they solve different problems.

The Factory Pattern focuses on choosing which object to create. For example, a PaymentFactory may decide whether to create CreditCardPayment or PayPalPayment.

The Builder Pattern focuses on how to construct a complex object step by step. For example, an EmailMessageBuilder may set recipient, subject, body, CC, BCC, attachments, and priority before creating the final EmailMessage.

In short, Factory answers “which object should be created?” while Builder answers “how should this complex object be built?”

Builder Pattern vs Abstract Factory Pattern

The Abstract Factory Pattern creates families of related objects. The Builder Pattern creates one complex object through multiple steps.

For example, Abstract Factory may create a group of UI components such as Button, Input, and Modal for a dark theme. Builder may create one complex Page object that contains title, layout, sections, metadata, and settings.

Both patterns support better object creation, but their purpose is different.

Builder Pattern in Laravel

Laravel developers use builder-style APIs frequently. For example, Laravel's query builder allows developers to build database queries step by step using method chaining.

$users = DB::table('users')
    ->where('active', true)
    ->orderBy('created_at', 'desc')
    ->limit(10)
    ->get();

This style is similar to the Builder Pattern because the query is constructed gradually before execution.

Developers can also create custom builders for reports, filters, search queries, export configuration, API requests, or complex DTO objects in Laravel applications.

Builder Pattern in Symfony

Symfony applications can also use builder classes for complex object creation. For example, builders can help create form configurations, request payloads, message objects, or report exports.

When combined with dependency injection, builder classes can remain clean and reusable. They can also be tested separately from controllers and services.

Real Use Cases of Builder Pattern

The Builder Pattern is useful in many real software development scenarios.

Common use cases include:

  • Building email messages with optional CC, BCC, and attachments.

  • Creating reports with sections, charts, filters, and export options.

  • Constructing HTTP requests with headers, query parameters, and body data.

  • Creating complex DTOs or value objects.

  • Building search filters or query objects.

  • Creating invoice objects with items, taxes, discounts, and metadata.

  • Building configuration objects with many optional settings.

In all of these cases, Builder Pattern improves readability and keeps object creation organized.

Benefits of Builder Pattern

The Builder Pattern provides many benefits when an object has many optional or complex construction steps.

Main benefits include:

  • Improves readability of object creation code.

  • Avoids long constructors with many parameters.

  • Supports step-by-step object construction.

  • Makes optional values easier to manage.

  • Can validate required values before creating the object.

  • Works well with immutable objects.

  • Supports fluent interfaces and method chaining.

  • Separates construction logic from the final object.

These benefits make Builder Pattern very practical for complex object creation.

Drawbacks of Builder Pattern

Although Builder Pattern is useful, it also has drawbacks. It can add extra classes and more code. For simple objects, this extra structure may be unnecessary.

If an object has only two or three required parameters, a normal constructor may be clearer than a builder. Adding a builder for every class can make the project more complicated.

Another drawback is that a poorly designed builder can allow invalid intermediate states. This is why validation in the build method is important when required values exist.

When to Use Builder Pattern

Use the Builder Pattern when object creation is complex, optional, or difficult to read with a normal constructor.

Builder Pattern is useful when:

  • The object has many constructor parameters.

  • Many parameters are optional.

  • Several values have the same data type and can be confused.

  • The object must be constructed step by step.

  • The final object should be immutable.

  • The construction process needs validation.

  • The code should be more readable with method chaining.

If these conditions exist, Builder Pattern can improve code quality significantly.

When Not to Use Builder Pattern

Do not use Builder Pattern when object creation is simple. If a class has only a few required values and no complex setup, a normal constructor is usually better.

Avoid Builder Pattern when:

  • The object has very few parameters.

  • The constructor is already clear and readable.

  • The builder would only duplicate constructor logic.

  • The project does not need step-by-step construction.

  • The pattern adds more complexity than value.

Design patterns should solve real problems. They should not be added only to make the code look more advanced.

Builder Pattern and Clean Code

The Builder Pattern supports clean code by making object creation more expressive. Instead of passing unclear values into a long constructor, each builder method explains the purpose of the value.

This reduces confusion and makes the code easier for other developers to read. It also keeps construction logic in one place, which improves maintainability.

However, clean code also means avoiding unnecessary complexity. Builders should be used only when they make construction clearer.

Common Mistakes with Builder Pattern

One common mistake is using Builder Pattern for every object, even when constructors are simple. This creates too many unnecessary classes.

Another mistake is not validating required values before building the object. This can allow incomplete objects to be created.

A third mistake is placing business workflows inside the builder. A builder should focus on object construction, not on executing business operations.

A fourth mistake is making builder methods unclear or inconsistent. Method names should describe the construction step clearly.

Best Practices for Builder Pattern

To use the Builder Pattern effectively, developers should keep the builder focused on construction and make the API readable.

Useful best practices include:

  • Use builders for complex objects, not simple objects.

  • Use clear method names that describe each construction step.

  • Return self from builder methods to support method chaining.

  • Validate required values inside the build method.

  • Keep business logic outside the builder.

  • Use builders with immutable final objects when appropriate.

  • Avoid creating unnecessary builder classes.

  • Keep the final product object focused and clean.

These practices help developers apply the Builder Pattern in a practical and maintainable way.

Practical Checklist Before Using Builder Pattern

Before using the Builder Pattern, developers can ask these questions:

  • Does the object have many parameters?

  • Are many parameters optional?

  • Is the constructor difficult to read?

  • Can values be confused because they have similar types?

  • Does the object need step-by-step construction?

  • Should the final object be immutable?

  • Will a builder make the code clearer?

If the answer is yes to several of these questions, Builder Pattern may be a good choice.

Conclusion

The Builder Pattern is a creational design pattern used to construct complex objects step by step. It is especially useful when an object has many optional parameters, nested values, configuration options, or validation rules.

Builder Pattern improves readability by replacing long constructors with clear method calls. It separates construction logic from the final object and works well with immutable objects, DTOs, reports, email messages, HTTP requests, and configuration objects.

However, Builder Pattern should be used only when it solves a real complexity problem. Simple objects do not need builders. When used correctly, the Builder Pattern is a powerful tool for writing clean, readable, and maintainable object-oriented software.