So in php im trying to get a public variable in the database class that connects to a database when the class is created. Like so -
<?php
class database {
public $_link;
public function __construct (){
$this->_link = new PDO("mysql:host=localhost; dbname=swinkidc_student", "swinkidc_student", "");
}
}
and...
<?php
class user{
private $db;
public function __construct() {
$this->db = new database;
}
/**
* Returns the ID of a user.
* #param string $user
* #return mixed
*/
public function getUserID($user){
$query = $_link->prepare("SELECT `user_id` FROM `users` WHERE `username` = :user");
$query->bindParam(":user", $user);
$query->execute();
$result = $query->fetch(PDO::FETCH_ASSOC);
return $result['user_id'];
}
/**
* Checks if given user is active.
* #param string $user
* #return bool Returns true/false if user is active.
*/
public function isUserActive($user){
}
}
If I extened database in user I can obviously refer to _link if i make it private and it will work, however, I don't think that i should inherit just for getting something like that..
Whenever i try the about i get :
Fatal error: Call to a member function prepare() on a non-object in /home/swinkidc/public_html/studentreach/core/authentication/user.php on line 18
As said, if i try the inheritance way it will work, I just don't feel like thats a great way of doing something like this.
Any suggestions please?
Thanks!
link is actually a property of database and not a property of user. Therefore use:
$query = $this->db->_link->...
You are just missing the $this->db-> before:
$query = $this->db->_link->prepare("SELECT `user_id` FROM `users` WHERE `username` = :user");
Now, it should work; you're trying to access it directly. First access the Database-link holding object through the $this->db and only then access the property $_link.
If you need $_link as a variable because it's shorter, assign the object $this->db->_link to $_link:
$_link = $this->db->_link;
At the beginning of your getUserID function, then it'll work too.
Try, in the User class, something as:
<?php
class user{
private $db;
public function __construct() {
$this->db = new database;
}
/**
* Returns the ID of a user.
* #param string $user
* #return mixed
*/
public function getUserID($user){
$query = $this->db->_link->prepare("SELECT `user_id` FROM `users` WHERE `username` = :user");
$query->bindParam(":user", $user);
$query->execute();
$result = $query->fetch(PDO::FETCH_ASSOC);
return $result['user_id'];
}
/**
* Checks if given user is active.
* #param string $user
* #return bool Returns true/false if user is active.
*/
public function isUserActive($user){
}
}
This can works as _link is not a attribute of User, but of database, and, if you extend the class database, it's acessible from the methods in link (at least in Java, i not know if the same way works for PHP), but not if you just instantiate it. It's just basic OOP.
Good luck.
Better to invert dependencies and wrap $db.
class database {
private $_link;
public function __construct(\PDO $pdo) {
$this->_link = $pdo;
}
public function getLink() {
return $this->_link;
}
//other useful stuff
}
class user {
private $_db;
public function __construct(\database $db) {
$this->_db = $db;
}
public function getDB() {
return $this->_db;
}
/**
* Returns the ID of a user.
* #param string $user
* #return mixed
*/
public function getUserID($user) {
$query = $this->getDB()->getLink()->prepare("SELECT `user_id` FROM `users` WHERE `username` = :user");
$query->bindParam(":user", $user);
$query->execute();
$result = $query->fetch(PDO::FETCH_ASSOC);
return $result['user_id'];
}
/**
* Checks if given user is active.
* #param string $user
* #return bool Returns true/false if user is active.
*/
public function isUserActive($user) {
}
}
Usage
$user = new \user(new \database(new \PDO("mysql:host=localhost; dbname=swinkidc_student", "swinkidc_student", "")));
Now you can configure your db connection outside of the class.
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!
I’m creating an authentication / login system using Slim 3 PHP on the back-end and Angular on the front-end and I’m trying to understand the ‘domain object’ and ‘data mapper’ part of a model layer within an MVC structure. I’ve read a lot of useful answers on various questions such as this, from which I understand the model should be comprised of ‘domain objects’, ‘data mappers’ and ‘services’.
However I’m not exactly sure what how this should be structured in the context of a user being able to register and log in to a website.
From my understanding I could have a user 'domain object' that has properties such as username and password. It could also have methods such as register or log in to represent business logic.
Would I then have a service class that creates a new instance of a user object, in which I would pass the form data into the object? So now my user object instance would have set username and password values?
Now i'm not sure how this objects property data would be inserted into the database. Would I use the user objects register method to insert the data into the database by passing in the username and password as parameters?
Apparently the service should be where the domain object and the data mapper interact, but i'm not sure how this would work if the register method is in the user domain object.
I was hoping someone could show me some code examples of what should be in the service class and how the interaction between the domain object and data mapper might work in the context of a user registering and logging in.
Note I don't want to use any frameworks, I want to try and implement a proper MVC structure manually as I feel i'd learn more.
So far I have this structure for registering a user:
I have an AuthenticationController with the method registerUser to allow a user to create an account:
class AuthenticationController
{
protected $authenticationService;
public function __construct(AuthenticationService $authenticationService)
{
$this->authenticationService = $authenticationService;
}
public function registerUser($request, $response)
{
$this->authenticationService->registerUser($request, $response);
}
}
I then have the AuthenticationService class with the registerUser method:
class AuthenticationService
{
protected $database;
public function __construct(PDO $database)
{
$this->database = $database;
}
public function registerUser ($request, $response)
{
$strings = $request→getParsedBody(); // will be sanitised / validated later
$username = $strings['username'];
$password = $strings['password'];
$email = "temp random email";
$stmt = $this->database->prepare("INSERT INTO users (email, username, password) values (:email, :username, :password)");
$stmt->bindParam(':email', $email);
$stmt->bindParam(':username', $username);
$stmt->bindParam(':password', $password);
$stmt->execute();
}
}
Later on I intend to put the SQL into an AuthenticationRepository and the PDO logic into it’s own class. This AuthenticationService method will also make sure the user details are sanitised using PHP’s built in functions.
I’m not sure if the proposed PDO database class or AuthenticationRepository would count as a data mapper or not.
The registration would be performed by the service.
The service could "directly" use a data mapper, in order to "transfer" the entity to/from the database. Though, additionally, a repository can be implemented. The service would see it and communicate with it as with a collection of one or more entities.
Since a service is part of the model layer (domain model), it should know nothing about any request or response objects. The controller should extract the needed values from the request and pass them as arguments to the service methods. A response can be sent back by the controller, or the view, depending on which MVC variation you are trying to implement.
You say "I intend to put the [...] PDO logic into it's own class". You really don't need to implement a wrapper for the PDO extension.
Here a registration example. I didn't test it at all. For more details see the resources list at the end of this answer. Maybe begin with the last one, which - I just realized - is the answer to a question of yours.
Used file system structure:
a) Extended "MyApp/UI":
b) Extended "MyApp/Domain":
The controller:
<?php
namespace MyApp\UI\Web\Controller\Users;
use Psr\Http\Message\ServerRequestInterface;
use MyApp\Domain\Model\Users\Exception\InvalidData;
use MyApp\Domain\Service\Users\Exception\FailedRegistration;
use MyApp\Domain\Service\Users\Registration as RegistrationService;
class Registration {
private $registration;
public function __construct(RegistrationService $registration) {
$this->registration = $registration;
}
public function register(ServerRequestInterface $request) {
$username = $request->getParsedBody()['username'];
$password = $request->getParsedBody()['password'];
$email = $request->getParsedBody()['email'];
try {
$user = $this->registration->register($username, $password, $email);
} catch (InvalidData $exc) {
// Write the exception message to a flash messenger, for example,
// in order to be read and displayed by the specific view component.
var_dump($exc->getMessage());
} catch (FailedRegistration $exc) {
// Write the exception message to the flash messenger.
var_dump($exc->getMessage());
}
// In the view component, if no exception messages are found in the flash messenger, display a success message.
var_dump('Successfully registered.');
}
}
The service:
<?php
namespace MyApp\Domain\Service\Users;
use MyApp\Domain\Model\Users\User;
use MyApp\Domain\Model\Users\Email;
use MyApp\Domain\Model\Users\Password;
use MyApp\Domain\Service\Users\Exception\UserExists;
use MyApp\Domain\Model\Users\UserCollection as UserCollectionInterface;
class Registration {
/**
* User collection, e.g. user repository.
*
* #var UserCollectionInterface
*/
private $userCollection;
public function __construct(UserCollectionInterface $userCollection) {
$this->userCollection = $userCollection;
}
/**
* Register user.
*
* #param string $username Username.
* #param string $password Password.
* #param string $email Email.
* #return User User.
*/
public function register(string $username, string $password, string $email) {
$user = $this->createUser($username, $password, $email);
return $this->storeUser($user);
}
/**
* Create user.
*
* #param string $username Username.
* #param string $password Password.
* #param string $email Email.
* #return User User.
*/
private function createUser(string $username, string $password, string $email) {
// Create the object values (containing specific validation).
$email = new Email($email);
$password = new Password($password);
// Create the entity (e.g. the domain object).
$user = new User();
$user->setUsername($username);
$user->setEmail($email);
$user->setPassword($password);
return $user;
}
/**
* Store user.
*
* #param User $user User.
* #return User User.
*/
private function storeUser(User $user) {
// Check if user already exists.
if ($this->userCollection->exists($user)) {
throw new UserExists();
}
return $this->userCollection->store($user);
}
}
The exception thrown when trying to register an already existing user:
<?php
namespace MyApp\Domain\Service\Users\Exception;
use MyApp\Domain\Service\Users\Exception\FailedRegistration;
class UserExists extends FailedRegistration {
public function __construct(\Exception $previous = null) {
$message = 'User already exists.';
$code = 123;
parent::__construct($message, $code, $previous);
}
}
<?php
namespace MyApp\Domain\Service\Users\Exception;
abstract class FailedRegistration extends \Exception {
public function __construct(string $message, int $code = 0, \Exception $previous = null) {
$message = 'Registration failed: ' . $message;
parent::__construct($message, $code, $previous);
}
}
The domain object (entity):
<?php
namespace MyApp\Domain\Model\Users;
use MyApp\Domain\Model\Users\Email;
use MyApp\Domain\Model\Users\Password;
/**
* User entity (e.g. domain object).
*/
class User {
private $id;
private $username;
private $email;
private $password;
public function getId() {
return $this->id;
}
public function setId(int id) {
$this->id = $id;
return $this;
}
public function getUsername() {
return $this->username;
}
public function setUsername(string $username) {
$this->username = $username;
return $this;
}
public function getEmail() {
return $this->email;
}
public function setEmail(Email $email) {
$this->email = $email;
return $this;
}
public function getPassword() {
return $this->password;
}
public function setPassword(Password $password) {
$this->password = $password;
return $this;
}
}
The value objects used by the entity:
<?php
namespace MyApp\Domain\Model\Users;
use MyApp\Domain\Model\Users\Exception\InvalidEmail;
/**
* Email object value.
*/
class Email {
private $email;
public function __construct(string $email) {
if (!$this->isValid($email)) {
throw new InvalidEmail();
}
$this->email = $email;
}
private function isValid(string $email) {
return (isEmpty($email) || !isWellFormed($email)) ? false : true;
}
private function isEmpty(string $email) {
return empty($email) ? true : false;
}
private function isWellFormed(string $email) {
return !filter_var($email, FILTER_VALIDATE_EMAIL) ? false : true;
}
public function __toString() {
return $this->email;
}
}
<?php
namespace MyApp\Domain\Model\Users;
use MyApp\Domain\Model\Users\Exception\InvalidPassword;
/**
* Password object value.
*/
class Password {
private const MIN_LENGTH = 8;
private $password;
public function __construct(string $password) {
if (!$this->isValid($password)) {
throw new InvalidPassword();
}
$this->password = $password;
}
private function isValid(string $password) {
return (isEmpty($password) || isTooShort($password)) ? false : true;
}
private function isEmpty(string $password) {
return empty($password) ? true : false;
}
private function isTooShort(string $password) {
return strlen($password) < self::MIN_LENGTH ? true : false;
}
public function __toString() {
return $this->password;
}
}
The exceptions thrown by the value objects:
<?php
namespace MyApp\Domain\Model\Users\Exception;
use MyApp\Domain\Model\Users\Exception\InvalidData;
class InvalidEmail extends InvalidData {
public function __construct(\Exception $previous = null) {
$message = 'The email address is not valid.';
$code = 123402;
parent::__construct($message, $code, $previous);
}
}
<?php
namespace MyApp\Domain\Model\Users\Exception;
use MyApp\Domain\Model\Users\Exception\InvalidData;
class InvalidPassword extends InvalidData {
public function __construct(\Exception $previous = null) {
$message = 'The password is not valid.';
$code = 123401;
parent::__construct($message, $code, $previous);
}
}
<?php
namespace MyApp\Domain\Model\Users\Exception;
abstract class InvalidData extends \LogicException {
public function __construct(string $message, int $code = 0, \Exception $previous = null) {
$message = 'Invalid data: ' . $message;
parent::__construct($message, $code, $previous);
}
}
The repository interface:
<?php
namespace MyApp\Domain\Model\Users;
use MyApp\Domain\Model\Users\User;
/**
* User collection, e.g. user repository.
*/
interface UserCollection {
/**
* Find a user by id.
*
* #param int $id User id.
* #return User|null User.
*/
public function findById(int $id);
/**
* Find all users.
*
* #return User[] User list.
*/
public function findAll();
/**
* Check if the given user exists.
*
* #param User $user User
* #return bool True if user exists, false otherwise.
*/
public function exists(User $user);
/**
* Store a user.
*
* #param User $user User
* #return User User.
*/
public function store(User $user);
}
The repository:
<?php
namespace MyApp\Domain\Infrastructure\Repository\Users;
use MyApp\Domain\Model\Users\User;
use MyApp\Domain\Infrastructure\Mapper\Users\UserMapper;
use MyApp\Domain\Model\Users\UserCollection as UserCollectionInterface;
/**
* User collection, e.g. user repository.
*/
class UserCollection implements UserCollectionInterface {
private $userMapper;
public function __construct(UserMapper $userMapper) {
$this->userMapper = $userMapper;
}
/**
* Find a user by id.
*
* #param int $id User id.
* #return User|null User.
*/
public function findById(int $id) {
return $this->userMapper->fetchUserById($id);
}
/**
* Find all users.
*
* #return User[] User list.
*/
public function findAll() {
return $this->userMapper->fetchAllUsers();
}
/**
* Check if the given user exists.
*
* #param User $user User
* #return bool True if user exists, false otherwise.
*/
public function exists(User $user) {
return $this->userMapper->userExists($user);
}
/**
* Store a user.
*
* #param User $user User
* #return User User.
*/
public function store(User $user) {
return $this->userMapper->saveUser($user);
}
}
The data mapper interface:
<?php
namespace MyApp\Domain\Infrastructure\Mapper\Users;
use MyApp\Domain\Model\Users\User;
/**
* User mapper.
*/
interface UserMapper {
/**
* Fetch a user by id.
*
* #param int $id User id.
* #return User|null User.
*/
public function fetchUserById(int $id);
/**
* Fetch all users.
*
* #return User[] User list.
*/
public function fetchAllUsers();
/**
* Check if the given user exists.
*
* #param User $user User.
* #return bool True if the user exists, false otherwise.
*/
public function userExists(User $user);
/**
* Save a user.
*
* #param User $user User.
* #return User User.
*/
public function saveUser(User $user);
}
The data mapper:
<?php
namespace MyApp\Domain\Infrastructure\Mapper\Users;
use PDO;
use MyApp\Domain\Model\Users\User;
use MyApp\Domain\Model\Users\Email;
use MyApp\Domain\Model\Users\Password;
use MyApp\Domain\Infrastructure\Mapper\Users\UserMapper;
/**
* PDO user mapper.
*/
class PdoUserMapper implements UserMapper {
/**
* Database connection.
*
* #var PDO
*/
private $connection;
public function __construct(PDO $connection) {
$this->connection = $connection;
}
/**
* Fetch a user by id.
*
* Note: PDOStatement::fetch returns FALSE if no record is found.
*
* #param int $id User id.
* #return User|null User.
*/
public function fetchUserById(int $id) {
$sql = 'SELECT * FROM users 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->convertRecordToUser($record);
}
/**
* Fetch all users.
*
* #return User[] User list.
*/
public function fetchAllUsers() {
$sql = 'SELECT * FROM users';
$statement = $this->connection->prepare($sql);
$statement->execute();
$recordset = $statement->fetchAll(PDO::FETCH_ASSOC);
return $this->convertRecordsetToUserList($recordset);
}
/**
* Check if the given user exists.
*
* Note: PDOStatement::fetch returns FALSE if no record is found.
*
* #param User $user User.
* #return bool True if the user exists, false otherwise.
*/
public function userExists(User $user) {
$sql = 'SELECT COUNT(*) as cnt FROM users WHERE username = :username';
$statement = $this->connection->prepare($sql);
$statement->execute([
':username' => $user->getUsername(),
]);
$record = $statement->fetch(PDO::FETCH_ASSOC);
return ($record['cnt'] > 0) ? true : false;
}
/**
* Save a user.
*
* #param User $user User.
* #return User User.
*/
public function saveUser(User $user) {
$id = $user->getId();
if (!isset($id)) {
return $this->insertUser($user);
}
return $this->updateUser($user);
}
/**
* Insert a user.
*
* #param User $user User.
* #return User User.
*/
private function insertUser(User $user) {
$sql = 'INSERT INTO users (
username,
password,
email
) VALUES (
:username,
:password,
:email
)';
$statement = $this->connection->prepare($sql);
$statement->execute([
':username' => $user->getUsername(),
':password' => (string) $user->getPassword(),
':email' => (string) $user->getEmail(),
]);
$user->setId($this->connection->lastInsertId());
return $user;
}
/**
* Update a user.
*
* #param User $user User.
* #return User User.
*/
private function updateUser(User $user) {
$sql = 'UPDATE users
SET
username = :username,
password = :password,
email = :email
WHERE id = :id';
$statement = $this->connection->prepare($sql);
$statement->execute([
':id' => $user->getId(),
':username' => $user->getUsername(),
':password' => (string) $user->getPassword(),
':email' => (string) $user->getEmail(),
]);
return $user;
}
/**
* Convert a record to a user.
*
* #param array $record Record data.
* #return User User.
*/
private function convertRecordToUser(array $record) {
$user = $this->createUser(
$record['id'],
$record['username'],
$record['password'],
$record['email']
);
return $user;
}
/**
* Convert a recordset to a list of users.
*
* #param array $recordset Recordset data.
* #return User[] User list.
*/
private function convertRecordsetToUserList(array $recordset) {
$users = [];
foreach ($recordset as $record) {
$users[] = $this->convertRecordToUser($record);
}
return $users;
}
/**
* Create user.
*
* #param int $id User id.
* #param string $username Username.
* #param string $password Password.
* #param string $email Email.
* #return User User.
*/
private function createUser(int $id, string $username, string $password, string $email) {
$user = new User();
$user
->setId($id)
->setUsername($username)
->setPassword(new Password($password))
->setEmail(new Email($email))
;
return $user;
}
}
Resources:
Keynote: Architecture the Lost Years
Sandro Mancuso : An introduction to interaction-driven design
Unbreakable Domain Models
An older answer of mine, for some explanations.
I feel a bit stupid asking this question since there are a lot of resources talking and explaining mappers and repositories but I can't seem to get my head around it. So I've created some example code to explain my confusion. Please note that I don't know if this code would actually work I wrote this as an example.
This would be the entity / class (Quote.php)
class Quote {
private $id;
private $author;
private $content;
public function getId() {
return $this->id;
}
public function getAuthor() {
return $this->author;
}
public function getContent() {
return $this->content;
}
public function setId(int $id) {
$this->id = $id;
}
public function getAuthor(string $author) {
$this->author = $author;
}
public function setContent(string $content) {
$this->content = $content;
}
}
And this would be the mapper (QuoteMapper.php)
class QuoteMapper {
private $PDO;
public function __construct(PDO $PDO) {
$this->PDO = $PDO;
}
public function find(int $id = null, string $search = null) {
if (!empty($id) && !empty($search)) {
//Search for id and search word
$stmt = $this->PDO->prepare("SELECT `id`, `author`, `content` FROM `quotes` WHERE `id` = :id AND `content` LIKE :search LIMIT 1");
$stmt->bindParam('search', $search, PDO::PARAM_INT);
$stmt->bindParam('id', $id, PDO::PARAM_INT);
else if (!empty($id)) {
//search for id only
$stmt = $this->PDO->prepare("SELECT `id`, `author`, `content` FROM `quotes` WHERE `id` = :id AND `content` LIKE :search LIMIT 1");
$stmt->bindParam('id', $id, PDO::PARAM_INT);
} else if (!empty($search)) {
//search for search word only
$stmt = $this->PDO->prepare("SELECT `id`, `author`, `content` FROM `quotes` WHERE `id` = :id AND `content` LIKE :search LIMIT 1");
$stmt->bindParam('search', $search, PDO::PARAM_INT);
}
$stmt->execute();
$stmt->bindColumn('id', $id);
$stmt->bindColumn('author', $author);
$stmt->bindColumn('content', $content);
$stmt->fetch();
$quote = new Image();
$quote->setId($title);
$quote->setAuthor($source);
$quote->setContent($alternative);
return $image;
}
public function save(Quote $quote) {
//A save function
}
public function delete(Quote $quote) {
//A delete function
}
}
Last but not least, this would be the repository (QuoteRepository.php)
class ArticleRepository {
private $articleMapper;
public function __construct(ArticleMapper $articleMapper) {
$this->articleMapper = $articleMapper;
}
public function find(int $id = null, string $search = null) {
$article = $this->articleMapper->find($id, $search);
return $article;
}
public function save(Quote $quote) {
$this->articleMapper->save($user);
}
public function delete(Quote $quote) {
$this->articleMapper->delete($user);
}
}
As I understand my mapper isn't 'wrong' since the purpose of the mapper is to do things such as get and set data from persistent data storage (such as MySQL)
A Data Mapper is a Data Access Layer that performs bidirectional
transfer of data between a persistent data store (often a relational
database) and an in-memory data representation (the domain layer).
From Wikipedia
But my repository doesn't actually do anything. It just passes the function call along to the mapper? So I can only assume that my mapper contains code that should be in the repository, but what code would that be? Or perhaps I've completely misunderstood how data mappers and repositories would work together.
If there are any other things that I have done that are wrong or considered bad practice I would like to hear it. I'm really trying to figure this out! :)
DataMapper is a layer to isolate an application from a concrete database. It transforms an object into a record of a database and a record into an object. DataMapper gives us the ability to work with Database and be unaware of what RDBMS we use. Example:
interface DataMapperInterface
{
/**
* Find objects by a criteria
*
* #param array $criteria Search params
* #return Quote[] Found entities
*/
public function find(array $criteria);
/**
* Insert an object into a database
*
* #param Quote $object Object that will be inserted
*/
public function insert(Quote $object);
/**
* Update an object date in a database
*
* #param Quote $object Object that will be updated
*/
public function update(Quote $object);
/**
* Remove an object from a database
*
* #param Quote $object Object that will be removed
*/
public function delete(Quote $object);
}
Repository is a layer for encapsulation of logic of a query building. It gives us the ability to work with a collection of objects and be unaware to work with Database anything.
class Repository
{
/**
* #var DataMapperInterface Mapper to transform objects
*/
protected $mapper;
/**
* Constructor
*
* #param DataMapperInterface $mapper Mapper to transform objects
*/
public function __construct(DataMapperInterface $mapper)
{
$this->mapper = $mapper;
}
/**
* Find all objects
*
* #return Quote[] Found entities
*/
public function findAll()
{
return $this->mapper->find([]);
}
/**
* Find an object by an identifier
*
* #return Quote[] Found entities
*/
public function findById(integer $id)
{
$criteria = ['id' => $id];
return $this->mapper->find($criteria);
}
/**
* Find objects by an author name
*
* #return Quote[] Found entities
*/
public function findByAuthor($name)
{
$criteria = ['author' => $name];
return $this->mapper->find($criteria);
}
/**
* Save an object into the repository
*/
public function save(Quote $object)
{
if (empty($object->id)) {
$this->mapper->insert($object);
} else {
$this->mapper->update($object);
}
a }
/**
* Remove an object from the repository
*/
public function remove(Quote $object)
{
$this->mapper->delete($object);
}
}
This is the very simple instance and all is more difficult in a real application: queries are bigger, repository can cooperate with many other patterns (Query Object to query building, Unit of Work to track changes, Identity Map to avoid repeatedly-load of objects, etc.)
I've created a User database abstraction class in PHP which extends a base class I made called DBO (database object). The DBO object's job is just to hold $db as my codeigniter's $db reference.
So ideally, in my User object, I could do $this->db->insert() and access my codeigniter's database object.
here's my code:
DBO:
class DBO {
public $db;
public $ci;
//put your code here
public function __construct() {
$ci =& get_instance();
$this->db =& $ci->db;
}
}
USER: (which extends DBO)
class User extends DBO{
/** User id of the user
* #var int */
public $user_id;
/** User's first name in english
* #var string */
public $first_name;
/** User's last name in english
* #var string */
public $last_name;
/** User's name in Korean
* #var string */
public $korean_name;
/** User's phone number
* #var string */
public $phone;
/** User's email address
* #var string */
public $email;
/**
* Creates a new user from a row or blank.
* Creates a new user from a database row if given or an empty User object if null
*
* #param Object $row A row object from table: admin_users
*/
public function __construct(stdClass $row = null){
if($row){
if(isset($row->user_id)) $this->user_id = $row->user_id;
if(isset($row->first_name)) $this->first_name = $row->first_name;
if(isset($row->last_name)) $this->last_name = $row->last_name;
if(isset($row->korean_name)) $this->korean_name = $row->korean_name;
if(isset($row->phone)) $this->phone = $row->phone;
if(isset($row->email)) $this->email = $row->email;
}
}
/**
* Saves this user to the database.
* #return boolean Whether this was successfully saved to the database.
*/
public function create(){
$data = array(
'first_name' => $this->first_name,
'last_name' => $this->last_name,
'korean_name' => $this->korean_name,
'phone' => $this->phone,
'email' => $this->email,
);
if($this->db->insert(Tables::$USERS, $data) && $this->db->affected_rows() == 1){
$this->user_id = $this->db->insert_id();
return true;
} else {
return false;
}
}
But whenever I try to use $this->db->insert(), it says Call to a member function insert() on a non-object.
Is there a way to make this work? Or is it fundamentally wrong? Should my User class ONLY hold the information, and pass a User object to my Users model object and have that run the database functionality?
Thanks for the help
You are missing call to the parent constructor. Use
parent::__contstruct()
in your user class constructor. By doing this, the parent class constructor will be called and so the db object will be initialised and you will be able to use it.
You should also read this
http://www.techflirt.com/tutorials/oop-in-php/inheritance-in-php.html
to understand inheritance. Kindly read about OOPs also so you can get idea of features OOPs in PHP is providing.
I have a very weird behaviour with PDO. I won't go into much details as it would take up way too much time but basically what I observed is that when I re-use a \PDOStatement that performs a simple INSERT I sistematically get a wrong value when invoking PDO::lastInsertId().
The first time I execute the statement it works fine and I get back the right id. Subsequent executions will instead always return '0'. This is even more weird because it happens only between tests (PHPUnit ones). So say I execute the insert using the prepared statement in test1 (working), in test2 it will fail miserably.
When executing multiple times the prepared statement in a non unit-testing environment (in a simple php file fro instance) it all works fine and the last inserted ids are always accurate. Very weird indeed.
Here's the test (note that PersistencyManagerInstance is just a plain intsance of PersistencyManager):
<?php
class PersistencyManagerTest extends PHPUnit_Framework_TestCase {
const DELETE_ALL = "TRUNCATE user";
const ADD_USER = "INSERT INTO user values(null, :username, :password)";
const CHECK_USER_EXISTENCE = "SELECT * FROM user WHERE username = :username AND password = :password";
const DELETE_USER_BY_ID = "DELETE FROM user WHERE id = ?";
protected $manager = null;
public function __construct() {
$this->manager = new PersistencyManagerInstance(PDOFactory::build());
}
public function setUp() {
$this->manager->exec(self::DELETE_ALL);
}
public function tearDown() {
$this->manager->exec(self::DELETE_ALL);
}
public function testInsert() {
$user = new User("laurent", "password");
$id = $this->manager->insert(self::ADD_USER, $user->export());
$this->assertEquals("1", $id);
}
public function testInsertAgain() {
$user1 = new User("laurent1", "password1");
$id = $this->manager->insert(self::ADD_USER, $user1->export());
$this->assertEquals("1", $id);
}
public function testQuery() {
$user = new User("laurent", "password");
$this->manager->insert(self::ADD_USER, $user->export());
$results = $this->manager->query(self::CHECK_USER_EXISTENCE, $user->export());
$this->assertEquals(1, count($results));
}
public function testExec() {
$user = new User("laurent", "password---");
$id = $this->manager->insert(self::ADD_USER, $user->export());
$affected = $this->manager->exec(self::DELETE_USER_BY_ID, array($id));
$this->assertEquals(1, $affected);
}
}
testInsert works while testInsertAgain does not.
and here's the class:
<?php
namespace memory\manager;
use \PDO;
abstract class PersistencyManager {
/**
* #var array An array of \PDOStatement objects
*/
protected static $ps = array();
/**
* #var \PDO
*/
protected $connection = null;
protected function prepareStmt($sql) {
// return $this->connection->prepare($sql);
$key = md5($sql);
if (!isset(self::$ps[$key])) {
self::$ps[$key] = $this->connection->prepare($sql);
}
return self::$ps[$key];
}
public function __construct(PDO $connection) {
$this->connection = $connection;
$this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
public function __destruct() {
$this->connection = null;
}
/**
* Good for SELECT operations. By default it fetches using arrays.
* #param string $sql
* #param array $values
* #param integer $fetchStyle
* #return array A list of matching elements (The elements' type depends on $fetchStyle)
*/
public function query($sql, array $values = array(), $fetchStyle = PDO::FETCH_ASSOC) {
$prepared = $this->prepareStmt($sql);
$prepared->execute($values);
$prepared->setFetchMode($fetchStyle);
$all = $prepared->fetchAll();
$prepared->closeCursor();
return $all;
}
/**
* Good for INSERT operations.
* #param string $sql
* #param array $values
* #return string Last inserted element's id in string format
*/
public function insert($sql, array $values = array()) {
$prepared = $this->prepareStmt($sql);
$prepared->execute($values);
$prepared->closeCursor();
return $this->connection->lastInsertId();
}
/**
* Good for all the remaining routines.
* #param string $sql
* #param array $values
* #return integer The number of effected rows
*/
public function exec($sql, array $values = array()) {
$prepared = $this->prepareStmt($sql);
$prepared->execute($values);
$count = $prepared->rowCount();
$prepared->closeCursor();
return $count;
}
}
Any idea?
Cheers
guys I was starting a new connection at every test. That was the reason.