Best and Faster way for generate DAO class - php

I have reed a lot of pages about how generate a DAO class using PDO but I haven't find a good and faster way for obtain it.
Suppose we have a Database table called Animals with this structure
CREATE TABLE animals
(
idAnimal int PRIMARY KEY NOT NULL AUTO_INCREMENT,
name varchar(20),
dateOfBirth DATETIME
);
A good way of development is create two class: Animals and AnimalsDAO like this
class Animals{
/**
* #var integer
*/
public $idAnimal;
/**
* #var string
*/
public $name;
/**
* #var DateTime
*/
public $dateOfBirth;
/**
* Animals constructor.
* #param int $idAnimal
* #param string $name
* #param DateTime $dateOfBirth
*/
public function __construct($idAnimal, $name, DateTime $dateOfBirth) {
$this->idAnimal = $idAnimal;
$this->name = $name;
$this->dateOfBirth = $dateOfBirth;
}
}
In the AnimalsDAO generally there is the method for insert and retrive the object from database.
And in this class there is already an ORM problems (ORM Object Relation Mapping) because the fetch method of PDO don't work properly with object casting
For years I have develop DAO in this way
class ClienteDAO {
/**
* #param $idAnimal integer
* #return Cliente
* #throws Exception
*/
public static function getClienteById($idAnimal ){
$q="SELECT * FROM animals WHERE idAnimal =:idanim";
$sth=PDOConnection::instance()->prepare($q);
$sth->bindParam(':idanim', $idCliente,PDO::PARAM_INT);
if($sth->execute()==0)
throw new PDOException("ERROR EXECUTE");
if($sth->rowCount()!=1)
throw new PDOException("ERROR ROW NUMBERS");
$row=$sth->fetch(PDO::FETCH_NUM);
$row[2]=new DateTime($row[2]);
return new Animals(...$row);
}
}
So then if I change add or remove a database field, I have to only edit the variable in the Animal class, regenerate the constructor (I use PhpStorm) and edit eventually the rows after the fetch
Exist a best and faster way for generate the DAO class ?
(Another way is retrive the class attribute name and use the fetch Names method but there is again the casting problems with dateTime columns)
The problems is more complicate when there is an inheritance into DataBase solved with association and the inheritance is in the php class
ER DIAGRAM
And the database structure is translated in
DATABASE STRUCTURE
Obviously, on the php side there is a father super class and two child class (extends the fater class)
How is the faster way for generate the DAO method for the child ?

As per OP req, an example from my Codebase
Device
namespace DAO;
use common\Context;
use enums\DeviceOStypeEnum;
use NAO\Inbound\IBDeviceSpec;
use NAO\Outbound\OBDevice;
use protocols\IClassInit;
use protocols\ITokenizer;
use traits\ClassInitTrait;
class Device extends AbstractDataObject implements IClassInit , ITokenizer
{
const APPLE_PERMANENT_DEVICE_GUID = "f8d55ac7-6e6a-4a0c-a5ec-20df1f384d62";
const GOOGLE_PERMANENT_DEVICE_GUID = "788996ff-5da3-47f2-9601-3f9ae79b51aa";
use ClassInitTrait;
/** #var int $id */
protected $id;
/** #var string $deviceGuid */
var $deviceGuid;
/** #var DeviceOStypeEnum $osType */
var $osType;
/** #var Version $osVersion */
var $osVersion;
/** #var string $manufacturer */
var $manufacturer;
/** #var string $modelCode */
var $modelCode;
/** #var \DateTime $createdOn */
var $createdOn;
/**#var \DateTime $lastSeen */
var $lastSeen;
/** #var bool $active */
var $active;
/** #var $isPhone */
var $isPhone;
/** #var App $app */
var $app;
public static function postInit($c , $isTraceEnabled , $isPDOuser)
{
}
/**
* Device constructor.
*
* #param int $id
* #param string $deviceGuid
* #param DeviceOStypeEnum $osType
* #param Version $osVersion
* #param string $manufacturer
* #param string $modelCode
* #param \DateTime $createdOn
* #param \DateTime $lastSeen
* #param App $app
* #param bool $isPhone
* #param bool $active
*/
public function __construct($id , $deviceGuid ,
$osType , $osVersion , $manufacturer , $modelCode ,
\DateTime $createdOn , \DateTime $lastSeen ,
$active , $isPhone , $app)
{
$this->id = $id;
$this->deviceGuid = $deviceGuid;
$this->osType = $osType;
$this->osVersion = $osVersion;
$this->manufacturer = $manufacturer;
$this->modelCode = $modelCode;
$this->createdOn = $createdOn;
$this->lastSeen = $lastSeen;
$this->active = $active;
$this->app = $app;
$this->isPhone = $isPhone;
}
/**
* #param array $row
*
* #return Device
*/
public static function fromAssociativeArray($row)
{
$OStype = new DeviceOStypeEnum($row['os_type']);
$osVersion = Version::fromString($row['os_version']);
$createdOn = dateTimeFromSQLquery($row['created_on']);
$lastSeen = dateTimeFromSQLquery($row['last_seen']);
$active = (bool) $row['active'];
$deviceGuid = binaryGuidAsStringGuid($row['device_guid_bin']);
$isPhone = (bool) $row['is_phone'];
$app = AppDAO::applicationWithId($row['app_id']);
return new Device(
$row['id'] ,
$deviceGuid ,
$OStype ,
$osVersion ,
$row['manufacturer'] ,
$row['model_code'] ,
$createdOn ,
$lastSeen ,
$active ,
$isPhone ,
$app
);
}
// plus a whole bunch of business logic after
DeviceDAO (partiel)
namespace DAO;
use enums\DeviceOStypeEnum;
use NAO\Inbound\IBDeviceSpec;
use protocols\IClassInit;
use traits\ClassInitTrait;
class DeviceDAO implements IClassInit
{
use ClassInitTrait;
/**
* #param string $guid
* #param DeviceOStypeEnum $osType
* #param Version $osVersion
* #param string $manufacturer
* #param string $modelCode
* #param boolean $isPhone
* #param App $app
*
* #return Device|null
*/
public static function insert($guid ,
DeviceOStypeEnum $osType , Version $osVersion ,
$manufacturer , $modelCode ,
$isPhone , App $app)
{
$pdo = self::getClassPDO();
$q = $e = null;
$createdOn = now();
$lastSeen = now();
$sql = <<<SQL
INSERT INTO Device SET device_guid_bin = :guid,
os_type = :ost,
os_version = :version ,
manufacturer=:manufacturer,model_code=:model,
created_on=:co, last_seen = :lastseen , active=1, `is_phone`=:phone, `app_id` = :appid
SQL;
$device = null;
try {
$q = $pdo->prepare($sql);
$q->bindValue('guid' , stringGuidAsBinaryGuid($guid) , \PDO::PARAM_STR);
$q->bindValue('ost' , $osType->stringValue , \PDO::PARAM_STR);
$q->bindValue('version' , $osVersion->__toString() , \PDO::PARAM_STR);
$q->bindValue('manufacturer' , $manufacturer , \PDO::PARAM_STR);
$q->bindValue('model' , $modelCode , \PDO::PARAM_STR);
$q->bindValue('co' , dateTimeAsSQLstring($createdOn) , \PDO::PARAM_STR);
$q->bindValue('lastseen' , dateTimeAsSQLstring($lastSeen) , \PDO::PARAM_STR);
$q->bindValue('phone' , $isPhone , \PDO::PARAM_BOOL);
$q->bindValue('appid' , $app->getId() , \PDO::PARAM_INT);
if ($q->execute()) {
$id = $pdo->lastInsertId();
$device = new Device(
$id , $guid ,
$osType , $osVersion ,
$manufacturer , $modelCode ,
$createdOn , $lastSeen , true , $isPhone ,
$app
);
} else {
self::logQueryFail("Unknown error while inserting a device" , $q , $e);
}
} catch (\Exception $e) {
self::logQueryFail("Error while inserting a Device" , $q , $e);
}
return $device;
}
/**
* #param IBDeviceSpec $spec
*
* #return Device|null
*/
public static function insertWithDeviceSpec(IBDeviceSpec $spec)
{
$app = AppDAO::applicationWithGuid($spec->appGuid);
return self::insert(
$spec->deviceGuid , $spec->osType , $spec->osVersion , $spec->manufacturer , $spec->modelCode ,
$spec->isPhone , $app
);
}
/**
* #param Device $device
*
* #return bool
*/
public static function update(Device $device)
{
if (!$device) {
self::getClassLogger()->error("Attemptempt to update null Device");
return false;
}
$pdo = self::getClassPDO();
$q = $e = null;
$sql = <<<SQL
UPDATE Device
SET device_guid_bin = :guid,
os_type = :ost,
os_version = :version ,
manufacturer=:manufacturer,
model_code=:model,
created_on=:co,
last_seen = :lastseen,
active=:ac,
`is_phone`=:phone,
`app_id`=:appid
WHERE
id=:id
SQL;
try {
$q = $pdo->prepare($sql);
$q->bindValue('id' , $device->getId() , \PDO::PARAM_STR);
$q->bindValue('guid' , stringGuidAsBinaryGuid($device->deviceGuid) , \PDO::PARAM_STR);
$q->bindValue('ost' , $device->osType->stringValue , \PDO::PARAM_STR);
$q->bindValue('version' , $device->osVersion->__toString() , \PDO::PARAM_STR);
$q->bindValue('manufacturer' , $device->manufacturer , \PDO::PARAM_STR);
$q->bindValue('model' , $device->modelCode , \PDO::PARAM_STR);
$q->bindValue('co' , dateTimeAsSQLstring($device->createdOn) , \PDO::PARAM_STR);
$q->bindValue('lastseen' , dateTimeAsSQLstring($device->lastSeen) , \PDO::PARAM_STR);
$q->bindValue('ac' , $device->active , \PDO::PARAM_BOOL);
$q->bindValue('phone' , $device->isPhone , \PDO::PARAM_BOOL);
$q->bindValue('appid' , $device->app->getId() , \PDO::PARAM_INT);
if ($q->execute()) {
return true;
} else {
self::logQueryFail("Unknown error while updating a device" , $q , $e);
}
} catch (\Exception $e) {
self::logQueryFail("Error while inserting a Device" , $q , $e);
}
return false;
}
/**
* #param string $guid
*
* #return Device|null
*/
public static function deviceWithDeviceGuid($guid)
{
if (!$guid) return null;
$pdo = self::getClassPDO();
$q = $e = null;
$device = null;
$sql = <<<SQL
SELECT * FROM Device WHERE device_guid_bin=:gu
SQL;
try {
$q = $pdo->prepare($sql);
$q->bindValue(':gu' , stringGuidAsBinaryGuid($guid) , \PDO::PARAM_STR);
if ($q->execute()) {
$rows = $q->fetchAll();
if (count($rows) == 0) {
self::getClassLogger()->trace(__FUNCTION__ . " Query for device [$guid] returned no device");
} else if (count($rows) > 1) {
self::logQueryFail(__FUNCTION__ . " : Query for device returned multiple rows ! [$guid]" , $q , $e);
} else {
$row = $rows[0];
$device = Device::fromAssociativeArray($row);
}
} else {
self::logQueryFail(__FUNCTION__ . " : Error while fetching device with guid[$guid]" , $q , $e);
}
} catch (\Exception $e) {
self::logQueryFail(__FUNCTION__ . " : Error while fetching device with guid[$guid]" , $q , $e);
}
return $device;
}
}
// etc ...
The SQL
--
-- Table structure for table `Device`
--
DROP TABLE IF EXISTS `Device`;
/*!40101 SET #saved_cs_client = ##character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `Device`
(
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`app_id` bigint(20) NOT NULL,
`os_type` enum ('android','iPhone OS','iOS','nix') NOT NULL,
`os_version` varchar(11) DEFAULT NULL,
`manufacturer` varchar(50) DEFAULT NULL,
`model_code` varchar(50) DEFAULT NULL,
`created_on` datetime NOT NULL,
`last_seen` datetime NOT NULL,
`active` tinyint(4) NOT NULL DEFAULT '1',
`is_phone` tinyint(4) NOT NULL DEFAULT '1',
`device_guid_bin` varbinary(16) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_device_guid` (`device_guid_bin`),
KEY `idx_app` (`app_id`),
KEY `idx_active` (`active`),
CONSTRAINT `fk_device_app` FOREIGN KEY (`app_id`) REFERENCES `App` (`id`) ON DELETE CASCADE
) ENGINE = InnoDB
AUTO_INCREMENT = 68
DEFAULT CHARSET = utf8mb4;
/*!40101 SET character_set_client = #saved_cs_client */;
notes
Have not shown functions like dateTimeFromSQLquery etc ... all in my global functions. I use such to normalize DB. Time is always UTC at rest (DB), and in flight (API)
For relationships, i systematically prefer a lazy load method (not in the code shown)
SomeObjectDAO encapsulates completely caching (or not).

Related

Query specific field with doctrine returns object array null

I'm facing what I think is a really easy task (or it should be at least), I've seen a lot of related questions and try to resolve my own problem with them with no luck at all, this is why I'm writing my own question here.
I want to be able to gather the average temperature from my table within the last 7 rows, i.e the average temperature for the last 7 days.
If I query my database using this simple query I obtain a result that matches my requirements:
select avg(m.temperatura) as temperature from (SELECT temperatura from database.meteodata order by indice desc limit 7) m
But, the problem is trying to move that query into doctrine with PHP.
I'm new to doctrine. I'm building a API using slim framework (version 3). I have a database with a table following this schema:
CREATE TABLE `meteodata` (
'indice' int(11) NOT NULL AUTO_INCREMENT,
'timestamp' timestamp NULL DEFAULT NULL,
'temperatura' float DEFAULT NULL,
'humedad' float DEFAULT NULL,
'viento' float NOT NULL,
'direccion_viento' int(11) NOT NULL,
'lluvia' float NOT NULL,
'summary' varchar(100) DEFAULT NULL,
'icon' varchar(45) DEFAULT NULL,
PRIMARY KEY ('indice')
) ENGINE=InnoDB AUTO_INCREMENT=562 DEFAULT CHARSET=latin1;
I also have an entity class to match that table into my PHP classes like following:
use Doctrine\ORM\Mapping as ORM;
/** #ORM\Entity()
* #ORM\Table(name="meteodata")
*/
class MeteoStation implements JsonSerializable
{
/** #ORM\Id()
* #ORM\Column(name="indice", type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $indice;
/** #ORM\Column(type="datetime", nullable=false) */
protected $timestamp;
/** #ORM\Column(name="viento", type="float") */
protected $wind;
/** #ORM\Column(name="temperatura", type="float") */
protected $temperature;
/** #ORM\Column(name="humedad", type="float") */
protected $humidity;
/** #ORM\Column(name="lluvia", type="float") */
protected $rain;
/** #ORM\Column(name="direccion_viento", type="integer") */
protected $dirViento;
/** #ORM\Column(length=100) */
protected $summary;
/** #ORM\Column(length=20) */
protected $icon;
/**
* #return mixed
*/
public function getIndice()
{
return $this->indice;
}
/**
* #param mixed $indice
*/
public function setIndice($indice)
{
$this->indice = $indice;
}
/**
* #return mixed
*/
public function getTimestamp()
{
return $this->timestamp;
}
/**
* #param mixed $timestamp
*/
public function setTimestamp($timestamp)
{
$this->timestamp = $timestamp;
}
/**
* #return mixed
*/
public function getWind()
{
return $this->wind;
}
/**
* #param mixed $wind
*/
public function setWind($wind)
{
$this->wind = $wind;
}
/**
* #return mixed
*/
public function getTemperature()
{
return $this->temperature;
}
/**
* #param mixed $temperature
*/
public function setTemperature($temperature)
{
$this->temperature = $temperature;
}
/**
* #return mixed
*/
public function getHumidity()
{
return $this->humidity;
}
/**
* #param mixed $humidity
*/
public function setHumidity($humidity)
{
$this->humidity = $humidity;
}
/**
* #return mixed
*/
public function getRain()
{
return $this->rain;
}
/**
* #param mixed $rain
*/
public function setRain($rain)
{
$this->rain = $rain;
}
/**
* #return mixed
*/
public function getDirViento()
{
return $this->dirViento;
}
/**
* #param mixed $dirViento
*/
public function setDirViento($dirViento)
{
$this->dirViento = $dirViento;
}
/**
* #return mixed
*/
public function getSummary()
{
return $this->summary;
}
/**
* #param mixed $summary
*/
public function setSummary($summary)
{
$this->summary = $summary;
}
/**
* #return mixed
*/
public function getIcon()
{
return $this->icon;
}
/**
* #param mixed $icon
*/
public function setIcon($icon)
{
$this->icon = $icon;
}
public function jsonSerialize()
{
return [
"index" => $this->getIndice(),
"timestamp" => $this->getTimestamp(),
"wind" => $this->getWind(),
"temperature" => $this->getTemperature(),
"humidity" => $this->getHumidity(),
"wind_direction" => $this->getDirViento(),
"rain" => $this->getRain(),
"summary" => $this->getSummary(),
"icon" => $this->getIcon()
];
}
}
The problem here is that the return result is always null.
I have a repository field with this method:
public function getLastAvgTemp()
{
$rsm = new ResultSetMappingBuilder($this->entityManager);
$rsm->addRootEntityFromClassMetadata('MeteoStation', 'm');
$rsm->addFieldResult('m', 'temperatura', 'temperature');
$query = $this->entityManager->createNativeQuery("select avg(m.temperatura) as temperatura from (SELECT temperatura from vinesens.meteodata order by indice desc limit 7) m", $rsm);
$data = $query->getResult();
var_dump($data);
//return $data;
}
The result is
nullarray(1) {
[
0
]=>
NULL
}
I know it has to be something easy, because if I change the query to this one (just retrieving the last 7 data) (I know it would be done differently it's just for the sake of the question)
$rsm = new ResultSetMappingBuilder($this->entityManager);
$rsm->addRootEntityFromClassMetadata('MeteoStation', 'm');
$rsm->addFieldResult('m', 'temperatura', 'temperature');
$query = $this->entityManager->createNativeQuery("select * from (SELECT * from vinesens.meteodata order by indice desc limit 7) m", $rsm);
$data = $query->getResult();
var_dump($data);
Then it returns all my values with the appropriate data.
So I think it has something to do with selecting only one particular column from the table and I don't know how to fix it because if I change the query to this:
$rsm = new ResultSetMappingBuilder($this->entityManager);
$rsm->addRootEntityFromClassMetadata('MeteoStation', 'm');
$rsm->addFieldResult('m', 'temperatura', 'temperature');
$query = $this->entityManager->createNativeQuery("select m.temperatura as temperatura from (SELECT temperatura from vinesens.meteodata order by indice desc limit 7) m", $rsm);
$data = $query->getResult();
var_dump($data);
It returns 7 null objects like so:
array(7) {
[
0
]=>
NULL
[
1
]=>
NULL
[
2
]=>
NULL
[
3
]=>
NULL
[
4
]=>
NULL
[
5
]=>
NULL
[
6
]=>
NULL
}
Please, if you need more information don't hesitate to ask me.
Thank you so much.

Is this the common structure for the domain mapper model?

Hopefully i am asking this on the right stack exchange forum. If not please do let me know and I will ask somewhere else. I have also asked on Code Review, but the community seems a lot less active.
As I have self learned PHP and all programming in general, I have only recently found out about 'Data Mappers' which allows data to be passed into classes without said classes knowing where the data comes from. I have read some of the positives of using mappers and why they make it 'easier' to perform upgrades later down the line, however I am really struggling to find out the reccomended way of using mappers and their layouts in a directory structure.
Let's assume we have a simple application whos purpose is to echo out a first name and last name of a user.
The way I have been using/creating mappers (as well as the file structure is as follows):
index.php
include 'classes/usermapper.php';
include 'classes/user.php';
$user = new User;
$userMapper = new userMapper;
try {
$user->setData([
$userMapper->fetchData([
'username'=>'peter1'
])
]);
} catch (Exception $e) {
die('Error occurred');
}
if ($user->hasData()) {
echo $user->fullName();
}
classes/user.php
class User {
private $_data;
public function __construct() { }
public function setData($userObject = null) {
if (!$userObject) { throw new InvalidArgumentException('No Data Set'); }
$this->_data = $dataObject;
}
public function hasData() {
return (!$this->_data) ? false : true;
}
public function fullName() {
return ucwords($this->_data->firstname.' '.$this->_data->lastname);
}
}
classes/usermapper.php
class userMapper {
private $_db;
public function __construct() { $this->_db = DB::getInstance(); }
public function fetchData($where = null) {
if (!is_array($where)) {
throw new InvalidArgumentException('Invalid Params Supplied');
}
$toFill = null;
foreach($where as $argument=>$value) {
$toFill .= $argument.' = '.$value AND ;
}
$query = sprintf("SELECT * FROM `users` WHERE %s ", substr(rtrim($toFill), 0, -3));
$result = $this->_db->query($query); //assume this is just a call to a database which returns the results of the query
return $result;
}
}
With understanding that the users table contains a username, first name and last name, and also that a lot of sanitizing checks are missing, why are mappers convenient to use?
This is a very long winded way in getting data, and assuming that users aren't everything, but instead orders, payments, tickets, companies and more all have their corresponding mappers, it seems a waste not to create just one mapper and implement it everywhere in each class.
This allows the folder structure to look a whole lot nicer and also means that code isn't repeated as often.
The example mappers looks the same in every case bar the table the data is being pulled from.
Therefore my question is. Is this how data mappers under the 'domain model mappers' should look like, and if not how could my code be improved? Secondly is this model needed in all cases of needing to pull data from a database, regardless of the size of class, or should this model only be used where the user.php class in this case is very large?
Thank you in advance for all help.
The Data Mapper completely separates the domain objects from the persistent storage (database) and provides methods that are specific to domain-level operations. Use it to transfer data from the domain to the database and vice versa. Within a method, a database query is usually executed and the result is then mapped (hydrated) to a domain object or a list of domain objects.
Example:
The base class: Mapper.php
abstract class Mapper
{
protected $db;
public function __construct(PDO $db)
{
$this->db = $db;
}
}
The file: BookMapper.php
class BookMapper extends Mapper
{
public function findAll(): array
{
$sql = "SELECT id, title, price, book_category_id FROM books;";
$statement = $this->db->query($sql);
$items = [];
while ($row = $statement->fetch()) {
$items[] = new BookEntity($row);
}
return $items;
}
public function findByBookCategoryId(int $bookCategoryId): array
{
$sql = "SELECT id, title, price, book_category_id
FROM books
WHERE book_category_id = :book_category_id;";
$statement = $this->db->prepare($sql);
$statement->execute(["book_category_id" => $bookCategoryId]);
$items = [];
while ($row = $statement->fetch()) {
$items[] = new BookEntity($row);
}
return $items;
}
/**
* Get one Book by its ID
*
* #param int $bookId The ID of the book
* #return BookEntity The book
* #throws RuntimeException
*/
public function getById(int $bookId): BookEntity
{
$sql = "SELECT id, title, price, book_category_id FROM books
WHERE id = :id;";
$statement = $this->db->prepare($sql);
if (!$result = $statement->execute(["id" => $bookId])) {
throw new DomainException(sprintf('Book-ID not found: %s', $bookId));
}
return new BookEntity($statement->fetch());
}
public function insert(BookEntity $book): int
{
$sql = "INSERT INTO books SET title=:title, price=:price, book_category_id=:book_category_id";
$statement = $this->db->prepare($sql);
$result = $statement->execute([
'title' => $book->getTitle(),
'price' => $book->getPrice(),
'book_category_id' => $book->getBookCategoryId(),
]);
if (!$result) {
throw new RuntimeException('Could not save record');
}
return (int)$this->db->lastInsertId();
}
}
The file: BookEntity.php
class BookEntity
{
/** #var int|null */
protected $id;
/** #var string|null */
protected $title;
/** #var float|null */
protected $price;
/** #var int|null */
protected $bookCategoryId;
/**
* Accept an array of data matching properties of this class
* and create the class
*
* #param array|null $data The data to use to create
*/
public function __construct(array $data = null)
{
// Hydration (manually)
if (isset($data['id'])) {
$this->setId($data['id']);
}
if (isset($data['title'])) {
$this->setTitle($data['title']);
}
if (isset($data['price'])) {
$this->setPrice($data['price']);
}
if (isset($data['book_category_id'])) {
$this->setBookCategoryId($data['book_category_id']);
}
}
/**
* Get Id.
*
* #return int|null
*/
public function getId(): ?int
{
return $this->id;
}
/**
* Set Id.
*
* #param int|null $id
* #return void
*/
public function setId(?int $id): void
{
$this->id = $id;
}
/**
* Get Title.
*
* #return null|string
*/
public function getTitle(): ?string
{
return $this->title;
}
/**
* Set Title.
*
* #param null|string $title
* #return void
*/
public function setTitle(?string $title): void
{
$this->title = $title;
}
/**
* Get Price.
*
* #return float|null
*/
public function getPrice(): ?float
{
return $this->price;
}
/**
* Set Price.
*
* #param float|null $price
* #return void
*/
public function setPrice(?float $price): void
{
$this->price = $price;
}
/**
* Get BookCategoryId.
*
* #return int|null
*/
public function getBookCategoryId(): ?int
{
return $this->bookCategoryId;
}
/**
* Set BookCategoryId.
*
* #param int|null $bookCategoryId
* #return void
*/
public function setBookCategoryId(?int $bookCategoryId): void
{
$this->bookCategoryId = $bookCategoryId;
}
}
The file: BookCategoryEntity.php
class BookCategoryEntity
{
const FANTASY = 1;
const ADVENTURE = 2;
const COMEDY = 3;
// here you can add the setter and getter methods
}
The table structure: schema.sql
CREATE TABLE `books` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`price` decimal(19,2) DEFAULT NULL,
`book_category_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `book_category_id` (`book_category_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE `book_categories` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
/*Data for the table `book_categories` */
insert into `book_categories`(`id`,`title`) values (1,'Fantasy');
insert into `book_categories`(`id`,`title`) values (2,'Adventure');
insert into `book_categories`(`id`,`title`) values (3,'Comedy');
Usage
// Create the database connection
$host = '127.0.0.1';
$dbname = 'test';
$username = 'root';
$password = '';
$charset = 'utf8';
$collate = 'utf8_unicode_ci';
$dsn = "mysql:host=$host;dbname=$dbname;charset=$charset";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_PERSISTENT => false,
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES $charset COLLATE $collate"
];
$db = new PDO($dsn, $username, $password, $options);
// Create the data mapper instance
$bookMapper = new BookMapper($db);
// Create a new book entity
$book = new BookEntity();
$book->setTitle('Harry Potter');
$book->setPrice(29.99);
$book->setBookCategoryId(BookCategoryEntity::FANTASY);
// Insert the book entity
$bookId = $bookMapper->insert($book);
// Get the saved book
$newBook = $bookMapper->getById($bookId);
var_dump($newBook);
// Find all fantasy books
$fantasyBooks = $bookMapper->findByBookCategoryId(BookCategoryEntity::FANTASY);
var_dump($fantasyBooks);

Laravel model event does not fire

I've made a custom trait in laravel 5.4 so when a pivot table is being ->sync(); it will fire an update event and then I will listen to that with an observer.
However it's not working the way I want.
In my app service provider:
Organisation::observe(OrganisationObserver::class);
There I've got this:
/**
* Listen to the Organisation updated event.
*
* #param Organisation $organisation
* #return void
*/
public function updated(Organisation $organisation)
{
dd('test');
$this->cache->tags(Organisation::class)->flush();
$this->cache->tags(Relation::class)->flush();
}
THis is the trait I use on the model:
<?php
namespace App\Deal\Custom;
trait BelongsToManyWithSyncEvent
{
/**
* Custom belongs to many because by default it does not observes
* changes on pivot tables. When a pivot table is changed return
* a custom belongs to many with sync cache clear class.
* Here we will clear the cache.
*
* #param $related
* #param null $table
* #param null $foreignKey
* #param null $relatedKey
* #param null $relation
* #return BelongsToManyWithSyncCacheClear
*/
public function belongsToMany($related, $table = null, $foreignKey = null, $relatedKey = null, $relation = null)
{
if (is_null($relation)) {
$relation = $this->guessBelongsToManyRelation();
}
$instance = $this->newRelatedInstance($related);
$foreignKey = $foreignKey ?: $this->getForeignKey();
$relatedKey = $relatedKey ?: $instance->getForeignKey();
if (is_null($table)) {
$table = $this->joiningTable($related);
}
return new BelongsToManyWithSyncCacheClear(
$instance->newQuery(), $this, $table, $foreignKey, $relatedKey, $relation
);
}
}
The BelongsToManyWithSyncCacheClear class looks like this:
<?php
namespace App\Deal\Custom;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Concerns\HasEvents;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class BelongsToManyWithSyncCacheClear extends BelongsToMany
{
use HasEvents;
/**
* BelongsToManyWithSyncEvents constructor.
*
* #param Builder $query
* #param Model $parent
* #param string $table
* #param string $foreignKey
* #param string $relatedKey
* #param null $relationName
*/
public function __construct(Builder $query, Model $parent, $table, $foreignKey, $relatedKey, $relationName = null)
{
parent::__construct($query, $parent, $table, $foreignKey, $relatedKey, $relationName);
}
/**
* When a pivot table is being synced,
* we will clear the cache.
*
* #param array|\Illuminate\Database\Eloquent\Collection|\Illuminate\Support\Collection $ids
* #param bool $detaching
* #return array
*/
public function sync($ids, $detaching = true)
{
$changes = parent::sync($ids, $detaching);
$this->fireModelEvent('updated', false);
return $changes;
}
}
Here I fire an updated event:
$this->fireModelEvent('updated', false);
But the dd('test'); in my OrganisationObserver is never triggered. What am I doing wrong here!?

dbUnit: possible to create database out of a dataset?

Is it possible to create the in memory database structure with a DataSet and not using SQL to create the structure?
Currently I have this code, which works. Now I would like to replace the initTable function.
class QueryTest extends \PHPUnit_Extensions_Database_TestCase
{
// only instantiate pdo once for test clean-up/fixture load
static private $pdo = null;
// only instantiate PHPUnit_Extensions_Database_DB_IDatabaseConnection once per test
private $conn = null;
// create connection
public function getConnection()
{
if ($this->conn === null) {
if (self::$pdo == null) {
self::$pdo = new \PDO('sqlite::memory:');
}
$this->conn = $this->createDefaultDBConnection(self::$pdo, "db");
self::initTable();
}
return $this->conn;
}
protected function setUp(){
parent::setUp();
//$this->pdo->query('CREATE TABLE `test`;');
}
public function initTable()
{
$query = "
CREATE TABLE IF NOT EXISTS `guestbook` (
id INT PRIMARY KEY,
content VARCHAR(50) NOT NULL DEFAULT '',
user VARCHAR(20) NOT NULL DEFAULT'',
created VARCHAR(20) NOT NULL DEFAULT ''
)
";
static::$pdo->query($query);
}
public function getDataSet(){
return $this->createXMLDataSet(dirname(__FILE__) . '/../../_files/database.xml');
}
public function testData()
{
$guestbook = new Guestbook();
$guestbook->addEntry("suzy", "Hello world!", self::$pdo);
$queryTable = $this->getConnection()->createQueryTable(
'guestbook', 'SELECT id, content, user FROM guestbook'
);
$expectedTable = $this->createFlatXmlDataSet(dirname(__FILE__) . '/../../_files/expected.xml')
->getTable("guestbook");
$this->assertTablesEqual($expectedTable, $queryTable);
}
}
class Guestbook
{
public function addEntry($name, $message, $pdo)
{
$pdo->query('INSERT INTO `guestbook` (`id`, `content`, `user`) VALUES (3, "' . $message . '", "' . $name . '");');
}
}
I could solve my problem by using this implementation for the unit test.
class QueryPlannerTest extends \PHPUnit_Extensions_Database_TestCase
{
/**
* #var type \PDO
*/
static private $pdo = null;
/**
* #var \PHPUnit_Extensions_Database_DB_IDatabaseConnection
*/
private $conn = null;
/**
* #var QueryPlanner
*/
protected $testObject;
/**
* Connects to in-memory database and retuns a connection.
*
* #return \PHPUnit_Extensions_Database_DB_IDatabaseConnection
*/
public function getConnection()
{
if ($this->conn === null) {
if (self::$pdo == null) {
self::$pdo = new \PDO('sqlite::memory:');
}
$this->conn = $this->createDefaultDBConnection(self::$pdo, "db");
// register the pdo object in the singleton class
$db = Database::getInstance(self::$pdo);
self::initDatabase();
}
return $this->conn;
}
/**
* Returns a dataset for the current tests.
*
* #return \XmlDataSet
*/
public function getDataSet()
{
return $this->createXMLDataSet(dirname(__FILE__) . '/../../datasource/QueryPlanerTestDatabase.xml');
}
/**
* Initializes the in-memory database.
*/
public static function initDatabase()
{
$query = "CREATE TABLE IF NOT EXISTS `user` (
`iduser` INT UNSIGNED,
`email` VARCHAR(100),
`password` VARCHAR(60),
`salt` VARCHAR(22),
`firstname` VARCHAR(150),
`lastname` VARCHAR(150),
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`activated` TINYINT(1) NOT NULL DEFAULT '0',
`activation_code` VARCHAR(10),
`banned` TINYINT(1) NOT NULL DEFAULT '0')";
self::$pdo->query($query);
}
/**
* Resets the database after each test case.
*
* #return \PHPUnit_Extensions_Database_Operation_Truncate
*/
protected function getTearDownOperation()
{
return \PHPUnit_Extensions_Database_Operation_Factory::TRUNCATE();
}
If you use this to set up your unit test you can simply assert the result of a query with your dataset.

What is the second parameter doing in model("pagemodel", 'pages')

I have following model and controller. And database table pages has id, title, content and slug.
Q1. Why does the line in controller,
$this->load->model("pagemodel", 'pages'); // Load the page model,
have 'pages'?
Is it naming "pagemodel" as pages?
And I see a line,
$page_data = $this->pages->fetch($page_slug); // Pull the page data from the database
using pages.
Model code
<?php
class Pagemodel extends Model
{
/**
* Constructor
*/
function Pagemodel()
{
parent::Model();
}
/**
* Return an array of pages — used in the manage view
*
* #access public
* #return array
*/
function pages()
{
$query = $this->db->query("SELECT * FROM `pages` ORDER BY `id` ASC");
return $query->result_array();
}
/**
* Return a list of a single page — used when editing a page
*
* #access public
* #param integer
* #return array
*/
function page($id)
{
$query = $this->db->query("SELECT * FROM `pages` WHERE `id` = '$id' LIMIT 1");
return $query->result_array();
}
/**
* Return an array of a page — used in the front end
*
* #access public
* #param string
* #return array
*/
function fetch($slug)
{
$query = $this->db->query("SELECT * FROM `pages` WHERE `slug` = '$slug'");
return $query->result_array();
}
/**
* Add a record to the database
*
* #access public
* #param array
*/
function add($data)
{
$this->db->query("INSERT INTO `pages` (`title`, `content`, `slug`) VALUES (".$this->db->$data['title'].", ".$this->db->$data['content'].", ".$this->db->escape($data['slug']).")");
}
/**
* Update a record in the database
*
* #access public
* #param integer
* #param array
*/
function edit($id, $data)
{
$this->db->query("UPDATE `pages` SET `title` = ".$this->db->escape($data['title']).", `content` = ".$this->db->escape($data['content']).", `slug` = ".$this->db->escape($data['slug'])." WHERE `id` = '$id'");
}
/**
* Remove a record from the database
*
* #access public
* #param integer
*/
function delete($id)
{
$this->db->query("DELETE FROM `pages` WHERE `id` = '$id'");
}
}
?>
Controller code
<?php
class Page extends Application
{
function Page()
{
parent::Application();
$this->load->model("pagemodel", 'pages'); // Load the page model
}
function _view($page, $page_data)
{
$meta = array (
'meta_title' => 'title here',
'meta_keywords' => 'keywords here',
'meta_description' => 'description here',
'meta_robots' => 'all',
'extra_headers' => '
<script language="javascript" type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.0/jquery.min.js"></script>
'
);
$data = array();
// add any data
// merge meta and data
$data = array_merge($data,$meta);
// load view with data
$this->load->view('header', $data);
$this->load->view($page, $page_data);
$this->load->view('footer');
}
function index()
{
$page_slug = $this->uri->segment('2'); // Grab the URI segment
if($page_slug === FALSE)
{
$page_slug = 'home'; //Change home if you change the name in the back-end
}
$page_data = $this->pages->fetch($page_slug); // Pull the page data from the database
if($page_data === FALSE)
{
show_404(); // Show a 404 if no page exists
}
else
{
$this->_view('index', $page_data[0]);
}
}
}
?>
From system/libraries/Loader.php
/**
* Model Loader
*
* This function lets users load and instantiate models.
*
* #access public
* #param string the name of the class
* #param string name for the model
* #param bool database connection
* #return void
*/
function model($model, $name = '', $db_conn = FALSE)
{
...
So yes, the second parameter is just setting a 'friendly' name for the model, and is not needed. It adds a layer of confusion in my opinion.
You are probably right, try renaming 'pages' and I bet it's not available anymore by calling
$this->pages in your controller code.

Categories