PHP Laravel are DB function results cached? - php

Noob question. I'm working on a project which involves interacting with some legacy software and the database is not compatible with regular Laravel Relationships.
If I'm defining things in a constructor like this:
public function __construct(array $attributes = array())
{
parent::__construct($attributes);
$this->vatpercentage = $this->customer()->vatpercentage;
$this->vatcode = $this->customer()->vatcode;
$this->partrevision = $this->part()->revision;
$this->unitprice = $this->part()->unitprice;
}
public function part(){
return Part::findOrFail($this->partnum);
}
public function customer(){
$traderid = Order::where('id', $this->orderid)->value('traderid');
return Customer::where('id', $traderid)->where('tradertype', 'C')->first();
}
I need to reference customer(), part() and other similar functions many times in the constructor. Is the DB getting queried every time I reference $this->customer(), etc or is the result cached the first time I reference it and then used for all the other times following? Basically am I doing a lot of unnecessary DB calls by coding in this way rather than setting $this->customer = $this->customer() and grabbing the values like $this->customer->example?

No database query or method call is going to be cached automatically in your application, nor should it. Laravel and PHP aren't going to know how you want to use queries or methods.
Everytime you call customer(), you're building up and executing a new query. You could easily cache the result in a property if that's what you want, but you'd have watch the value of the $orderid property:
protected $customerCache;
public function customer()
{
if ($customerCache) return $customerCache;
$traderid = Order::where('id', $this->orderid)->value('traderid');
return $customerCache = Customer::where('id', $traderid)->where('tradertype', 'C')->first();
}
You're also performing too much in your constructor. I would highly recommend not performing queries in any constructors, constructors should be used to pass dependencies. The way you have it designed would make it very hard to unit test.

In Laravel 4.* there was a remember() method handling the cache in queries. It was removed from 5.1 with a valid reason that it's not the responsibility of Eloquent neither the query Builder to handle cache. A very very simplified version of a decorator class that could handle the caching of your queries:
final class CacheableQueryDecorator
{
private $remember = 60; //time in minutes, switch to load from config or add a setter
private $query = null;
public function __construct(Builder $builder)
{
$this->query = $builder;
}
private function getCacheKey(string $prefix = ''):string
{
return md5($prefix . $this->query->toSql() . implode(',', $this->query->getBindings()));
}
public function __call($name, $arguments)
{
$cache = Cache::get($this->getCacheKey($name), null);
if ($cache) {
return $cache;
}
$res = call_user_func_array([$this->query, $name], $arguments);
Cache::put($this->getCacheKey($name), $res, $this->remember);
return $res;
}
}
Using it:
$results = (new CacheableQueryDecorator($query))->get()

Related

How to avoid internal calling function when running PHPUnit test? And how to set mock data for internal performance?

I have a class Receipt.php
<?php
namespace TDD;
class Receipt {
private $user_id = 1;
private $pending_amount;
public function total(array $items = []){
$items[] = $this->pending();
return array_sum($items);
}
public function tax($amount,$tax){
return $amount * $tax;
}
private function pending()
{
$sql = 'select pending_amount from Pending_transtions where user_id =' . $this->user_id . ' limit 1;';
//$pending_amt = $this->mainDb->get_sql_row($sql);
//$this->pending = $pending_amt['pending_amount'];
return $this->pending_amount = 45;
}
public function addTaxPending($tax){
return $this->pending_amount * $tax;
}
}?>
And in my PHPUnit file, ReceiptTest.php
<?php
namespace TDD\Test;
require(dirname(dirname(__FILE__))).DIRECTORY_SEPARATOR.'vendor'.DIRECTORY_SEPARATOR.'autoload.php';
use PHPUnit\Framework\TestCase;
use TDD\Receipt;
class ReceiptTest extends TestCase{
public function setUp(): void {
$this->Receipt = new Receipt();
}
public function tearDown(): void{
unset($this->Receipt);
}
public function testTotal(){
$input = [0,2,5,8];
$output = $this->Receipt->total($input);
$this->assertEquals(15,$output,"this is not valid");
}
public function testTax(){
$inputAmount = 10.00;
$inputTax = 0.10;
$output = $this->Receipt->tax($inputAmount,$inputTax);
$this->assertEquals(1.0,$output,"this tax expecting 1.0");
}
}?>
question:
How to ignore internal calling function pending() because it fetches data from DB. At the same time I want to access the property of $this->pending_amount.
Here Pending() must be private function.
How can I achieve that? I am looking for your valuable solutions
Proper solution is to replace your dependency (the one which is saved under $this->mainDb in your example) with a "mocked" one in your test case.
Here is an article from PHPUnit manual, which shows how to create mocks - https://phpunit.readthedocs.io/en/9.5/test-doubles.html#mock-objects
--
Speaking about ways of injection: your can either pass $this->mainDb instance via class constructor, or make so-called "seam" in form of public setMainDb method (which is not an elegant solution - I'd prefer to avoid it).
Another thing which I had to do sometimes, is to replace the value via Reflection: make private property accessible and set it inside of test to the value I need.
--
Update:
Based on given example, I think the easiest way to achieve desired result is:
Change test case's setUp to:
public function setUp(): void
{
$this->Receipt = new Receipt();
$mainDbMock = new class() {
public function get_sql_row() {
return [
"pending_amount" => 0
];
}
};
$this->Receipt->setMainDb($mainDbMock);
}
Add "seam"-method to your Receipt class:
public function setMainDb($mainDb)
{
$this->mainDb = $mainDb;
}
You need to mock the dependency. The dependency in your case is the database. You need to replace access to the database with a mock that you can configure with known return values.
Take the time to read the PHPUnit documentation on how to use mock objects.
A basic example
Given a DB access class with a get_sql_row method that accepts an SQL string and returns some data structure, we don't need to fill in any code here in our example because we're going to mock the get_sql_row method and configure it to return known values for our test.
class MainDb
{
public function get_sql_row(string $sql)
{
// ...
}
}
Our receipt class accepts a DB access object in the constructor and uses it to execute SQL e.g. the pending method executes SQL to get the pending amount (if any).
class Receipt
{
public function __construct($mainDb)
{
$this->mainDb = $mainDb;
}
public function total(array $items = [])
{
$items[] = $this->pending();
return array_sum($items);
}
private function pending()
{
$pendingAmount = $this->mainDb->get_sql_row('sql...');
return $pendingAmount['pending_amount'];
}
}
In the testcase setup method we create a mock of the DB access class and create the receipt object with the mocked database. The mock is then used in the tests to configure expectations, received arguments, and return values.
The testTotalWithNoPendingAmount test configures the DB mock to return 0 when get_sql_row is invoked. The get_sql_row is only expected to be called once and will fail otherwise. Configuring get_sql_row to return a known value allows us to test the behaviour of the receipt class without touching the DB.
Similarly, testTotalWithPendingAmount configures the DB mock get_sql_row to return a value other than 0 so that we can test behaviour of calculating the total when there is a pending amount.
final class ReceiptTest extends TestCase
{
protected Receipt $receipt;
protected function setUp(): void
{
// create a mock of the database access object
$this->dbMock = $this->createMock(MainDb::class);
// create receipt with mocked database
$this->receipt = new Receipt($this->dbMock);
}
public function testTotalWithNoPendingAmount(): void
{
$this->dbMock->expects($this->once())
->method('get_sql_row')
->willReturn(['pending_amount' => 0]);
$this->assertEquals(15, $this->receipt->total([0, 2, 5, 8]));
}
public function testTotalWithPendingAmount(): void
{
$this->dbMock->expects($this->once())
->method('get_sql_row')
->willReturn(['pending_amount' => 3]);
$this->assertEquals(18, $this->receipt->total([0, 2, 5, 8]));
}
}
Take the time to read the PHPUnit documentation on using mock objects and understand how they work and how to use them in your testing.
Note also that your handling of SQL is potentially open to SQL injection. Please read about SQL injection.

mocking mongodb with phpunit

I have a save function for create new document with mongoDB
public function save(User $user): User
{
$result = $this->usersCollection->insertOne($user->getUser());
$user->setId($result->getInsertedId());
return $user;
}
And Change __construct for implemet test
public function __construct($db = null)
{
if (is_null($db)) {
parent::__construct();
} else {
$this->db = $db;
}
$this->usersCollection = $this->db->users;
}
I write this test for save function
public function testSave()
{
$mongo = \Mockery::mock('MongoDB\Client');
$mongo->shouldReceive('insertOne')->andReturn("ok");
$mongo->shouldReceive('selectDatabase')->andReturnSelf();
$user = new User('jack', '0015005050');
$um = new UserMongoDB($mongo);
$res = $um->save($user);
}
everything works well but my problem is $result->getInsertedId() How to I can Mock this function?
Error : Call to a member function getInsertedId() on string
The return type of the insertOne method must be an instance of InsertOneResult (see docs). At the moment you are returning the string "ok". You could keep going and make insertOne return a mock of InsertOneResult. This may work but you are at the gate to mocking hell. Personally, I'd write integration tests for the save method. Mocking the save method in other unit tests is way easier than mocking the low level MongoDB stuff all over the place.

Understanding models in a PHP MVC - splitting in multiple classes?

I am reading a lot of articles of PHP MVC use lately and found out I was programming my CMS all wrong...
Apparently the model-layer of an MVC contains multiple class instances to pass on data.
What I can't seem to figure out is how to apply this technique to my case. I hope you can help me shed light to the case:
class site_model {
private $iSite_id;
private $sSite_name;
//+ 8 other variables
function __construct($iSite_id = null, $sSite_name = null) {
$this->iSite_id= $iSite_id;
$this->sSite_name = $sSite_name;
}
public function get_all_sites() {
$aList = [];
$sSQL = 'SELECT * FROM sites WHERE 1';
$mReturn = mysql_query($sSQL);
while($aRow = mysql_fetch_assoc($mResult)) {
$aList[] = new site_model($aRow['site_id'], $['site_name']);
}
return $aList;
}
//+ 20 other query functions
}
class site_controller {
private $oSiteModel;
public function return_all_sites() {
$oSiteModel = new site_model();
return $oSiteModel->get_all_sites();
}
}
What I am worried about is when I create an array of model objects that contain a lot of db-functions (20+), and I don't even need most of the functions, I am loading a lot of useless data into the memory? I have no idea how much extra memory that is, but if I can avoid it, I think I should?
A few questions:
Is it good practice to store all the query-functions into the model?
If I load multiple instances of the model object, does the memory load in all these db-functions per instance or do they share the same functions stored in memory?
[I ask this because it's important to me to keep performance up, and I don't want to consume unnecessary resources]
Is using a separate db_model class the right way to go? With this I mean a model (data) just for setting and getting data and applying validations, and use another model(db) to contain all db functions and let the controller call the right db functions to set the data-model?
Ia it good practice to use a db-API class to execute the functions with data retrieved from models instead of the (db)model for better encapsulation and code reuse?
Like:
interface DatabaseAdapter
{
public function connect();
public function disconnect();
public function prepare($sql, array $options = array());
public function execute(array $parameters = array());
public function fetch($fetchStyle = null,
$cursorOrientation = null, $cursorOffset = null);
public function fetchAll($fetchStyle = null, $column = 0);
public function select($table, array $bind,
$boolOperator = "AND");
public function insert($table, array $bind);
public function update($table, array $bind, $where = "");
public function delete($table, $where = "");
//.. extra functions here for custom queries like joins etc..
}
Thank you in advance for helping me become a better programmer :)

zf2 - creating models (with dependencies) in mapper without servicemanager

Following from my previous post about removing ServiceLocatorAwareInterface's from my zf2 app, i am now faced with a puzzle involving object creation when using data mappers.
The current implementation of my data mapper uses a tablegateway to find specific rows, calls the service manager to obtain a domain object, then populates and returns the full object.
public function findById($userId){
$rowset = $this->gateway->select(array('id' => $userId));
$row = $rowset->current();
if (!$row) {
throw new \DomainException("Could not find user with id of $userId in the database");
}
$user = $this->createUser($row);
return $user;
}
public function createUser($data){
$userModel = $this->getServiceManager()->get('Model\User');
$hydrator = $this->getHydrator();
if($data instanceof \ArrayObject){
$hydrator->hydrate($data->getArrayCopy(), $userModel);
}else{
$hydrator->hydrate($data, $userModel);
}
return $userModel;
}
The model needs to be called from the service manager because it has other dependencies, so calling $user = new App\Model\User() from within the mapper is not an option.
However, now i am removing instances of the servicemanager from my code, i am unsure of the best way to get the model into the mapper. The obvious answer is to pass it in the constructor and save the instance as a property of the mapper:
public function __construct(TableGateway $gateway, \App\Model\User $userModel){
$this->_gateway = $gateway;
$this->_userModel= $userModel;
}
public function createUser($data){
$userModel = $this->_userModel;
//....snip....
}
This works to a degree, but then multiple calls to createUser (such as when finding all users, for instance) over writes each instance with the last objects data (as to be expected, but not what i want)
So i need a "new" object returned each time i call createUser, but the dependency being passed into the constructor. With the model passed into the constructor I can clone the object eg.
public function createUser($data){
$userModel = clone $this->_userModel
//....snip....
}
...but something about it doesn't seem right, code smell?
You are right, it doesn't smell good.
Designing an ORM isn't easy. There is and probably always will be discussion about the way an ORM should be designed. Now, when I'm trying to understand your design I noticed you are pointing out that your models contain the data but also have "other" dependencies. This is wrong, the models containing your data should work without any layer in your application.
Entities should work without the ORM
In my opinion you should separate your business logic (dependencies) from your data. This will have many advantages:
More expressive
Easier to test
Less coupling
More flexible
Easier to refactor
For more information about how to design your ORM layer I highly recommend browsing through these slides.
DataMaper
Lets make the UserMapper responsible for separating the in-memory objects (containing only data) from the database.
class UserMapper
{
protected $gateway;
protected $hydrator;
public function __construct(TableGateway $gateway, HydratorInterface $hydrator)
{
$this->gateway = $gateway;
$this->hydrator = $hydrator;
}
public function findOneById($id)
{
$rowset = $this->_gateway->select(array('id' => $id));
$row = $rowset->current();
if(!$row) {
throw new \DomainException("Could not find user with id of $id in the database.");
}
$user = new User;
$this->hydrator->hydrate($row, $user);
return $user;
}
public function findManyBy(array $criteria)
{
// $criteria would be array('colum_name' => 'value')
}
public function save(User $user)
{
$data = $this->hydrator->extract($user);
// ... and save it using the $gateway.
}
}
For more information about the responsibility of data mappers check out Martin Fowler's definition.
Buniness Logic
It's recommended not to place any model related business logic directly into the Controller. Therefor lets just create a simple UserService which will handle validation. If your fond of form objects you could also use Zend\Form\Form in this process.
class UserService
{
protected $inputFilter;
protected $hydrator;
public function __construct(InputFilter $inputFilter, HydratorInterface $hydrator)
{
$this->inputFilter = $inputFilter;
$this->hydrator = $hydrator;
}
protected function validate(array $data)
{
// Use the input filter to validate the data;
}
public function createUser(array $data)
{
$validData = $this->validate($data);
$user = new User;
$this->hydrator->hydrate($validData, $user);
return $user;
}
}
Data Object
Now lets make the objects containing the data Plain Old PHP Objects, not bound by any restriction. This means they are not coupled with any logic and we could use them anywhere. For instance if we decide to replace our ORM with another like Doctrine.
class User
{
protected $name;
public function setName($name)
{
$this->name = $name;
}
public function getName()
{
return $this->name;
}
}
More information about the concept of Plain Old PHP Objects can be found on Wikipedia's explanation of POJO.

PhalconPHP model

I have two questions related to models in Phalcon, that I am struggling to find an answer for:
How do I access the Dependecy Injector in a model?
Is it plausible to create a model that is not binded to a database table? If it isn't then where should I put logic that doesn't need to be stored (some functions to work with an API)?
You can access the Di from anywhere in the code by using the getDefault() function
$di = \Phalcon\DI\FactoryDefault::getDefault();
You can extend the Phalcon model to expose certain functionality and extend your models using that one. For instance consider the following model that offers a bit more functionality (you can always extend it as you wish. In the example below I am showing how to use the builder to construct your queries and also a function that can be used to fetch a schema for a particular model.
class MyModel extends \Phalcon\Mvc\Model
{
protected static function di()
{
return \Phalcon\DI\FactoryDefault::getDefault();
}
public static function fetchSchema()
{
return "schema generators";
}
public static function fetchById($id)
{
$results = null;
try {
$builder = self::getBuilder();
$field = 'id';
$bind[$field] = $id;
$builder->where("{$field} = :{$field}:");
$query = $builder->getQuery();
// One record is needed here so set the unique row
$query->setUniqueRow(true);
// Execute!
$results[] = $query->execute($bind);
} catch (\Exception $e) {
$results = self::exceptionToArray($e);
}
return $results;
}
protected static function getBuilder()
{
$di = self::di();
$manager = $di['modelsManager'];
$builder = $manager->createBuilder();
$builder->from(get_called_class());
return $builder;
}
protected static function execute($builder, $bind, $unique = false)
{
$query = $builder->getQuery();
// One record is needed here so set the unique row
$query->setUniqueRow($unique);
// Execute!
$results = $query->execute($bind);
if (!$results || count($results) == 0) {
$results = array();
}
return $results;
}
protected static function exceptionToArray($exception)
{
$results['error'] = array(
'code' => $exception->getCode(),
'file' => $exception->getFile(),
'line' => $exception->getLine(),
'message' => $exception->getMessage(),
'trace' => $exception->getTrace(),
'trace_as_string' => $exception->getTraceAsString()
);
return $results;
}
}
I don't know about this particular framework but it's definitely a bad idea to access a dependency injection container inside application code. A dependency injection container should only exist at the top level of the application. It should create the model and pass it fully constructed dependencies for everything it needs.
1) it very tightly couples your application logic to the framework which isn't good because your code is not portable
2) It gives your application code access to every possible dependency ever. This means it doesn't have a clear API. What dependencies does the code actually have? You can't answer this without looking through the code and seeing what it's fetching from the DI container. see: http://misko.hevery.com/code-reviewers-guide/flaw-digging-into-collaborators/

Categories