Dealing with “Inter-Dependencies”

I ran into a problem I provisionally call Inter-Dependency: Trying to initiate two classes while both have each other as a dependency. One of them gets initiated first but it doesn’t work because it is depending on the other. Here is an example – pay attention to the initiation of $bookService and $authorService:

require 'some_classes.php'; // see below

$db = new Database();
$bookService = new BookService($db, $authorService);
$authorService = new AuthorService($db, $bookService);

$book = $bookService->getBook(1);

print $book->title.' by '.$book->getAuthor()->name;

print 'Same author:';

foreach ($book->getAuthor()->getBooks() as $otherBook) {
	print '• '.$otherBook->title;
}
// some_classes.php

class Database {
	private $tables = [
		'books' => [
			[
				'id' => 1,
				'title' => 'Book 1',
				'author_id' => 1
			],
			[
				'id' => 2,
				'title' => 'Book 2',
				'author_id' => 1
			],
			[
				'id' => 3,
				'title' => 'Book 3',
				'author_id' => 2
			]
		],
		'authors' => [
			[
				'id' => 1,
				'name' => 'Author 1'
			],
			[
				'id' => 2,
				'name' => 'Author 2'
			]
		]
	];
	
	public function fetch($table, $col, $needle) {
		$rows = [];
		
		foreach ($this->tables[$table] as $row) {
			foreach ($row as $key => $val) {
				if ($key == $col && $val == $needle) {
					$results[] = $row;
				}
			}
		}
		
		return $rows;
	}
}

class BookService {
	private $db;
	private $authorService;
	
	public function __construct(Database $db, AuthorService $authorService) {
		$this->db = $db;
		
		$this->authorService = $authorService;
	}
	
	public function getBook($id) {
		$rows = $this->db->fetch('books', 'id', $id);
		
		return buildBook($rows[0]);
	}
	
	public function getBooksOfAuthor($id) {
		$books = [];
		
		foreach ($rows as $row) {
			$rows = $this->db->search('books', 'author_id', $id);
			$books[] = buildBook($row);
		}
		
		return $books;
	}
	
	private function buildBook($values) {
		return new Book($values, $this->authorService);
	}
}

class AuthorService {
	private $db;
	private $bookService;
	
	public function __construct(Database $db, BookService $bookService) {
		$this->db = $db;
		
		$this->bookService = $bookService;
	}
	
	public function getAuthor($id) {
		$rows = $this->db->fetch('authors', 'id', $id);
		
		return buildAuthor($rows[0]);
	}
	
	private function buildAuthor($values) {
		return new Author($values, $this->bookService);
	}
}

class Book {
	private $authorService;
	
	public $id;
	public $title;
	public $authorID;
	
	public function __construct($values, AuthorService $authorService) {
		foreach ($values as $key => $val) {
			$this->$key = $val;
		}
		
		$this->authorService = $authorService;
	}
	
	public function getAuthor() {
		return $this->authorService->getAuthor($this->authorID);
	}
}

class Author {
	private $bookService;
	
	public $id;
	public $name;
	
	public function __construct($values, BookService $bookService) {
		foreach ($values as $key => $val) {
			$this->$key = $val;
		}
		
		$this->bookService = $bookService;
	}
	
	public function getBooks() {
		return $this->bookService->getBooksOfAuthor($this->id);
	}
}

Could you tell me a proper workaround for this problem? I’m not searching for a library to help me, I’d rather like to learn the right pattern.

Thanks
—Fangio

1 Like

I think the author should be part of the Book data. When you populate the book objects from database (maybe in BookService?) include the author data in the Book objects.
This way


$book = $bookService->getBook(1);

$book object will hold the Author object with author properties in it.
This way you could get rid of AuthorService, unless it will a have different purpose.

And the fact you got private $authorService; inside Book and private $bookService; inside Author feels just wrong. Imo there should not be services in the Book and Author classes. They should serve one purpose, holding the data for Book or Author.

But that way I would lose the convenient way to lazy-load additional books written by the same author.

This variant works:

$db = new Database();
$bookService = new BookService($db);
$authorService = new AuthorService($db);

$bookService->provideAuthorService($authorService);
$authorService->provideBookService($bookService);

$book = $bookService->getBook(1);

print $book->title.' by '.$book->getAuthor()->name;

print 'Same author:';

foreach ($book->getAuthor()->getBooks() as $otherBook) {
	print '• '.$otherBook->title;
}

In combination with an DI-Container that seems quite practical to me, doesn’t it?

Regards
—Fangio

1 Like

This is very true. The way I described in the post earlier you would lose the lazy-load for additional books written by same author like you mentioned. Unless your Author class had the GetBooks() method which feels wrong also. Also advantage in your last post is that you don’t need to query the author data for the book unless you need it. I do like your latest solution.

@Fangio - Before you go too far down this path you should ensure that your solution will scale to meet your application requirements. The number of EntityServices can quickly become unmanageable. Suppose you need a LibraryService, BookstoreService, OwnerService,LendeeService,PublisherService,CharacterService etc.

It is the relations between entities that quickly become important and perhaps difficult to manage.

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.