PHP/MVC/PDO-数据库类外部的beginTransaction
有人能帮我吗?我有以下课程(所有课程都是功能性的,为了便于阅读,此处缩写): 还有一堂书课PHP/MVC/PDO-数据库类外部的beginTransaction,php,class,model-view-controller,methods,pdo,Php,Class,Model View Controller,Methods,Pdo,有人能帮我吗?我有以下课程(所有课程都是功能性的,为了便于阅读,此处缩写): 还有一堂书课 class Books extends Controller { public function __construct() { $this->model = $this->loadModel('BookModel'); } // etc. $this->model->beginTransaction(); 图书模型如下所示:
class Books extends Controller {
public function __construct() {
$this->model = $this->loadModel('BookModel');
}
// etc.
$this->model->beginTransaction();
图书模型如下所示:
class BookModel {
protected $db;
public function __construct() {
$this->db = new Database;
}
public function beginTransaction() {
$this->db->beginTransaction();
}
我知道我只能访问数据库类内部的PDO beginTransaction,但是有没有其他方法,或者我必须使用这个复杂的路径,调用调用PDO方法的方法
我有一种感觉,我在做一些非常愚蠢的事情。也许可以将BookModel扩展到Database类,但这感觉也不对
谢谢 一些建议: [a]您不应该在类方法内部创建对象(使用“new”)。相反,您应该将现有实例注入构造函数/设置器。这被命名为依赖项注入,可以与依赖项注入容器一起应用
数据库将从注入构造函数的单个PDO实例中获益匪浅。注入任务将是DI容器的工作。例如,如果要使用,将有一个用于创建数据库连接的
return [
'database-connection' => function (ContainerInterface $container) {
$parameters = $container->get('database.connection');
$dsn = $parameters['dsn'];
$username = $parameters['username'];
$password = $parameters['password'];
$connectionOptions = [
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_PERSISTENT => false,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
];
$connection = new PDO($dsn, $username, $password, $connectionOptions);
return $connection;
},
];
和另一个定义条目,将其注入数据库
:
return [
Database::class => autowire()
->constructorParameter('connection', get('database-connection')),
];
数据库
构造函数如下所示:
public function __construct(PDO $connection) {
$this->dbh = $connection;
}
[c]模型
不是一个类(如BookModel
)。它是一个层(模型层,或),由多个组件组成:实体(或域对象),域服务。您的BookModel
是实体和数据映射器(至少)的组合。注意:从数据库继承它是错误的,因为模型不能是数据库
[d]您不应该将模型注入控制器。相反,控制器应该使用所谓的(也称为用例,或动作,或交互者)。这些服务包含所谓的应用程序逻辑,是将表示层(或交付机制)与域模型解耦的正确方法,其中包括控制器和视图。应用服务还保证了两层之间的通信。注意:也可能有特定于域的域服务,与特定于应用程序的应用程序服务分开
[e]数据库
类根本不需要!您已经拥有了非常优雅和强大的PDO,可以处理数据库操作
[f]实际上,“调用调用PDO方法的方法”并没有错。此链中的每个方法都封装了特定于当前对象的特定行为。不过,每个方法的功能都应该增加一些附加值。否则,拥有这条链条就毫无意义了。例如:在应用程序服务中,您可以直接使用数据映射器从数据库中按id获取书籍:
class FindBooksService {
public function __construct(
private BookMapper $bookMapper
) {
}
public function findBookById(?int $id = null): ?Book {
return $this->bookMapper->fetchBookById($id);
}
}
class BookMapper {
public function __construct(
private PDO $connection
) {
}
public function fetchBookById(?int $id): ?Book {
$sql = 'SELECT * FROM books WHERE id = :id LIMIT 1';
// Fetch book data from database; convert the record to a Book object ($book).
//...
return $book;
}
}
现在,您可以使用存储库来隐藏查询数据来自数据库的事实。这是有意义的,因为存储库对象被其他组件视为特定类型(此处为Book
)的对象集合。因此,其他组件认为存储库是一组书籍,而不是某个数据库中的一组数据,它们相应地向存储库请求这些书籍。存储库将反过来对数据映射器进行互操作,以查询数据库。因此,前面的代码变成:
class FindBooksService {
/**
* @param BookCollection $bookCollection The repository: a collection of books, e.g. of Book instances.
*/
public function __construct(
private BookCollection $bookCollection
) {
}
public function findBookById(?int $id = null): ?Book {
return $this->bookCollection->findBookById($id);
}
}
class BookCollection {
private array $books = [];
public function __construct(
private BookMapper $bookMapper
) {
}
/**
* This method adds a plus value to the omolog method in the data mapper (fetchBookById):
* - caches the Book instances in the $books list, therefore reducing the database querying operations;
* - hides the fact, that the data comes from a database, from the external world, e.g. other components.
* - provides an elegant collection-like interface.
*/
public function findBookById(?int $id): ?Book {
if (!array_key_exists($id, $this->books)) {
$book = $this->bookMapper->fetchBookById($id);
$this->books[id] = $book;
}
return $this->books[$id];
}
}
class BookMapper {
// the same...
}
[g]一个“真正”的错误是将一个对象传递给其他对象,而只是供最后一个对象使用
备选示例代码:
我写了一些代码来代替你的。我希望它能帮助您更好地理解基于MVC的应用程序的组件是如何协同工作的
重要:注意名称空间SampleMvc/Domain/Model/
:这就是域模型。请注意,应用程序服务,例如来自SampleMvc/App/Service/
的所有组件,应仅与域模型组件通信,例如来自SampleMvc/domain/model/
(主要是接口)的组件,而不是来自SampleMvc/domain/Infrastructure/
。反过来,您选择的DI容器将负责为应用程序服务使用的SampleMvc/Domain/Infrastructure/
接口注入来自SampleMvc/Domain/Model/
的适当类实现
注意SampleMvc/Domain/Infrastructure/Book/PdoBookMapper.php
中的方法updateBook()。我在其中包含了一个事务代码,以及两个很棒的链接。玩得开心
项目结构:
SampleMvc/App/Controller/Book/AddBook.php:
一些建议:
[a]您不应该在类方法内部创建对象(使用“new”)。相反,您应该将现有实例注入构造函数/设置器。这被命名为依赖项注入,可以与依赖项注入容器一起应用
[b]正如@YourCommonSense所指出的,数据库将从注入构造函数的单个PDO实例中获益匪浅。注入任务将是DI容器的工作。例如,如果要使用,将有一个用于创建数据库连接的
return [
'database-connection' => function (ContainerInterface $container) {
$parameters = $container->get('database.connection');
$dsn = $parameters['dsn'];
$username = $parameters['username'];
$password = $parameters['password'];
$connectionOptions = [
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_PERSISTENT => false,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
];
$connection = new PDO($dsn, $username, $password, $connectionOptions);
return $connection;
},
];
还有一个<?php
namespace SampleMvc\App\Controller\Book;
use Psr\Http\Message\{
ResponseInterface,
ServerRequestInterface,
};
use SampleMvc\App\Service\Book\{
AddBook as AddBookService,
Exception\BookAlreadyExists,
};
use SampleMvc\App\View\Book\AddBook as AddBookView;
/**
* A controller for adding a book.
*
* Let's assume the existence of this route definition:
*
* $routeCollection->post('/books/add', SampleMvc\App\Controller\Book\AddBook::class);
*/
class AddBook {
/**
* @param AddBookView $view The view for presenting the response to the request back to the user.
* @param AddBookService $addBookService An application service for adding a book to the model layer.
*/
public function __construct(
private AddBookView $view,
private AddBookService $addBookService
) {
}
/**
* Add a book.
*
* The book details are submitted from a form, using the HTTP method "POST".
*
* @param ServerRequestInterface $request A server request.
* @return ResponseInterface The response to the current request.
*/
public function __invoke(ServerRequestInterface $request): ResponseInterface {
$authorName = $request->getParsedBody()['authorName'];
$title = $request->getParsedBody()['title'];
try {
$book = $this->addBookService($authorName, $title);
$this->view->setBook($book);
} catch (BookAlreadyExists $exception) {
$this->view->setErrorMessage(
$exception->getMessage()
);
}
$response = $this->view->addBook();
return $response;
}
}
<?php
namespace SampleMvc\App\Controller\Book;
use Psr\Http\Message\ResponseInterface;
use SampleMvc\App\View\Book\FindBooks as FindBooksView;
use SampleMvc\App\Service\Book\FindBooks as FindBooksService;
/**
* A controller for finding books.
*
* Let's assume the existence of this route definition:
*
* $routeCollection->post('/books/find/{authorName}', [SampleMvc\App\Controller\FindBooks::class, 'findBooksByAuthorName']);
*/
class FindBooks {
/**
* @param FindBooksView $view The view for presenting the response to the request back to the user.
* @param FindBooksService $findBooksService An application service for finding books by querying the model layer.
*/
public function __construct(
private FindBooksView $view,
private FindBooksService $findBooksService
) {
}
/**
* Find books by author name.
*
* The author name is provided by clicking on a link of some author name
* in the browser. The author name is therefore sent using the HTTP method
* "GET" and passed as argument to this method by a route dispatcher.
*
* @param string|null $authorName (optional) An author name.
* @return ResponseInterface The response to the current request.
*/
public function findBooksByAuthorName(?string $authorName = null): ResponseInterface {
$books = $this->findBooksService->findBooksByAuthorName($authorName);
$response = $this->view
->setBooks($books)
->findBooksByAuthorName()
;
return $response;
}
}
<?php
namespace SampleMvc\App\Service\Book\Exception;
/**
* An exception thrown if a book already exists.
*/
class BookAlreadyExists extends \OverflowException {
}
<?php
namespace SampleMvc\App\Service\Book;
use SampleMvc\Domain\Model\Book\{
Book,
BookMapper,
};
use SampleMvc\App\Service\Book\Exception\BookAlreadyExists;
/**
* An application service for adding a book.
*/
class AddBook {
/**
* @param BookMapper $bookMapper A data mapper for transfering books
* to and from a persistence system.
*/
public function __construct(
private BookMapper $bookMapper
) {
}
/**
* Add a book.
*
* @param string|null $authorName An author name.
* @param string|null $title A title.
* @return Book The added book.
*/
public function __invoke(?string $authorName, ?string $title): Book {
$book = $this->createBook($authorName, $title);
return $this->storeBook($book);
}
/**
* Create a book.
*
* @param string|null $authorName An author name.
* @param string|null $title A title.
* @return Book The newly created book.
*/
private function createBook(?string $authorName, ?string $title): Book {
return new Book($authorName, $title);
}
/**
* Store a book.
*
* @param Book $book A book.
* @return Book The stored book.
* @throws BookAlreadyExists The book already exists.
*/
private function storeBook(Book $book): Book {
if ($this->bookMapper->bookExists($book)) {
throw new BookAlreadyExists(
'A book with the author name "' . $book->getAuthorName() . '" '
. 'and the title "' . $book->getTitle() . '" already exists'
);
}
return $this->bookMapper->saveBook($book);
}
}
<?php
namespace SampleMvc\App\Service\Book;
use SampleMvc\Domain\Model\Book\{
Book,
BookMapper,
};
/**
* An application service for finding books.
*/
class FindBooks {
/**
* @param BookMapper $bookMapper A data mapper for transfering books
* to and from a persistence system.
*/
public function __construct(
private BookMapper $bookMapper
) {
}
/**
* Find a book by id.
*
* @param int|null $id (optional) A book id.
* @return Book|null The found book, or null if no book was found.
*/
public function findBookById(?int $id = null): ?Book {
return $this->bookMapper->fetchBookById($id);
}
/**
* Find books by author name.
*
* @param string|null $authorName (optional) An author name.
* @return Book[] The found books list.
*/
public function findBooksByAuthorName(?string $authorName = null): array {
return $this->bookMapper->fetchBooksByAuthorName($authorName);
}
}
<?php
namespace SampleMvc\App\View\Book;
use SampleMvc\{
App\View\View,
Domain\Model\Book\Book,
};
use Psr\Http\Message\ResponseInterface;
/**
* A view for adding a book.
*/
class AddBook extends View {
/** @var Book The added book. */
private Book $book = null;
/**
* Add a book.
*
* @return ResponseInterface The response to the current request.
*/
public function addBook(): ResponseInterface {
$bodyContent = $this->templateRenderer->render('@Templates/Book/AddBook.html.twig', [
'activeNavItem' => 'AddBook',
'book' => $this->book,
'error' => $this->errorMessage,
]);
$response = $this->responseFactory->createResponse();
$response->getBody()->write($bodyContent);
return $response;
}
/**
* Set the book.
*
* @param Book $book A book.
* @return static
*/
public function setBook(Book $book): static {
$this->book = $book;
return $this;
}
}
<?php
namespace SampleMvc\App\View\Book;
use SampleMvc\{
App\View\View,
Domain\Model\Book\Book,
};
use Psr\Http\Message\ResponseInterface;
/**
* A view for finding books.
*/
class FindBooks extends View {
/** @var Book[] The list of found books. */
private array $books = [];
/**
* Find books by author name.
*
* @return ResponseInterface The response to the current request.
*/
public function findBooksByAuthorName(): ResponseInterface {
$bodyContent = $this->templateRenderer->render('@Templates/Book/FindBooks.html.twig', [
'activeNavItem' => 'FindBooks',
'books' => $this->books,
]);
$response = $this->responseFactory->createResponse();
$response->getBody()->write($bodyContent);
return $response;
}
/**
* Set the books list.
*
* @param Book[] $books A list of books.
* @return static
*/
public function setBooks(array $books): static {
$this->books = $books;
return $this;
}
}
<?php
namespace SampleMvc\App\View;
use Psr\Http\Message\ResponseFactoryInterface;
use SampleLib\Template\Renderer\TemplateRendererInterface;
/**
* View.
*/
abstract class View {
/** @var string The error message */
protected string $errorMessage = '';
/**
* @param ResponseFactoryInterface $responseFactory Response factory.
* @param TemplateRendererInterface $templateRenderer Template renderer.
*/
public function __construct(
protected ResponseFactoryInterface $responseFactory,
protected TemplateRendererInterface $templateRenderer
) {
}
/**
* Set the error message.
*
* @param string $errorMessage An error message.
* @return static
*/
public function setErrorMessage(string $errorMessage): static {
$this->errorMessage = $errorMessage;
return $this;
}
}
<?php
namespace SampleMvc\Domain\Infrastructure\Book;
use SampleMvc\Domain\Model\Book\{
Book,
BookMapper,
};
use PDO;
/**
* A data mapper for transfering Book entities to and from a database.
*
* This class uses a PDO instance as database connection.
*/
class PdoBookMapper implements BookMapper {
/**
* @param PDO $connection Database connection.
*/
public function __construct(
private PDO $connection
) {
}
/**
* @inheritDoc
*/
public function bookExists(Book $book): bool {
$sql = 'SELECT COUNT(*) as cnt FROM books WHERE author_name = :author_name AND title = :title';
$statement = $this->connection->prepare($sql);
$statement->execute([
':author_name' => $book->getAuthorName(),
':title' => $book->getTitle(),
]);
$data = $statement->fetch(PDO::FETCH_ASSOC);
return ($data['cnt'] > 0) ? true : false;
}
/**
* @inheritDoc
*/
public function saveBook(Book $book): Book {
if (isset($book->getId())) {
return $this->updateBook($book);
}
return $this->insertBook($book);
}
/**
* @inheritDoc
*/
public function fetchBookById(?int $id): ?Book {
$sql = 'SELECT * FROM books WHERE id = :id LIMIT 1';
$statement = $this->connection->prepare($sql);
$statement->execute([
'id' => $id,
]);
$record = $statement->fetch(PDO::FETCH_ASSOC);
return ($record === false) ?
null :
$this->convertRecordToBook($record)
;
}
/**
* @inheritDoc
*/
public function fetchBooksByAuthorName(?string $authorName): array {
$sql = 'SELECT * FROM books WHERE author_name = :author_name';
$statement = $this->connection->prepare($sql);
$statement->execute([
'author_name' => $authorName,
]);
$recordset = $statement->fetchAll(PDO::FETCH_ASSOC);
return $this->convertRecordsetToBooksList($recordset);
}
/**
* Update a book.
*
* This method uses transactions as example.
*
* Note: I never worked with transactions, but I
* think the code in this method is not wrong.
*
* @link https://phpdelusions.net/pdo#transactions (The only proper) PDO tutorial: Transactions
* @link https://phpdelusions.net/pdo (The only proper) PDO tutorial
* @link https://phpdelusions.net/articles/error_reporting PHP error reporting
*
* @param Book $book A book.
* @return Book The updated book.
* @throws \Exception Transaction failed.
*/
private function updateBook(Book $book): Book {
$sql = 'UPDATE books SET author_name = :author_name, title = :title WHERE id = :id';
try {
$this->connection->beginTransaction();
$statement = $this->connection->prepare($sql);
$statement->execute([
':author_name' => $book->getAuthorName(),
':title' => $book->getTitle(),
':id' => $book->getId(),
]);
$this->connection->commit();
} catch (\Exception $exception) {
$this->connection->rollBack();
throw $exception;
}
return $book;
}
/**
* Insert a book.
*
* @param Book $book A book.
* @return Book The newly inserted book.
*/
private function insertBook(Book $book): Book {
$sql = 'INSERT INTO books (author_name, title) VALUES (:author_name, :title)';
$statement = $this->connection->prepare($sql);
$statement->execute([
':author_name' => $book->getAuthorName(),
':title' => $book->getTitle(),
]);
$book->setId(
$this->connection->lastInsertId()
);
return $book;
}
/**
* Convert the given record to a Book instance.
*
* @param array $record The record to be converted.
* @return Book A Book instance.
*/
private function convertRecordToBook(array $record): Book {
$id = $record['id'];
$authorName = $record['author_name'];
$title = $record['title'];
$book = new Book($authorName, $title);
$book->setId($id);
return $book;
}
/**
* Convert the given recordset to a list of Book instances.
*
* @param array $recordset The recordset to be converted.
* @return Book[] A list of Book instances.
*/
private function convertRecordsetToBooksList(array $recordset): array {
$books = [];
foreach ($recordset as $record) {
$books[] = $this->convertRecordToBook($record);
}
return $books;
}
}
<?php
namespace SampleMvc\Domain\Model\Book;
/**
* Book entity.
*/
class Book {
/**
* @param string|null $authorName (optional) The name of an author.
* @param string|null $title (optional) A title.
*/
public function __construct(
private ?string $authorName = null,
private ?string $title = null
) {
}
/**
* Get id.
*
* @return int|null
*/
public function getId(): ?int {
return $this->id;
}
/**
* Set id.
*
* @param int|null $id An id.
* @return static
*/
public function setId(?int $id): static {
$this->id = $id;
return $this;
}
/**
* Get the author name.
*
* @return string|null
*/
public function getAuthorName(): ?string {
return $this->authorName;
}
/**
* Set the author name.
*
* @param string|null $authorName The name of an author.
* @return static
*/
public function setAuthorName(?string $authorName): static {
$this->authorName = $authorName;
return $this;
}
/**
* Get the title.
*
* @return string|null
*/
public function getTitle(): ?string {
return $this->title;
}
/**
* Set the title.
*
* @param string|null $title A title.
* @return static
*/
public function setTitle(?string $title): static {
$this->title = $title;
return $this;
}
}
<?php
namespace SampleMvc\Domain\Model\Book;
use SampleMvc\Domain\Model\Book\Book;
/**
* An interface for various data mappers used to
* transfer Book entities to and from a persistence system.
*/
interface BookMapper {
/**
* Check if a book exists.
*
* @param Book $book A book.
* @return bool True if the book exists, false otherwise.
*/
public function bookExists(Book $book): bool;
/**
* Save a book.
*
* @param Book $book A book.
* @return Book The saved book.
*/
public function saveBook(Book $book): Book;
/**
* Fetch a book by id.
*
* @param int|null $id A book id.
* @return Book|null The found book, or null if no book was found.
*/
public function fetchBookById(?int $id): ?Book;
/**
* Fetch books by author name.
*
* @param string|null $authorName An author name.
* @return Book[] The found books list.
*/
public function fetchBooksByAuthorName(?string $authorName): array;
}