How can I make a singleton of the PDO extention? Extending doesn't work, because I get a fatal error when I try it ...
You don't need a Singleton.
But to answer this nevertheless:
You cannot turn a public visibility to a stricter visibility. So PDO cannot have the constructor's visibility changed to anything but public. So you need to wrap the PDO instance into a Singleton:
class MyPdo
{
/**
* #var MyPdo
*/
protected static $_instance;
/**
* #var Pdo
*/
protected $_pdo;
/**
* Creates instance and returns it on subsequent calls
*
* #throws InvalidArgumentException
* #param array $options PDO connection data
* #returns MyPdo
*/
public static function getInstance(array $options = NULL)
{
if(self::$_instance === NULL) {
if($options === NULL) {
throw new InvalidArgumentException(
'You must supply connection options on first run');
}
// call constructor with given options and assign instance
self::$_instance = new self(
$options['dsn'],
$options['user'],
$options['password'],
$options['driver_options']
);
}
return self::$_instance;
}
/**
* Creates new MyPdo wrapping a PDO instance
*
* #throws PDOException
* #param String $dsn
* #param String $user
* #param String $password
* #param Array $driver_options
* #return void
*/
private function __construct($dsn, $user, $password, $driver_options)
{
try {
$this->_pdo = new PDO($dsn, $user, $password, $driver_options);
} catch (PDOException $e) {
echo 'Connection failed: ' . $e->getMessage();
}
}
/**
* Singletons may not be cloned
*/
private function __clone() {}
/**
* Delegate every get to PDO instance
*
* #param String $name
* #return Mixed
*/
public function __get($name)
{
return $this->_pdo->$name;
}
/**
* Delegate every set to PDO instance
*
* #param String $name
* #param Mixed $val
* #return Mixed
*/
public function __set($name, $val)
{
return $this->_pdo->$name = $val;
}
/**
* Delegate every method call to PDO instance
*
* #param String $method
* #param Array $args
* #return Mixed
*/
public function __call($method, $args) {
return call_user_func_array(array($this->_pdo, $method), $args);
}
}
You'd use it like this:
$db = MyPdo::getInstance(array(
'dsn'=>'mysql:dbname=mysql;host=127.0.0.1',
'user' => 'root',
'password' => 'minnymickydaisydonaldplutogoofysanfrancisco',
'driver_options' => array(
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8"
)));
$version = $db->query( 'SELECT version();' );
echo $version->fetchColumn();
// remove reference to instance
unset($db);
// doesn't require connection data now as it returns same instance again
$db = MyPdo::getInstance();
$version = $db->query( 'SELECT version();' );
echo $version->fetch();
Related
I created this code where I create an instance of the database and work with it. Now I'm trying to convert the code to a static form, but I can't.
$pdo = new PDO('sqlite:src/chinook.db');
$sql = "CREATE TABLE IF NOT EXISTS uzivatele(
uzivatelId INTEGER PRIMARY KEY,
jmeno TEXT,
prijmeni TEXT,
body INTEGER
);";
$statement = $pdo->prepare($sql);
$statement->execute();
function dropTable($pdo,$name)
{
$sql = "DROP TABLE $name";
$statement = $pdo->prepare($sql);
$statement->execute();
}
...
static
This is how I have a class implemented for pdo (according to the manual) and I would like to implement static methods, such as createTable, but I can't redo it
class Db
{
protected static $pdo = null;
public static function get(): \PDO
{
return self::$pdo ?? (self::$pdo = new \PDO(
'sqlite:hw-06.db',
null,
null,
[
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION
]
));
}
}
use App\Db;
class Account
{
...
public static function createTable(): void
{
$db = Db::get();
$sql = "CREATE TABLE IF NOT EXISTS uzivatele(
uzivatelId INTEGER PRIMARY KEY,
jmeno TEXT,
prijmeni TEXT,
body INTEGER
);";
$statement = $db->prepare($sql);
$statement->execute();
}
index.php
Account::createTable();
If u want to implement a simple singleton, u can use the "getInstance()" concept and combine with "__callStatic" and "call_user_func_array" to make a PDO functions to be static too, all PDO and Database class functions will become static:
<?php
declare(strict_types = 1);
/*
* PDO database class - only one connection alowed
*/
final class Database
{
/**
* #var PDO $connection The connection
*/
private $connection;
/**
* #var Database $instance The single instance
*/
private static $instance;
/**
* #var string $engine The engine of connection
*/
private $engine = 'sqlite:persistence.db'; // sqlite::memory:
/**
* #var array $options Default option to PDO connection
*/
private $options = [
PDO::ATTR_PERSISTENT => true,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_OBJ,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false
];
/**
* Private constructor to prevent instance
*
* #throws \Throwable
* #return void
*/
private function __construct()
{
try {
$this->connection = new PDO($this->engine, null, null, $this->options);
}
catch (\Throwable $error) {
error_log("{$error->getMessage()}");
}
}
/**
* Get an instance of the Database
*
* #return PDO
*/
private static function getInstance(): PDO
{
// If no instance then make one
if (!self::$instance) {
self::$instance = new self;
}
return self::$instance->connection;
}
/**
* Transpiler of static methods for PDOStatements
*
* #var string $method The PDO static method
* #var array $args
* #return string|PDOStatement
*/
public static function __callStatic(string $method, array $args)
{
return call_user_func_array(array(self::getInstance(), $method), $args);
}
/**
* Destroying PDO connection
*
* #return void
*/
public function __destruct()
{
if (!empty($this->connection)) {
unset($this->connection);
}
}
/**
* Magic method clone is empty to prevent duplication of connection
*/
public function __clone() { }
public function __wakeup() { }
public function __toString() { }
}
to use there:
<?php
require_once __DIR__ . '/Database.php';
Database::exec('CREATE TABLE IF NOT EXISTS uzivatele (
uzivatelId INTEGER PRIMARY KEY,
jmeno TEXT,
prijmeni TEXT,
body INTEGER
);');
Database::exec("INSERT INTO uzivatele (jmeno, prijmeni, body) VALUES ('test', 'test', 1);");
var_dump(Database::lastInsertId());
$stmt = Database::prepare("SELECT * FROM uzivatele;");
$stmt->execute();
$data = $stmt->fetchAll();
var_dump($data);
note that "prepared statments objects" are still like objects!
i dont see any problem in using database connections as static, if they are not used in parallel, there is no problem, it even reduces the overhead of creating many connections with the database. but be careful, in some cases it may not be beneficial, as in cases where the code is not being executed by a CGI or FastCGI but by a wrapper, it can cause slowdowns and even give a problem!
could someone help me on this? I have following classes (all functional, abbreviated here for sake of legibility):
class Database {
private $host = DB_HOST;
// etc...
public function __construct() {
$dsn = 'mysql:host=' . $this->host . ';dbname=' . $this->dbname;
$options = array(PDO::ATTR_PERSISTENT => true, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION);
try {
$this->dbh = new PDO($dsn, $this->user, $this->pass, $options);
} catch (PDOException $e) {
$this->error = $e->getMessage();
echo $this->error;
}
}
public function beginTransaction() {
$this->stmt = $this->dbh->beginTransaction();
}
and a class for let’s say books;
class Books extends Controller {
public function __construct() {
$this->model = $this->loadModel('BookModel');
}
// etc.
$this->model->beginTransaction();
and the BookModel looks like:
class BookModel {
protected $db;
public function __construct() {
$this->db = new Database;
}
public function beginTransaction() {
$this->db->beginTransaction();
}
I know I can only access the PDO beginTransaction inside of the Database class, but is there another way, or I have to use this complicated path, call the method that calls the method that calls the PDO method?
I have a feeling I’m doing something very stupid here. Maybe extending the BookModel to the Database class, but that doesn’t feel right either.
Thanks!
Some suggestions:
[a] You shouldn't create objects (with "new") inside class methods. Instead you should inject existent instances into constructors/setters. This is named dependency injection and can be applied with a dependency injection container.
Dependency Injection and Dependency Inversion in PHP - James Mallison - PHPTour 2017 Nantes
PHP-DI The dependency injection container for humans
[b] As #YourCommonSense noted, Database would greatly benefit from a single PDO instance, injected in the constructor. The injection task would be the job of the DI container. For example, if you'd use PHP-DI, there would be a definition entry for creating a database connection:
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;
},
];
and another definition entry to inject it in Database:
return [
Database::class => autowire()
->constructorParameter('connection', get('database-connection')),
];
The Database contructor would look like:
public function __construct(PDO $connection) {
$this->dbh = $connection;
}
[c] The model is not a class (like BookModel). It is a layer (model layer, or domain model), composed of multiple components: entities (or domain objects), value objects, data mappers, repositories, domain services. Your BookModel is a combination btw. an entity and a data mapper (at least). Note: inheriting it from Database is wrong, because a model can't be a database.
How should a model be structured in MVC?
The difference between domains, domain models, object models and domain objects
[d] You shouldn't inject models into controllers. Instead, controllers should use so-called application services (also named use cases, or actions, or interactors). These services contain the so-called application logic and are the proper way to decouple the presentation layer (or delivery mechanism) - which, among other components, includes the controllers and the views - from the domain model. The application services also assure the communication btw. the two layers. Note: there could also be domain services, specific to the domain and separated from the application services, which are specific to the application.
Sandro Mancuso : Crafted Design
Ruby Midwest 2011 - Keynote: Architecture the Lost Years by Robert Martin
Robert "Uncle Bob" Martin - Architecture: The Lost Years
How should a model be structured in MVC?
[e] Database class is not needed at all! You already have the very elegant & powerful PDO at disposal, to handle the database operations.
[f] Actually, it is not wrong to "call the method that calls the method that calls the PDO method". Each method in this chain encapsulates a certain behavior, specific to the current object. Though, the functionality of each method should add some plus value. Otherwise, it wouldn't make sense to have this chain, indeed. An example: In an application service, you can directly use a data mapper to fetch a book by id from the database:
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;
}
}
Now, you could use a repository instead, to hide even the fact that the queried data comes from a database. This makes sense, since a repository object is seen as a collection of objects of a certain type (here Book) by other components. Therefore, the other components think that the repository is a collection of books, not a bunch of data in some database, and they ask the repository for them correspondingly. The repository will, in turn, interogate the data mapper to query the database. So, the previous code becomes:
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] A "real" mistake would be to pass an object through other objects, just to be used by the last object.
Alternative example code:
I wrote some code as an alternative to yours. I hope it will help you better understand, how the components of an MVC-based application could work together.
Important: Notice the namespace SampleMvc/Domain/Model/: that's the domain model. Note that the application services, e.g. all components from SampleMvc/App/Service/, should communicate ONLY with the domain model components, e.g. with the components from SampleMvc/Domain/Model/ (mostly interfaces), not from SampleMvc/Domain/Infrastructure/. In turn, the DI container of your choice will take care of injecting the proper class implementations from SampleMvc/Domain/Infrastructure/ for the interfaces of SampleMvc/Domain/Model/ used by the application services.
Notice the method updateBook() in SampleMvc/Domain/Infrastructure/Book/PdoBookMapper.php. I included a transaction code in it, along with two great links. Have fun.
Project structure:
SampleMvc/App/Controller/Book/AddBook.php:
<?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;
}
}
SampleMvc/App/Controller/Book/FindBooks.php:
<?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;
}
}
SampleMvc/App/Service/Book/Exception/BookAlreadyExists.php:
<?php
namespace SampleMvc\App\Service\Book\Exception;
/**
* An exception thrown if a book already exists.
*/
class BookAlreadyExists extends \OverflowException {
}
SampleMvc/App/Service/Book/AddBook.php:
<?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);
}
}
SampleMvc/App/Service/Book/FindBooks.php:
<?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);
}
}
SampleMvc/App/View/Book/AddBook.php:
<?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;
}
}
SampleMvc/App/View/Book/FindBooks.php:
<?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;
}
}
SampleMvc/App/View/View.php:
<?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;
}
}
SampleMvc/Domain/Infrastructure/Book/PdoBookMapper.php:
<?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;
}
}
SampleMvc/Domain/Model/Book/Book.php:
<?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;
}
}
SampleMvc/Domain/Model/Book/BookMapper.php:
<?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;
}
I'm trying to "use" a vendor script to connect to feefo api (an online reviews service) but when I try and use the script it gives me this error:
Type error: Argument 1 passed to
BlueBayTravel\Feefo\Feefo::__construct() must be an instance of
GuzzleHttp\Client, null given, called in/Users/webuser1/Projects/_websites/domain.co.uk/plugins/gavinfoster/feefo/components/Feedback.php on line 47
Here is the vendor code I'm using:
/*
* This file is part of Feefo.
*
* (c) Blue Bay Travel <developers#bluebaytravel.co.uk>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace BlueBayTravel\Feefo;
use ArrayAccess;
use Countable;
use Exception;
use GuzzleHttp\Client;
use Illuminate\Contracts\Config\Repository;
use Illuminate\Contracts\Support\Arrayable;
use SimpleXMLElement;
/**
* This is the feefo class.
*
* #author James Brooks <james#bluebaytravel.co.uk>
*/
class Feefo implements Arrayable, ArrayAccess, Countable
{
/**
* The guzzle client.
*
* #var \GuzzleHttp\Client
*/
protected $client;
/**
* The config repository.
*
* #var \Illuminate\Contracts\Config\Repository
*/
protected $config;
/**
* The review items.
*
* #var array
*/
protected $data;
/**
* Create a new feefo instance.
*
* #param \GuzzleHttp\Client $client
* #param \Illuminate\Contracts\Config\Repository $config
*
* #return void
*/
public function __construct(Client $client, Repository $config)
{
$this->client = $client;
$this->config = $config;
}
/**
* Fetch feedback.
*
* #param array|null $params
*
* #return \BlueBayTravel\Feefo\Feefo
*/
public function fetch($params = null)
{
if ($params === null) {
$params['json'] = true;
$params['mode'] = 'both';
}
$params['logon'] = $this->config->get('feefo.logon');
$params['password'] = $this->config->get('feefo.password');
try {
$body = $this->client->get($this->getRequestUrl($params));
return $this->parse((string) $body->getBody());
} catch (Exception $e) {
throw $e; // Re-throw the exception
}
}
/**
* Parses the response.
*
* #param string $data
*
* #return \Illuminate\Support\Collection
*/
protected function parse($data)
{
$xml = new SimpleXMLElement($data);
foreach ((array) $xml as $items) {
if (isset($items->TOTALRESPONSES)) {
continue;
}
foreach ($items as $item) {
$this->data[] = new FeefoItem((array) $item);
}
}
return $this;
}
/**
* Assigns a value to the specified offset.
*
* #param mixed $offset
* #param mixed $value
*
* #return void
*/
public function offsetSet($offset, $value)
{
if (is_null($offset)) {
$this->data[] = $value;
} else {
$this->data[$offset] = $value;
}
}
/**
* Whether or not an offset exists.
*
* #param mixed $offset
*
* #return bool
*/
public function offsetExists($offset)
{
return isset($this->data[$offset]);
}
/**
* Unsets an offset.
*
* #param mixed $offset
*
* #return void
*/
public function offsetUnset($offset)
{
if ($this->offsetExists($offset)) {
unset($this->data[$offset]);
}
}
/**
* Returns the value at specified offset.
*
* #param mixed $offset
*
* #return mixed
*/
public function offsetGet($offset)
{
return $this->offsetExists($offset) ? $this->data[$offset] : null;
}
/**
* Count the number of items in the dataset.
*
* #return int
*/
public function count()
{
return count($this->data);
}
/**
* Get the instance as an array.
*
* #return array
*/
public function toArray()
{
return $this->data;
}
/**
* Returns the Feefo API endpoint.
*
* #param array $params
*
* #return string
*/
protected function getRequestUrl(array $params)
{
$query = http_build_query($params);
return sprintf('%s?%s', $this->config->get('feefo.baseuri'), $query);
}
}
And here is the code I'm using to try and use the fetch() method from the vendor class:
use Cms\Classes\ComponentBase;
use ArrayAccess;
use Countable;
use Exception;
use GuzzleHttp\Client;
use Illuminate\Contracts\Config\Repository;
use Illuminate\Contracts\Support\Arrayable;
use SimpleXMLElement;
use BlueBayTravel\Feefo\Feefo;
class Feedback extends ComponentBase
{
public $client;
public $config;
/**
* Container used for display
* #var BlueBayTravel\Feefo
*/
public $feedback;
public function componentDetails()
{
return [
'name' => 'Feedback Component',
'description' => 'Adds Feefo feedback to the website'
];
}
public function defineProperties()
{
return [];
}
public function onRun()
{
$this->feedback = $this->page['feedback'] = $this->loadFeedback($this->client, $this->config);
}
public function loadFeedback($client, $config)
{
$feefo = new Feefo($client, $config);
$feedback = $feefo->fetch();
return $feedback;
}
}
Won't allow me to call the Fetch() method statically so trying to instantiate and then use:
public function loadFeedback($client, $config)
{
$feefo = new Feefo($client, $config);
$feedback = $feefo->fetch();
return $feedback;
}
I've also tried type hinting the args like this:
public function loadFeedback(Client $client, Repository $config)
{
$feefo = new Feefo($client, $config);
$feedback = $feefo->fetch();
return $feedback;
}
But still I get the exception error above. I'm struggling to understand how to get past this. Any help for a newbie much appreciated :)
Just type hinting the function won't cast it to that object type. You need to properly pass the Guzzle\Client object to your function call.
// Make sure you 'use' the GuzzleClient on top of the class
// or use the Fully Qualified Class Name of the Client
$client = new Client();
$feedback = new Feedback();
// Now we passed the Client object to the function of the feedback class
// which will lead to the constructor of the Feefo class which is
// where your error is coming from.
$loadedFeedback = $feedback->loadFeedback($client);
Don't forget to do the same for the Repository $config from Laravel/Lumen
I am a beginner in Zend and php. Please excuse me if I am absurd and silly.
I am doing my first application in Zend,called thenexsocial, with the help of the tutorials available online. But I get this Fatal error every time I refresh the browser. I referred all the posts regarding this in Stack exchange, but couldn't solve the problem.
The error occurs in the "Abstract.php of Pdo folder in the Zend library".
<?php
/**
* Zend Framework
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://framework.zend.com/license/new-bsd
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license#zend.com so we can send you a copy immediately.
*
* #category Zend
* #package Zend_Db
* #subpackage Adapter
* #copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* #license http://framework.zend.com/license/new-bsd New BSD License
* #version $Id$
*/
/**
* #see Zend_Db_Adapter_Abstract
*/
require_once 'Zend/Db/Adapter/Abstract.php';
/**
* #see Zend_Db_Statement_Pdo
*/
require_once 'Zend/Db/Statement/Pdo.php';
/**
* Class for connecting to SQL databases and performing common operations using PDO.
*
* #category Zend
* #package Zend_Db
* #subpackage Adapter
* #copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* #license http://framework.zend.com/license/new-bsd New BSD License
*/
abstract class Zend_Db_Adapter_Pdo_Abstract extends Zend_Db_Adapter_Abstract
{
/**
* Default class name for a DB statement.
*
* #var string
*/
protected $_defaultStmtClass = 'Zend_Db_Statement_Pdo';
/**
* Creates a PDO DSN for the adapter from $this->_config settings.
*
* #return string
*/
protected function _dsn()
{
// baseline of DSN parts
$dsn = $this->_config;
// don't pass the username, password, charset, persistent and driver_options in the DSN
unset($dsn['username']);
unset($dsn['password']);
unset($dsn['options']);
unset($dsn['charset']);
unset($dsn['persistent']);
unset($dsn['driver_options']);
// use all remaining parts in the DSN
foreach ($dsn as $key => $val) {
$dsn[$key] = "$key=$val";
}
return $this->_pdoType . ':' . implode(';', $dsn);
}
/**
* Creates a PDO object and connects to the database.
*
* #return void
* #throws Zend_Db_Adapter_Exception
*/
protected function _connect()
{
// if we already have a PDO object, no need to re-connect.
if ($this->_connection) {
return;
}
// get the dsn first, because some adapters alter the $_pdoType
$dsn = $this->_dsn();
// check for PDO extension
if (!extension_loaded('pdo')) {
/**
* #see Zend_Db_Adapter_Exception
*/
require_once 'Zend/Db/Adapter/Exception.php';
throw new Zend_Db_Adapter_Exception('The PDO extension is required for this adapter but the extension is not loaded');
}
// check the PDO driver is available
if (!in_array($this->_pdoType, PDO::getAvailableDrivers())) {
/**
* #see Zend_Db_Adapter_Exception
*/
require_once 'Zend/Db/Adapter/Exception.php';
throw new Zend_Db_Adapter_Exception('The ' . $this->_pdoType . ' driver is not currently installed');
}
// create PDO connection
$q = $this->_profiler->queryStart('connect', Zend_Db_Profiler::CONNECT);
// add the persistence flag if we find it in our config array
if (isset($this->_config['persistent']) && ($this->_config['persistent'] == true)) {
$this->_config['driver_options'][PDO::ATTR_PERSISTENT] = true;
}
try {
$this->_connection = new PDO(
$dsn,
$this->_config['username'],
$this->_config['password'],
$this->_config['driver_options']
);
$this->_profiler->queryEnd($q);
// set the PDO connection to perform case-folding on array keys, or not
$this->_connection->setAttribute(PDO::ATTR_CASE, $this->_caseFolding);
// always use exceptions.
$this->_connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
/**
* #see Zend_Db_Adapter_Exception
*/
require_once 'Zend/Db/Adapter/Exception.php';
throw new Zend_Db_Adapter_Exception($e->getMessage(), $e->getCode(), $e);
ini_set("memory_limit", -1);
}
}
/**
* Test if a connection is active
*
* #return boolean
*/
public function isConnected()
{
return ((bool) ($this->_connection instanceof PDO));
}
/**
* Force the connection to close.
*
* #return void
*/
public function closeConnection()
{
$this->_connection = null;
}
/**
* Prepares an SQL statement.
*
* #param string $sql The SQL statement with placeholders.
* #param array $bind An array of data to bind to the placeholders.
* #return PDOStatement
*/
public function prepare($sql)
{
$this->_connect();
$stmtClass = $this->_defaultStmtClass;
if (!class_exists($stmtClass)) {
require_once 'Zend/Loader.php';
Zend_Loader::loadClass($stmtClass);
}
$stmt = new $stmtClass($this, $sql);
$stmt->setFetchMode($this->_fetchMode);
return $stmt;
}
/**
* Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column.
*
* As a convention, on RDBMS brands that support sequences
* (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence
* from the arguments and returns the last id generated by that sequence.
* On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method
* returns the last value generated for such a column, and the table name
* argument is disregarded.
*
* On RDBMS brands that don't support sequences, $tableName and $primaryKey
* are ignored.
*
* #param string $tableName OPTIONAL Name of table.
* #param string $primaryKey OPTIONAL Name of primary key column.
* #return string
*/
public function lastInsertId($tableName = null, $primaryKey = null)
{
$this->_connect();
return $this->_connection->lastInsertId();
}
/**
* Special handling for PDO query().
* All bind parameter names must begin with ':'
*
* #param string|Zend_Db_Select $sql The SQL statement with placeholders.
* #param array $bind An array of data to bind to the placeholders.
* #return Zend_Db_Statement_Pdo
* #throws Zend_Db_Adapter_Exception To re-throw PDOException.
*/
public function query($sql, $bind = array())
{
if (empty($bind) && $sql instanceof Zend_Db_Select) {
$bind = $sql->getBind();
}
if (is_array($bind)) {
foreach ($bind as $name => $value) {
if (!is_int($name) && !preg_match('/^:/', $name)) {
$newName = ":$name";
unset($bind[$name]);
$bind[$newName] = $value;
}
}
}
try {
return parent::query($sql, $bind);
} catch (PDOException $e) {
/**
* #see Zend_Db_Statement_Exception
*/
require_once 'Zend/Db/Statement/Exception.php';
throw new Zend_Db_Statement_Exception($e->getMessage(), $e->getCode(), $e);
}
}
/**
* Executes an SQL statement and return the number of affected rows
*
* #param mixed $sql The SQL statement with placeholders.
* May be a string or Zend_Db_Select.
* #return integer Number of rows that were modified
* or deleted by the SQL statement
*/
public function exec($sql)
{
if ($sql instanceof Zend_Db_Select) {
$sql = $sql->assemble();
}
try {
$affected = $this->getConnection()->exec($sql);
if ($affected === false) {
$errorInfo = $this->getConnection()->errorInfo();
/**
* #see Zend_Db_Adapter_Exception
*/
require_once 'Zend/Db/Adapter/Exception.php';
throw new Zend_Db_Adapter_Exception($errorInfo[2]);
}
return $affected;
} catch (PDOException $e) {
/**
* #see Zend_Db_Adapter_Exception
*/
require_once 'Zend/Db/Adapter/Exception.php';
throw new Zend_Db_Adapter_Exception($e->getMessage(), $e->getCode(), $e);
}
}
/**
* Quote a raw string.
*
* #param string $value Raw string
* #return string Quoted string
*/
protected function _quote($value)
{
if (is_int($value) || is_float($value)) {
return $value;
}
$this->_connect();
return $this->_connection->quote($value);
}
/**
* Begin a transaction.
*/
protected function _beginTransaction()
{
$this->_connect();
$this->_connection->beginTransaction();
}
/**
* Commit a transaction.
*/
protected function _commit()
{
$this->_connect();
$this->_connection->commit();
}
/**
* Roll-back a transaction.
*/
protected function _rollBack() {
$this->_connect();
$this->_connection->rollBack();
}
/**
* Set the PDO fetch mode.
*
* #todo Support FETCH_CLASS and FETCH_INTO.
*
* #param int $mode A PDO fetch mode.
* #return void
* #throws Zend_Db_Adapter_Exception
*/
public function setFetchMode($mode)
{
//check for PDO extension
if (!extension_loaded('pdo')) {
/**
* #see Zend_Db_Adapter_Exception
*/
require_once 'Zend/Db/Adapter/Exception.php';
throw new Zend_Db_Adapter_Exception('The PDO extension is required for this adapter but the extension is not loaded');
}
switch ($mode) {
case PDO::FETCH_LAZY:
case PDO::FETCH_ASSOC:
case PDO::FETCH_NUM:
case PDO::FETCH_BOTH:
case PDO::FETCH_NAMED:
case PDO::FETCH_OBJ:
$this->_fetchMode = $mode;
break;
default:
/**
* #see Zend_Db_Adapter_Exception
*/
require_once 'Zend/Db/Adapter/Exception.php';
throw new Zend_Db_Adapter_Exception("Invalid fetch mode '$mode' specified");
break;
}
}
/**
* Check if the adapter supports real SQL parameters.
*
* #param string $type 'positional' or 'named'
* #return bool
*/
public function supportsParameters($type)
{
switch ($type) {
case 'positional':
case 'named':
default:
return true;
}
}
/**
* Retrieve server version in PHP style
*
* #return string
*/
public function getServerVersion()
{
$this->_connect();
try {
$version = $this->_connection->getAttribute(PDO::ATTR_SERVER_VERSION);
} catch (PDOException $e) {
// In case of the driver doesn't support getting attributes
return null;
}
$matches = null;
if (preg_match('/((?:[0-9]{1,2}\.){1,3}[0-9]{1,2})/', $version, $matches)) {
return $matches[1];
} else {
return null;
}
}
}
The error occurs in the following line:
throw new Zend_Db_Adapter_Exception($e->getMessage(), $e->getCode(), $e);
Please help me. Thank you.
Increase memory limit in your php.ini file
OR
set memory limit like - ini_set("memory_limit","10M"); in your project's configuration file
I installed open cart on my local server, but it is displaying a message at top.
Deprecated: mysql_connect(): The mysql extension is deprecated and will be removed in the future: use mysqli or PDO instead in D:\new\htdocs\business\system\database\mysql.php on line 6
How can I fix it ?
This error is because you're using PHP 5.5 or above. The best solution to this is to not suppress errors as others have said (since that prevents you seeing errors from other issues) but to install a mysqli extension/PDO extension for OpenCart. This one is free and works well - it's the one I use
I am using opencart-1.5.6.1.zip on ApacheFriends XAMPP Version 1.8.3 and I see this error message on every page too.
Open opencart/config.php and opencart/admin/config.php.
Edit 'mysql' -> 'mysqli'
e.g.
//define('DB_DRIVER', 'mysql');
define('DB_DRIVER', 'mysqli');
Save the files and no need to restart anything. The error message is gone.
below is the code for PDO in opencart 1.5.6 or <
<?php
/**
* Class for working with database (PDO)
*
* #property \PDO $dbh
*
* #author WebImperia Dev
* #since 0.0.1
*/
final class OC_PDO
{
/**
* Link to the database connection
*
* #var \PDO
*/
private $dbh;
/**
* List of connection settings
*
* #var array
*/
private $options = array(
'PDO::ATTR_ERRMODE' => PDO::ERRMODE_SILENT
);
/**
* The number of rows affected by the last operation
*
* #var int
*/
private $affectedRows = 0;
/**
* The data for the database connection
*
* #var \stdClass
*/
private $params = array();
/**
* Sets the connection and connects to the database
*
* #param string $host server Address
* #param string $user Username
* #param string $pass Password
* #param string $name The database name
* #param string $charset Encoding connection
*/
public function __construct($host, $user, $pass, $name, $charset = 'utf8')
{
$this->params = new stdClass;
# keep connection data
$this->params->host = $host;
$this->params->user = $user;
$this->params->pass = $pass;
$this->params->name = $name;
$this->params->charset = $charset;
$this->params->connstr = "mysql:host={$host};dbname={$name};charset={$charset}";
# add the connection parameters
$this->options['PDO::MYSQL_ATTR_INIT_COMMAND'] = "SET NAMES '{$charset}'";
$this->connect();
}
/**
* Connect to database
*/
public function connect()
{
try {
$this->dbh = new PDO($this->params->connstr, $this->params->user, $this->params->pass, $this->options);
if (version_compare(PHP_VERSION, '5.3.6', '<=')) {
$this->dbh->exec($this->options['PDO::MYSQL_ATTR_INIT_COMMAND']);
}
} catch (PDOException $exception) {
trigger_error($exception->getMessage());
}
}
/**
* Query the database
*
* #param string $sql
* #return \stdClass
*/
public function query($sql = null)
{
if ($this->dbh) {
$data = new stdClass;
$sth=$this->dbh->prepare($sql);
$sth->execute();
//$sth= $this->dbh->query($sql);
$this->affectedRows = $sth->rowCount();
$data->rows = $sth ? $sth->fetchAll() : array();
$data->row = isset($data->rows[0]) ? $data->rows[0] : null;
$data->num_rows = $this->affectedRows;
return $data;
}
return null;
}
/**
* Concludes the string in quotation marks to be used in the query
*
* #param mixed $string shielded line
* #return string Returns shielded line or to FALSE , if the driver does not support screening
*/
public function escape($string = null)
{
return $this->dbh ? $this->dbh->quote($string) : null;
}
/**
* Gets the number of rows affected by the last operation
*
* #return int
*/
public function countAffected()
{
return $this->affectedRows;
}
/**
* Gets the ID of the last inserted row or sequence of values
*
* #return int
*/
public function getLastId()
{
return $this->dbh ? $this->dbh->lastInsertId() : 0;
}
/**
* Gets the name of the driver
*
* #return string|null
*/
public function getDriverName()
{
return $this->dbh ? $this->dbh->getAttribute(PDO::ATTR_DRIVER_NAME) : null;
}
/**
* Get information about the version of the client libraries that are used by the PDO driver
*
* #return string|null
*/
public function getVersion()
{
return $this->dbh ? $this->dbh->getAttribute(PDO::ATTR_CLIENT_VERSION) : null;
}
/**
* Closing a database connection
*/
public function close()
{
$this->dbh = null;
}
public function __destruct()
{
$this->close();
}
}