Бесконечный цикл с конструкторами из-за отношения m:n

У меня есть база данных с авторами и книгами, m:n авторов (a_id, ...) авторы_ книг (a_id, b_id) книги (b_id, ...)

Моя проблема в том, что я не могу использовать конструкторы для извлечения данных автора / книги в массив, потому что я получил бы бесконечный цикл.

class Book
{

    public $name;
    public $authors;

    public function __construct($name)
    {
        $this->name=$name;
        $this->authors=$this->Get_Authors();
    }

    public function Get_Authors()
    {
        $authors=array();
        /* ... (database) */
        $authors[]=new Author($name_from_db);
        return $authors;
    }

}

class Author
{

    public $name;
    public $books;

    public function __construct($name)
    {
        $this->name=$name;
        $this->books=$this->Get_Books();
    }

    public function Get_Books()
    {
        $books=array();
        /* ... (database) */
        $books[]=new Book($name_from_db);
        return $books;
    }

}

Пример:

new Book('book_1');

-> собирается получить 'author_1' и использует __constructor класса Author

new Author('author_1');

-> собирается извлечь book_1 и использует __constructor класса Book...

Какова "лучшая практика" для разрешения отношений a m:n в классах PHP?

2 ответа

Решение

Вы можете использовать ленивую загрузку здесь:

class Book {

    public $name;
    private $_authors = null;

    public function __constructor($name) {
        $this->name = $name;
    }

    public function getAuthors() {
        if ($this->_authors === null) {
            $this->_authors = array();
            /* database */
            $this->_authors[] = new Author(/**/);
        }
        return $this->_authors;
    }

    // You can add some magic getter if you want to access authors as property
    public function __get($key) {
        if ($key === 'authors') {
            return $this->getAuthors();
        }
        throw new Exception('Unknown property '.$key);
    }
}

class Authors {

    public $name;
    private $_books = null;

    public function __constructor($name) {
        $this->name = $name;
    }

    public function getBooks() {
        if ($this->_books === null) {
            $this->_books = array();
            /* database */
            $this->_books[] = new Book(/**/);
        }
        return $this->_books;
    }

    // You can add some magic getter if you want to access books as property
    public function __get($key) {
        if ($key === 'books') {
            return $this->getBooks();
        }
        throw new Exception('Unknown property '.$key);
    }
}

Это приведет к тому, что авторы / книги будут загружены только в том случае, если они вам понадобятся и не будут циклически повторяться, но вы можете столкнуться с другой проблемой здесь:

$author = new Author("Jon Doe");
$book = $author->books[0];
// assuming that book has one author
// $book->authors[0] will not be same object as $author

Для этого можно использовать третий объект для загрузки книг и авторов, который будет хранить уже загруженные объекты и вставлять их в нужные места.

class Library {

    private $_books = array();
    private $_authors = array();

    public function getBooksForAuthor($authorId) {
        /* db query... */
        $books = array();
        while ($row = $stmt->fetch()) {
            if (isset($this->_books[$row['id']]) {
                $books[] = $this->_books[$row['id']];
            } else {
                $book = new Book($row);
                $this->_books[$row['id']] = $book;
                $books[] = $book;
            }
        }
        return $books;
    }

    /* and similar authorsForBook() method */
}

class Author {

    private $_data;
    private $_library;
    private $_books = null;

    public function __constructor($data, $library) {
        $this->_data = $data;
        $this->_library = $library;
    }

    public function getBooks() {
        if ($this->_books === null) {
            $this->_books = $this->_library->getBooksForAuthor($this->_data['id']);
        }
        return $this->_books;
    }

    public function __get($key) {
        if ($key === 'books') {
            return $this->getBooks();
        }
        if (isset($this->_data[$key]) {
            return $this->_data[$key];
        }
        throw new Exception('Unknown property '.$key);
    }
}

/* and similar for book */

Почему вы хотите загрузить все связанные объекты? Используйте "отложенную загрузку"- не загружайте все связанные объекты, пока они вам не понадобятся. В вашем случае это будет означать получение связанного объекта через метод getter. Если вам нужно получить их через свойство, вы можете реализовать геттеры с помощью магических методов.

Другие вопросы по тегам