Essential Traits

January 2016 · 8 minute read

The following will describe some essential traits for Symfony2 and how to include them into the page bundle I’ve recently built.

All traits are going to be added in the GlobalBundle, under Entity so they can be accessed by other bundles that will be included into the app later.

The first thing to note in that there is one file which does not represent a trait, but the entity itself

Entity.php

namespace SelenaSmall\Bundle\GlobalBundle\Component\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * Base entity
 *
 * @ORM\MappedSuperClass
 */
abstract class Entity {
    # region Properties

    /**
     * @ORM\Column(type="integer", options={"unsigned"=true})
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    # endregion Properties

    # region Public

    # region Getters
    /**
     * Get id
     *
     * @return integer
     */
    public function getId() {
        return $this->id;
    }

    # endregion Getter

    # endregion Public
}

This file contains an abstract class for Entity which sets the id of each trait added to the class and returns the id specific to each entity at the time it is called.

Let’s look first, at the ActiveTrait.php and what it does:

ActiveTrait.php

namespace SelenaSmall\Bundle\GlobalBundle\Component\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Validate;

trait ActiveTrait {
	# region Properties

	/**
	 * @ORM\Column("is_active", type="boolean")
	 */
	protected $isActive = true;

	# endregion Properties

	# region Public

	# region Setters

	/**
	 * Set isActive
	 *
	 * @param boolean $isActive
	 * @return $this
	 */
	public function setIsActive($isActive) {
		$this->isActive = $isActive;
		return $this;
	}
	# endregion Setters
	# region Getters
	/**
	 * Get isActive
	 *
	 * @return boolean
	 */
	public function isActive() {
		return $this->isActive;
	}

	# endregion Getters

	# endregion Public
}

The Active trait is either true or false and therefore, parses a boolean to declare this. The first function in this file will set the boolean to either active or not active and the second function will get and return the response.

Once the entity has been declared, it can be used in the PageBundle

use ActiveTrait;

Next, we’ll look at the ContentTrait.php

ContentTrait.php

namespace SelenaSmall\Bundle\GlobalBundle\Component\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Validate;

trait ContentTrait {
    # region Properties

    /**
     * @ORM\Column(type="text", nullable=true)
     */
    protected $content;

    # endregion Properties

    # region Public

    # region Setters

    /**
     * Set content
     *
     * @params string $content
     * @return $this
     */
    public function setContent($content) {
        $this->content = $content;
        return $this;
    }

    # endregion Setters

    # region Getters

    /**
     * Get content
     *
     * @return string
     */
    public function getContent() {
        return $content->content;
    }

    # endregion Getters

    # endregion Public
}

First, this trait declares that the content will be in the form of text that it is possible to have no content. A function that sets the content to a string before a second function gets and returns that content.

DescriptionTrait.php

namespace SelenaSmall\Bundle\GlobalBundle\Component\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Validate;

trait DescriptionTrait {
    # region Properties

    /**
     * @ORM\Column(type="string", length=160, nullable=true)
     * @Validate\Length(max=160)
     */
    protected $description;

    # endregion Properties

    # region Public

    # region Setters

    /**
     * Set description
     *
     * @params string $description
     * @return $this
     */
    public function setDescription($description) {
        $this->description = $description;
        return $this;
    }

    # endregion Setters

    # region Getters

    /**
     * Get description
     *
     * @return string
     */
    public function getDescription() {
        return $this->description;
    }

    # endregion Getters

    # endregion Public
}

This trait simply contains a function that sets the description to a string before a second function gets and returns that content.

ExpireAtTrait.php

namespace SelenaSmall\Bundle\GlobalBundle\Component\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Validate;

trait ExpireAtTrait {
    # region Properties

    /**
     * @ORM\Column(name="expire_at", type="datetime", nullable=true)
     * @var \DateTime
     */
    protected $expireAt;

    # endregion Properties

    # region Public

    # region Setters

    /**
     * Set expireAt
     *
     * @params \DateTime $expireAt
     * @return $this
     */
    public function setExpireAt($expireAt) {
        $value = null;
        if ($expireAt instanceof \DateTime) {
            $value = $expireAt;
        }
        $this->expireAt = $value;
        return $this;
    }

    # endregion Setters

    # region Getters

    /**
     * Get expireAt
     *
     * @return \DateTime
     */
    public function getExpireAt() {
        return $this->expireAt;
    }
    # endregion Getters
    public function hasExpired() {
        if (!$this->expireAt) {
            return false;
        }
        return (time() >= $this->expireAt->getTimestamp());
    }

    # endregion Public
}

The ExpireAt trait again can be null but if it’s true it will be in the form of a DateTime. The first function to set the ExpireAt date declares that ExpireAt is an instance of \DateTime from the Symfony library. This trait not only has a getter to return the expiry date but also an extra public function which states that if this has expired, return the time that it did expire.

LoggedTrait.php

namespace SelenaSmall\Bundle\GlobalBundle\Component\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Validate;

trait LoggedTrait {
	# region Properties

	/**
	 * @ORM\Column(type="created_at", type="datetime", nullable=true)
	 * @var \DateTime
	 */

	protected $createdAt;

	/**
	 * @ORM\Column(name="modified_at", type="datetime", nullable=true)
	 * @var \DateTime
	 */

	protected $modifiedAt;

	/**
	 * Temporary flag to trigger modifiedAt updating
	 */
	protected $updateModifiedAt;

	# endregion Properties

	# region Public

	# region Setters

	/**
	 * Set createdAt
	 *
	 * @param \DateTime $createdAt
	 * @return $this
	 */
	public function setCreatedAt(\DateTime $createdAt) {
		$this->createdAt = $createdAt;
		return $this;
	}

	/**
	 * Set modifiedAt
	 *
	 * @param \DateTime $modifiedAt
	 * @return $this
	 */
	public function setModifiedAt(\DateTime $modifiedAt) {
		if ($this->updateModifiedAt !== false) {
			$this->modifiedAt = $modifiedAt;
		}
		return $this;
	}

	# endregion Setters

	# region Getters

	/**
	 * Get createdAt
	 *
	 * @return \DateTime
	 */
	public function getCreatedAt() {
		return $this->createdAt;
	}

	/**
	 * Get modifiedAt
	 *
	 * @return \DateTime
	 */
	public function getmodifiedAt() {
		return $this->modifiedAt;
	}

	# endregion Getters

	# region Helpers

	/**
	 * Disable automatic modifiedAt
	 */
	public function disableModifiedAt() {
		$this->updateModifiedAt = false;
	}

	# endregion Helpers

	# endregion Public
}

The LoggedTrait takes some additional properties in order to give the correct information, createdAt, modifiedAt, and updateModifiedAt are properties required to keep the logs in order. Both createdAt and modifiedAt need to be set to the parameter of \DateTime in their own respective functions. This trait also has an additional helper function which can allow the disabling of the modifiedAt function.

NameTrait.php

namespace SelenaSmall\Bundle\GlobalBundle\Component\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Validate;

trait NameTrait {
    # region Properties

    /**
     * @ORM\Column(type="string", length=69)
     * @Validate\NotBlank()
     * @Validate\Length(max=69)
     */
    protected $name;

    # endregion Properties

    # region Public

    # region Setters

    /**
     * Set name
     *
     * @params string $name
     * @return $this
     */
    public function setName($name) {
        $this->name = $name;
        return $this;
    }

    # endregion Setters

    # region Getters

    /**
     * Get name
     *
     * @return string
     */
    public function getName() {
        return $this->name;
    }

    # endregion Getters

    # endregion Public
}

The NameTrait simply parses a string of length=69  and validates that that string is not blank and is of the correct length.

PublishedAt.php

namespace SelenaSmall\Bundle\GlobalBundle\Component\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Validate;

trait PublishAtTrait {
    # region Properties

    /**
     * @ORM\Column(name="publish_at", type="datetime", nullable=true)
     * @var \DateTime
     */
    protected $publishAt;

    # endregion Properties

    # region Public

    # region Setters

    /**
     * Set publishAt
     *
     * @param \DateTime $publishAt
     * @return $this
     */
    public function setPublishAt($publishAt) {
        $value = null;
        if ($publishAt instanceof \DateTime) {
            $value = $publishAt;
        }
        $this->publishAt = $value;
        return $this;
    }

    # endregion Setters

    # region Getters

    /**
     * Get publishAt
     *
     * @return \DateTime
     */
    public function getPublishAt() {
        return $this->publishAt;
    }

    # endregion Getters

    # endregion Public
}

The PublishedAt trait sets the date and time this was published, which can be null in the case where this hasn’t yet been published.

SlugTrait.php

namespace SelenaSmall\Bundle\GlobalBundle\Component\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Validate;

trait SlugTrait {
    # region Properties

    /**
     * @ORM\Column(type="string", length=127)
     * @Validate\NotBlank()
     * @Validate\Regex("/^[a-z0-9\-_]+$/")
     * @Validate\Length(max=127)
     */
    protected $slug;

    # endregion Properties

    # region Public

    # region Setters

    /**
     *  Set slug
     *
     * @param string $slug
     * @return $this
     */
    public function setSlug($slug) {
        $this->slug = $slug;
        return $this;
    }

    # endregion Setters

    # region Getters

    /**
     * Get slug
     *
     * @return string
     */
    public function getSlug() {
        return $this->slug;
    }

    # endregion Getters

    # endregion Public
}

The SlugTrait defines what is allowed in the slugs which form url additions. In this case, it must be a regex string of max-length 127 and it cannot be blank. That is to say, the only symbols allowed in the slug are /1+$ or /

SortableTrait.php

namespace SelenaSmall\Bundle\GlobalBundle\Component\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Validate;

trait SortableTrait {
    # region Properties

    /**
     * @ORM\Column(name="display_order", type="integer", nullable=true, options={"unsigned"=true})
     * @Validate\GreaterThanOrEqual(value=0)
     */
    protected $displayOrder;

    # endregion Properties

    # region Public

    # region Setters

    /**
     * Set displayOrder
     *
     * @params string $displayOrder
     * @return $this
     */
    public function setDisplayOrder($displayOrder) {
        $this->displayOrder = $displayOrder;
        return $this;
    }

    # endregion Setters

    # region Getters

    /**
     * Get displayOrder
     *
     * @return string
     */
    public function getDisplayOrder() {
        return $displayOrder->displayOrder;
    }

    # endregion Getters

    # endregion Public
}

The SortableTrait has the property of an integer which is unsigned (cannot be negative), but can be null. The validator states that it must be greater than or equal to the value of zero.

UnlistedTrait.php

namespace SelenaSmall\Bundle\GlobalBundle\Component\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Validate;

trait UnlistedTrait {
	# region Properties

	/**
	 * @ORM\Column("is_unlisted", type="boolean")
	 */
	protected $isUnlisted = false;

	# endregion Properties

	# region Public

	# region Setters

	/**
	 * Set isUnlisted
	 *
	 * @param string $isUnlisted
	 * @return $this
	 */
	public function setIsUnlisted($isUnlisted) {
		$this->isUnlisted = $isUnlisted;
		return $this;
	}

	# endregion Setters

	# region Getters

	/**
	 * Get isUnlisted
	 *
	 * @return boolean
	 */
	public function getIsUnlisted() {
		return $this->isUnlisted;
	}

	# endregion Getters

	# endregion Public
}

The unlisted trait is either true or false.

Since all essential traits have been declared, they should have also been used in the PageBundle/Entity/page.php file as shown above

namespace SelenaSmall\Bundle\PageBundle\Entity;

use SelenaSmall\Bundle\GlobalBundle\Component\Entity\Entity;
use SelenaSmall\Bundle\GlobalBundle\Component\Entity\NameTrait;
use SelenaSmall\Bundle\GlobalBundle\Component\Entity\SlugTrait;
use SelenaSmall\Bundle\GlobalBundle\Component\Entity\DescriptionTrait;
use SelenaSmall\Bundle\GlobalBundle\Component\Entity\ContentTrait;
use SelenaSmall\Bundle\GlobalBundle\Component\Entity\SortableTrait;
use SelenaSmall\Bundle\GlobalBundle\Component\Entity\PublishAtTrait;
use SelenaSmall\Bundle\GlobalBundle\Component\Entity\ExpireAtTrait;
use SelenaSmall\Bundle\GlobalBundle\Component\Entity\LoggedTrait;
use SelenaSmall\Bundle\GlobalBundle\Component\Entity\ActiveTrait;
use SelenaSmall\Bundle\GlobalBundle\Component\Entity\UnlistedTrait;

use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Form\Extension\Validator\Constraints as Validate;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;

/**
 * Page Entity
 *
 * @ORM\Entity(repositoryClass="SelenaSmall\Bundle\GlobalBundle\Entity\PageRepository")
 * @ORM\Table(name="page")
 * @UniqueEntity(fields={"parent", "slug"})
 */
class Page extends Entity {
    # region Traits

    use NameTrait;
    use SlugTrait;
    use DescriptionTrait;
    use ContentTrait;
    use SortableTrait;
    use PublishAtTrait;
    use ExpireAtTrait;
    use LoggedTrait;
    use ActiveTrait;
    use UnlistedTrait;

  1. a-z0-9-_ [return]