2
votes

I'm developing a page where users can see an overview of graphical images that represent Internet traffic coming in and going out of a network device's ports. Information about these ports are collected in a table. This table has at least the following columns per port:

  • Port ID
  • Device ID
  • PortName
  • and more...

The Device ID column is the primary key of another table: Devices. This table has at least the following columns:

  • Device ID
  • DeviceName
  • and more...

As you can see, there is a relation between these two tables by Device ID. I have translated both tables to Domain Objects 'Port' and 'Device'. Both Domain Objects get populated by two Domain Mappers 'PortMapper' and 'DeviceMapper'. One attribute I need from every port is the DeviceName which I can get through Device ID. My question is about how I can best map these two Domain objects together.

My PortMapper's findById() function looks like this:

public function findById($id, DeviceMapper $deviceMapper) {
    $ports = $this->db->sql_select('SELECT * FROM ports WHERE customer_id = ?', [$id]);

    $arrayOfPorts = [];

    foreach($ports as $port) {   
        $device = $deviceMapper->findById($port['device_id']);
        $port['device'] = $device;
        $arrayOfPorts[] = $this->createObject($port);
    }

    return $arrayOfPorts;
}

And my DeviceMapper's findById() function:

public function findById($id) {
    $device = $this->db->sql_select('SELECT * FROM devices WHERE device_id = ?', [$id]);
    return $this->createObject($device);        
}

The way I instantiate this is in my Controller:

$db = new Database('librenms');
$portMapper = new PortMapper($db);
$deviceMapper = new DeviceMapper($db);
$ports = $portMapper->findById($_SESSION['user']['id'], $deviceMapper);

foreach($ports as $port) {
    echo $port->getHostname();
}

I get the Device's hostname per Port just fine. But the way I instantiate the whole thing is really bad. What would be a better approach to the relation between two Domain Objects?

I can do a simple LEFT JOIN query so that I don't need to map two Domain Objects together, but that's not where I want to go to.

1
Why don't you use an ORM framework like Doctrine ORM? - Genti Saliu
By writing code of my own I learn more than just use a big ORM framework like Doctrine. - Beeelze
Good enough. Then follow the patterns Doctrine or other frameworks use and implement the functionality yourself. - Genti Saliu
I'll take a look at Doctrine ORM. - Beeelze

1 Answers

1
votes

I think you'll need to restructure a few things. First of all, I recommend that your data mapper populate your domain objects. After all, that's what their supposed to do: map data onto objects. This method allows you to set known attributes on a domain object, then call the data mapper to "fetch" the rest of the data, given the known variables.

The next thing I suggest is creating domain object collection classes. These entity collections are essentially glorified arrays and can act as such if implemented correctly. These changes will help in producing the relationships you want to map better (see code below). Additionally, with this advice, your mappers will assume a more singular responsibility, leaving other relational logic out of the mapper/in a helper class if you so choose.

Below I illustrate how I would go about structuring this task. First, a PortCollection is created and a condition on the ID is set. We can then map the PortCollection on a PortCollectionMapper. After making necessary database calls, the PortCollectionMapper sends the results to the PortCollection which instantiates new Port instances for every row fetched and adds it to the PortCollection.

Now, we can loop through the PortCollection and use a DeviceMapper to populate information about the device.

<?php

$portCollection = new PortCollection;
$portCollection->setId( $_SESSION['user']['id'] );

$portCollectionMapper = new PortCollectionMapper;
$portCollectionMapper->fetch( $portCollection );
//$portCollection is now populated with Port instances

$deviceMapper = new DeviceMapper;

foreach( $portCollection as $port )
{
    //create a device from the port device ID
    $device = new Device;
    $device->setId( $port->getDeviceId() );

    //populate the device with data
    $deviceMapper->fetch( $device );

    //assign the device to the port
    $port->setDevice( $device );
}