I'm Marco!


Best Practices

What is this talk about?

Not about

We have enough talks like that

I don't want to be too negative

More overview than
in depth code

Where do we start?

A new project!

How exciting!

Yes, this happens every few years!

Sometimes it's even innovative stuff!

Let's write a new Controller!

(was that sarcasm?)

Do you KNOW
where to start?

Did you talk to the business folks?

Did you talk to your team?

Did you look at existing solutions?

We will assume that you did that.

You did, right?

Let's write a new Controller!

(was that sarcasm?)

Let's go API First!

Because everything needs an API, right?

Let's write a new Controller!

(was that sarcasm?)

Model your Domain First

Yes, that's the real API First

This means that you need to do actual brainwork!

It's also the most fun part!

Import your testing framework

We want to do TDD

(Tears driven development)

Play with
your Domain


That's what you want to expose via HTTP!

What interface will you need?



APIs can kill...

...your project.

Do you really need API first?

APIs are easier to build though

No frontend

No forms


Async is ok

Frontend is simple too!

If you don't mix it with an API

Or you make a SPA

Importing ZendFramework

Only after the domain stuff!

As usual, Skeleton Application

Please use composer, don't make lives difficult

Clean it up!

Remove init_autoloader.php

Remove the Application module routes

Remove the public/ styles

Keep it minimal first

You may keep some bits

Like the Application module error template

Start designing your Module

Use composer for autoloading

No weird paths like ModuleName/Module.php

PSR-0 or PSR-4 structure

Part of the application repository

Start integrating
the domain
with your Module

This is not thought-work

This is integration work

Each Domain Interaction
has a Controller

This is not thought-work

This is integration work

The Module class

Only few interfaces implemented:

  • ConfigProviderInterface
  • InitProviderInterface (optional)
  • BootstrapListenerInterface (optional)

Everything more is a smell


Write a test:
unserialize(serialize($module->getConfig())) == $module->getConfig()

References to class/interface name must use ::class

Service names should use ::class

No magic key constants, use Module class constants

What are Modules for?




What are Modules NOT for?



Don't split modules unless needed

A note on Dependencies

For every used
class / function / constant
you need the right composer.json entry!

Designing Controllers

  1. Convert request data to validated data
  2. Call domain layer
  3. Serialize response (view model)

Don't design a Controller without a Business Interaction first!

(This means that your domain must expose something about the interaction)

Controllers pass Valid Data to the Domain

You can use Commands

You can use Value Objects from the Domain

Should controllers follow REST principles?

Probably only for GET

Business interactions != CRUD

RPC is perfectly OK

The Encyclopedia Galactica, in its chapter on Forms states that it is far too complicated to define.
The Hitchhiker's Guide to the Galaxy has this to say on the subject of forms:
Avoid, if at all possible.

Form's can't really be avoided

Sometimes we can just use an InputFilter

Build the interactions first, the form fancyness later on.

Avoid form binding

$form->bindValues($presets) is perfectly OK

$form->getData() and MyCommand::fromFormData($data) is simpler!

MASSIVE simplification!


I don't really have anything to say here

It is specific to the domain

I suggest leveraging an existing repository implementation

On clean code

Refactor a bit every day

Keep it extremely clean/SOLID

No shortcuts

Keeping code clean is very simple

(once you know how to do it)

How to keep ZF code clean:

Factories all the way

Reduced Configs (prefer factories)

Small Methods


Clean coding is Mechanical

You can do it with eyes closed

And with one hand.

No real amount of thought-work

Just time consuming

You can think about other stuff meanwhile!

Clean code has loads of advantages

You can replace any code, any time

Less bugs, or easier to tackle

Manageable technical debt

Increased code understanding

Technical Debt Example

class MyService implements ServiceLocatorAwareInterface { ... }

Design in 1 hour

Code in 15 minutes

Test in 10 minutes

Addition of ~120 minutes of technical debt

class MyService { ... }

Design in 1 hour

Code in 15 minutes

Code factory in 5 minutes

Test in 10 minutes

Test factory in 10 minutes

Addition of ~30 minutes of technical debt


With ServiceLocatorAwareInterface: ~200 minutes of work

Without ServiceLocatorAwareInterface: ~130 minutes of work