A Tip for Lazy Symfony Developers

Search for brainy quotes about laziness and you’ll find a bunch of them saying something not too nice, but did you know that being a lazy programmer is actually a good thing?

At the end of the day, possibly a reason why you’d want to take a lazy approach when it comes to solving problems is because you want to save brain energy.

Now let me remind you that PHP is known to be a loosely typed programming language, which is not bad or good but rather a particular feature where variable types are just handled in a flexible way.

PHP variables can be used as needed without declaring them first, and type hinting in functions is optional.

Having said that, on the other hand Doctrine, a popular ORM for PHP and Symfony, encourages you to use so-called entities as it is shown below.
// src/DataFixtures/AddressFixtures.php

namespace AppDataFixtures;

use AppEntityAddress;
use DoctrineBundleFixturesBundleFixture;
use DoctrineCommonDataFixturesDependentFixtureInterface;
use DoctrineCommonPersistenceObjectManager;
use FakerFactory;

class AddressFixtures extends Fixture implements DependentFixtureInterface
{
    const N = 50;

    private $faker;

    public function __construct()
    {
        $this->faker = Factory::create();
        $this->faker->addProvider(new FakerProvideren_USAddress($this->faker));
    }

    public function load(ObjectManager $manager)
    {
        for ($i = 0; $i < self::N; $i++) {
            $address = (new Address())
                        ->setAddress($this->faker->address)
                        ->setPostcode($this->faker->postcode)
                        ->setCity($this->faker->city)
                        ->setUser($this->getReference('user-'.rand(0,UserFixtures::N-1)));
            $manager->persist($address);
        }

        $manager->flush();
    }

    public function getDependencies()
    {
        return [
            UserFixtures::class,
        ];
    }
}
As you can see, this example is about loading

50

address fixtures, every single setter method in the

Adress

entity must be called in order to create an

$address

object.

$address = (new Address())
            ->setAddress($this->faker->address)
            ->setPostcode($this->faker->postcode)
            ->setCity($this->faker->city)
            ->setUser($this->getReference('user-'.rand(0,UserFixtures::N-1)));
Some may argue this is unnecessarily verbose, in which case I’d personally come up with the following custom entity behaviour named

VerbosityTrait

taking advantage of PHP’s flexible nature.

// src/Entity/Behaviour/VerbosityTrait.php

namespace AppEntityBehaviour;

trait VerbosityTrait
{
    /**
     * Sets the entity's properties.
     *
     * @param array $props
     * @return $this
     */
    public function setProps(array $props)
    {
        foreach($props as $key => $val) {
            $method = 'set'.$this->camelize($key);
            if (method_exists($this, $method)) {
                $this->{$method}($this->filter($key, $val));
            }
        }

        return $this;
    }

    /**
     * Camelizes a string.
     *
     * @param string $string
     * @param string $separator
     * @return $this
     */
    private function camelize(string $string, string $separator = '_')
    {
        return str_replace($separator, '', ucwords($string, $separator));
    }

    /**
     * Applies a filter.
     *
     * @param string $key
     * @param mixed $val
     */
    private function filter(string $key, $val)
    {
        switch (true) {
            case empty($val):
                return null;
            case substr($key, 0, 3) === 'is_':
                return filter_var($val, FILTER_VALIDATE_BOOLEAN);
            case is_string($val) && preg_match("/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$/", $val):
                return new DateTime($val);
            default:
                return $val;
        }
    }
}
// src/Entity/Address.php

namespace AppEntity;

use AppEntityBehaviourVerbosityTrait;
use DoctrineORMMapping as ORM;

/**
 * @ORMEntity(repositoryClass="AppRepositoryAddressRepository")
 */
class Address
{
    use VerbosityTrait;

    /**
     * @ORMId()
     * @ORMGeneratedValue()
     * @ORMColumn(type="integer")
     */
    private $id;

    /**
     * @ORMColumn(type="string", length=255)
     */
    private $address;

    /**
     * @ORMColumn(type="string", length=15)
     */
    private $postcode;

    /**
     * @ORMManyToOne(targetEntity="AppEntityUser", inversedBy="addresses")
     */
    private $user;

    /**
     * @ORMColumn(type="string", length=255)
     */
    private $city;

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getAddress(): ?string
    {
        return $this->address;
    }

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

        return $this;
    }

    public function getPostcode(): ?string
    {
        return $this->postcode;
    }

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

        return $this;
    }

    public function getCity(): ?string
    {
        return $this->city;
    }

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

        return $this;
    }

    public function getUser(): ?User
    {
        return $this->user;
    }

    public function setUser(?User $user): self
    {
        $this->user = $user;

        return $this;
    }
}

This way the loading of fixtures turns into a different, concise flavour or version.

// src/DataFixtures/AddressFixtures.php

namespace AppDataFixtures;

use AppEntityAddress;
use DoctrineBundleFixturesBundleFixture;
use DoctrineCommonDataFixturesDependentFixtureInterface;
use DoctrineCommonPersistenceObjectManager;
use FakerFactory;

class AddressFixtures extends Fixture implements DependentFixtureInterface
{
    const N = 50;

    private $faker;

    public function __construct()
    {
        $this->faker = Factory::create();
        $this->faker->addProvider(new FakerProvideren_USAddress($this->faker));
    }

    public function load(ObjectManager $manager)
    {
        for ($i = 0; $i < self::N; $i++) {
            $address = (new Address())->setProps([
                'address' => $this->faker->address,
                'postcode' => $this->faker->postcode,
                'city' => $this->faker->city,
                'user' => $this->getReference('user-'.rand(0,UserFixtures::N-1)),
            ]);
            $manager->persist($address);
        }

        $manager->flush();
    }

    public function getDependencies()
    {
        return [
            UserFixtures::class,
        ];
    }
}
With the

Address

entity using our custom

VerbosityTrait

behaviour, the

$address

object is created with significant less verbosity.

$address = (new Address())->setProps([
    'address' => $this->faker->address,
    'postcode' => $this->faker->postcode,
    'city' => $this->faker->city,
    'user' => $this->getReference('user-'.rand(0,UserFixtures::N-1)),
]);
Entity properties are set through the

setProps()

method which accepts an associative array of properties, each of which can have an unknown type at execution time.

For further details on this implementation please visit programarivm/zebra which is a GitHub repo where I am playing around with a database design methodology that consists in seeding a development database with sample fake data while designing it at the same time.

read original article here