4

I'm pretty new to Doctrine, so any general advice is welcome. I'm trying to achieve the following:

A page can have both videos and photos. Both are media and share properties, so I thought Single Table Inheritance makes sense. Here are the relevant parts of my setup:

Page

/**
 * @ORM\Entity
 */
class Page {

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

    /**
     * @ORM\OneToMany(targetEntity="Media", mappedBy="page", cascade={"persist"})
     * @var ArrayCollection|Media[]
     */
    protected $media;

    /**
     * @return Media[]|ArrayCollection
     */
    public function getMedia()
    {
        return $this->media;
    }
}

Media

/**
 * @ORM\Entity
 * @ORM\Table(name="media")
 * @ORM\InheritanceType("SINGLE_TABLE")
 * @ORM\DiscriminatorColumn(name="media_type", type="string")
 * @ORM\DiscriminatorMap({"video" = "Video", "photo" = "Photo"})
 */
abstract class Media {

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

    /**
     * @ORM\Column(type="string")
     */
    protected $url;

    /**
     * @ORM\ManyToOne(targetEntity="Page", inversedBy="media")
     */
    protected $page;
}

Photo

/**
 * @ORM\Entity
 */
class Photo extends Media {

    /**
     * @ORM\Column(type="string")
     */
    protected $exif;
}

Video

/**
 * @ORM\Entity
 */
class Video extends Media {

    /**
     * @ORM\Column(type="integer")
     */
    protected $length;
}

This works perfectly fine, but -and this is my question- how do I fetch all Photos or Videos of a page. I've tried adding things like

/**
 * @ORM\OneToMany(targetEntity="Photo", mappedBy="page", cascade={"persist"})
 * @var ArrayCollection|Photo[]
 */
protected $photos;

to Page, but this results in a schema error, since it doesn't have an inverse defined on Photo. Any help is greatly appreciated!

DerLola
  • 3,858
  • 1
  • 19
  • 25

3 Answers3

3

You can apply filter to the media collection

class Page {
    .....
    public function getPhotos()
    {
        return $this->getMedia()->filter(
            function($media) {
                return $media instanceof Photo;
            }
        );
    }

    public function getVideos()
    {
        return $this->getMedia()->filter(
            function($media) {
                return $media instanceof Video;
            }
        );
    }
}

Bear in mind, it will fetch all medias in a single select query, instantiate both Photos and Videos, and then filter the result. It may affect performance, but you may benefit from doctrine cache, depending on your scenario.

If performance is a key, you may need to implement a custom methods in PageRepository to actually fetch only subset of medias using instance of dql as described in Query the Type.

Alex Blex
  • 34,704
  • 7
  • 48
  • 75
  • This is the answer I was looking for. Thank you for naming the pros and cons + giving an alternative solution using a Repository. – DerLola Jan 04 '16 at 13:49
1

I think you can solve this by using a join on the specific subclass:

$queryBuilder->select('m')
   ->from('Media', 'm')
   ->join('Photo', 'p', 'WITH', 'm.id = p.id')
   ->where('m.page = :page_id')
   ->setParameter('page_id', $pageId);
Wilt
  • 41,477
  • 12
  • 152
  • 203
1

You won't be able to have a single $media property on your Page class which maps to a superclass. You will have to have a property per class. See the note in the Doctrine docs:

A mapped superclass cannot be an entity, it is not query-able and persistent relationships defined by a mapped superclass must be unidirectional (with an owning side only). This means that One-To-Many associations are not possible on a mapped superclass at all. Furthermore Many-To-Many associations are only possible if the mapped superclass is only used in exactly one entity at the moment. For further support of inheritance, the single or joined table inheritance features have to be used.

Jonny
  • 2,223
  • 23
  • 30
  • Makes sense, except that in this case I'm not using a mapped superclass, but STI. The Media object is in fact an entity, so the quoted docs don't apply. Other than that, great answer. – DerLola Jan 04 '16 at 13:47