Hackernoon logoA Tip for Lazy Symfony Developers by@Bassaganas

A Tip for Lazy Symfony Developers

Author profile picture

@BassaganasJordi Bassagañas

Hi there! How are you today? I blog about technology, the Internet, SEO, programming tips, and more.

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 App\DataFixtures;

use App\Entity\Address;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\DataFixtures\DependentFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Faker\Factory;

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

    private $faker;

    public function __construct()
    {
        $this->faker = Factory::create();
        $this->faker->addProvider(new \Faker\Provider\en_US\Address($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 App\Entity\Behaviour;

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 App\Entity;

use App\Entity\Behaviour\VerbosityTrait;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repositoryClass="App\Repository\AddressRepository")
 */
class Address
{
    use VerbosityTrait;

    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

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

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

    /**
     * @ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="addresses")
     */
    private $user;

    /**
     * @ORM\Column(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 App\DataFixtures;

use App\Entity\Address;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\DataFixtures\DependentFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use Faker\Factory;

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

    private $faker;

    public function __construct()
    {
        $this->faker = Factory::create();
        $this->faker->addProvider(new \Faker\Provider\en_US\Address($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.
Author profile picture

@BassaganasJordi Bassagañas

Read my stories

Hi there! How are you today? I blog about technology, the Internet, SEO, programming tips, and more.

Tags

The Noonification banner

Subscribe to get your daily round-up of top tech stories!