What is the right way to chain custom repository methods in order to create one composed query that returns the entities based on multiple conditions.
Here is what I did:
use Doctrine\ORM\EntityRepository;
class ingredientRepository extends EntityRepository
{
private $query;
public function initNewQuery()
{
$this->query = $this->createQueryBuilder('i')->join('i.product', 'p');
return $this;
}
public function clearQuery()
{
$this->query = null;
return $this;
}
public function getResult(){
$r = $this->query->getQuery()->getResult();
$this->clearQuery();
return $r;
}
public function filterByProductName( $productName )
{
if(!$this->query) $this->initNewQuery();
$this->query->andWhere('p.name LIKE :name')->setParameter('name', '%'.$productName.'%');
return $this;
}
public function filterByMinContenu( $contenu )
{
if(!$this->query) $this->initNewQuery();
$this->query->andWhere('p.contenu > :contenu')->setParameter('contenu', $contenu);
return $this;
}
public function filterByType( $type ){
if(!$this->query) $this->initNewQuery();
$this->query->andWhere('i.type = :type')->setParameter('type', $type);
return $this;
}
}
This allows me to do things like:
$ingredients = $em->getRepository('ingredient')->initNewQuery()->filterByType(4)->getResult();
$ingredients = $em->getRepository('ingredient')->initNewQuery()->filterByProductName('ell')->filterByMinContenu(10)->getResult();
Which is exactly what I want !
But ... I don't really like it.
First, this is not how the default methods like findBy and so on work. These all return the results directly. So that doesn't seem right to mix those two behaviours.
And secondly, when calling only the filterByType method and then return the results, the query will use a join that it doesn't need. I guess I could find a way to do the join only if I need it, but again, doesn't seem right.
Any ideas on how to do this clean an simple ?
Fast answer without testing:)
use Doctrine\ORM\EntityRepository;
class ingredientRepository extends EntityRepository
{
private $query;
private function initNewQuery()
{
$this->query = $this->createQueryBuilder('i');
}
public function findBy($filterArray)
{
$this->initNewQuery();
if (array_key_exists('productName', $filterArray)) {
$this->filterByProductName($filterArray['productName']);
}
if (array_key_exists('minContenu', $filterArray)) {
$this->filterByMinContenu($filterArray['minContenu']);
}
if (array_key_exists('type', $filterArray)) {
$this->filterByType($filterArray['type']);
}
return $this->query->getQuery()->getResult();
}
private function filterByProductName($productName)
{
$this->query->join('i.product', 'p');
$this->query->andWhere('p.name LIKE :name')->setParameter('name', '%'.$productName.'%');
}
private function filterByMinContenu($contenu)
{
$this->query->andWhere('p.contenu > :contenu')->setParameter('contenu', $contenu);
}
private function filterByType($type)
{
$this->query->andWhere('i.type = :type')->setParameter('type', $type);
}
}
call:
$ingredients = $em->getRepository('ingredient')->findBy(['type'=>4]);
$ingredients = $em->getRepository('ingredient')->findBy(['productName'=>'ell', 'minContenu' => 10]);
Related
I'm trying to create a class function which resembles how we used to fetch database listing and convert into a dropdown listing.
eg: DB::table()->where()->get()
what i would like to achieve in laravel custom class or through model is this
Dropdown::fetch()->toArray()
Dropdown::fetch()->toDropdown()
I tried to figure out how this can be done through google. But couldn't find any solution to it.
I'm using laravel 5.8
--
Edit - Sample Code added
Code tried:
namespace App\Http\Models;
use DB;
use Closure;
use BadMethodCallException;
use Illuminate\Support\Traits\Macroable;
use Illuminate\Database\Eloquent\Model;
class Dropdown extends Model
{
private $result = [];
private $default;
public function _cities(){
$tbl_cities = config("tables.TBL_meta_cities");
$result = DB::table($tbl_cities)->select('id', 'cityname')
->orderBy('id')->get()->toArray();
$this->result = $result;
}
public function _select(){
}
public function _list(){
return $this->result;
}
public function _setDefault($def=''){
}
public static function __callStatic($method, $parameters)
{
$action = '_'.$method;
if(method_exists(get_called_class(), $action))
self::$action(...$parameters);
else echo 'not found';
}
public function __call($method, $parameters)
{
$action = '_'.$method;
if(method_exists($get_called_class(), $action))
self::$action(...$parameters);
else echo 'not found';
}
}
and i tried
Dropdown::cities()->list()
but ended with bugs
Well i figured it out myself.
class Dropdown extends Model
{
private static $result = [];
private function getCities(){
$result = City::select('id', 'cityname')
->orderBy('id')->get()->toArray();
self::$result = $result;
}
public function toArray(){
return self::$result;
}
public function toDropdown(){
// Do the dropdown works
}
/**
* Dynamically handle calls to the class.
*
* #param string $method
* #param array $parameters
* #return mixed
*
* #throws \BadMethodCallException
*/
public function __callMethod($method, $parameters){
// Check with inclusive
$class = get_called_class();
$avail = false;
$action = '';
// Check method availability - direct
if(!$avail){
$action = $method;
$avail = method_exists($class, $action);
}
// Check method 2
if(!$avail){
$action = 'get'.ucwords($method);
$avail = method_exists($class, $action);
}
if($avail){
// Call the method
$return = self::$action(...$parameters);
if(!empty($return)) return $return;
} else {
// Throw error if method not found
throw new BadMethodCallException("No such method exists: $name");
}
return new self;
}
public static function __callStatic($method, $parameters){
return (new self)->__callMethod($method, $parameters);
}
public function __call($method, $parameters){
return (new self)->__callMethod($method, $parameters);
}
}
All i need to do is return new self which does the trick instead of return $this so that the trailing function can be called easily.
Now i can able to call that function like this
Dropdown::cities()->toArray();
Reference:
https://stackoverflow.com/a/41631711/1156493
Thank you #Joseph for your time & support.
I'm writing PHPUnit Test with Mockery, (PHP v5.6.32, PHPUnit 3.7.21, Mockery dev-master) and found something which I can't understand about using Mockery::mock and Mockery::namedMocks.
My code is below, and the questions are:
Am I correct to use in LegendTest.php the Mockery::namedMock() instead of Mockery::mock() for SignalsCollection object?
Regarding to documentation about namedMock, I expect that frist argument is the Class name (SignalsCollection) and the second argument should be the extends statement (\ArrayObject) - but in my case I'm getting an error: Mockery\Exception\BadMethodCallException : Received Charts\SignalsCollection::getIterator(), but no expectations were specified, so I'm giving only one argument and this works fine. Why? What am I doing wrong? I'm confused.
Did I missed something in this test case or should I do something different to make tests better?
Signal.php:
class Signal
{
protected $id = 0;
protected $colName = '';
protected $tableName = '';
public function getId()
{
return $this->id;
}
public function setColName($colName)
{
$this->colName = $colName;
return $this;
}
public function setTableName($tableName)
{
$this->tableName = $tableName;
return $this;
}
}
SignalsCollection.php:
class SignalsCollection extends \ArrayObject
{
}
Legend.php
class Legend
{
protected $signalsCollection = null;
protected $graphModel = null;
public function __construct(SignalsCollection $signalsCollection, GraphModel $graphModel)
{
$this->signalsCollection = $signalsCollection;
$this->graphModel = $graphModel;
}
public function getSignalsCollection()
{
return $this->signalsCollection;
}
public function removeSignal(Signal $signal)
{
foreach ($this->signalsCollection as $key => $item) {
if ($item->getId() === $signal->getId()) {
$this->signalsCollection->offsetUnset($key);
break;
}
}
}
}
LegendTest.php:
class LegendTest extends \PHPUnit_Framework_TestCase
{
protected function tearDown()
{
parent::tearDown();
Mockery::close();
}
public function testRemoveSignal()
{
$testSignal = Mockery::mock('\Charts\Signal')
->shouldReceive('setColName', 'setTableName')
->andReturn(Mockery::self())
->mock();
$testSignal
->setColName('testColumnName')
->setTableName('testTableName');
$testSignalSecond = Mockery::mock('\Charts\Signal')
->shouldReceive('setId', 'setColName', 'setTableName')
->andReturn(Mockery::self())
->mock();
$testSignalSecond
->setId(1)
->setColName('testColumnName')
->setTableName('testTableName');
$signalsCollection = Mockery::namedMock('\Charts\SignalsCollection')
->shouldReceive('append', 'offsetUnset')
->andReturn(Mockery::self())
->mock();
$signalsCollection
->append($testSignal)
->append($testSignalSecond);
$legend = new Legend($signalsCollection, Mockery::mock('\Charts\GraphModel'));
$this->assertEquals($signalsCollection, $legend->getSignalsCollection());
$legend->removeSignal($testSignalSecond);
$signalsCollection->offsetUnset(1);
$this->assertEquals( $signalsCollection, $legend->getSignalsCollection() );
}
}
I want to be able to use an object like below, to retrieve new orders and new invoices. I feel like it is most readable, but I am having trouble writing the PHP class to work this way.
$amazon = new Amazon();
$amazon->orders('New')->get();
$amazon->invoices('New')->get();
In my PHP class, how would my get() method be able to distinguish whether to return orders or invoices?
<?php
namespace App\Vendors;
class Amazon
{
private $api_key;
public $orders;
public $invoices;
public function __construct()
{
$this->api_key = config('api.key.amazon');
}
public function orders($status = null)
{
$this->orders = 'orders123';
return $this;
}
public function invoices($status = null)
{
$this->invoices = 'invoices123';
return $this;
}
public function get()
{
// what is the best way to return order or invoice property
// when method is chained?
}
}
A couple of ways, if you want it dynamic and don't do any logic in the methods, use something like __call
<?php
class Amazon {
public $type;
public $method;
public function get()
{
// do logic
// ...
return 'Fetching: '.$this->method.' ['.$this->type.']';
}
public function __call($method, $type)
{
$this->method = $method;
$this->type = $type[0];
return $this;
}
}
$amazon = new Amazon();
echo $amazon->orders('New')->get();
echo $amazon->invoices('New')->get();
If you want to do logic in the methods, do something like:
<?php
class Amazon {
public $type;
public $method;
public function get()
{
return 'Fetching: '.$this->method.' ['.$this->type.']';
}
public function orders($type)
{
$this->method = 'orders';
$this->type = $type;
// do logic
// ...
return $this;
}
public function invoices($type)
{
$this->method = 'invoices';
$this->type = $type;
// do logic
// ...
return $this;
}
}
$amazon = new Amazon();
echo $amazon->orders('New')->get();
echo $amazon->invoices('New')->get();
As orders and invoices are set methods, I would suggest to do as follows:
public function get(array $elements)
{
$result = [];
foreach($elements as $element) {
$result[$element] = $this->$element;
}
return $result;
}
So, you can call get method as:
$amazon = new Amazon();
$amazon->orders('New')->invoices('New')->get(['orders', 'invoices']);
** You need to validate the element's availability within the get method.
I have question how can I cleverly handle a conditional where issue.
I have n field filters, e.g.: age, name, sex etc.
And now I can handle this using a dynamic scope:
public function scopeAge($query, $age)
{
if ($age) {
return $query->where('age', $age);
}
return $query;
}
and for the other fields so on.
But I need more generic example (write for all fields are a little bit cumbersome). It's possible to figure something using Laravel? I think about class e.g.: UserQuery.
class UserQuery
{
private $query;
public function __construct($query)
{
$this->query = $query;
$this->build();
}
private function build()
{
$this->appendAge();
$this->appendName();
}
private function appendAge()
{
$age = Input::get('age');
if ($age) {
$this->query->where('age', $age);
}
}
private function appendName()
{
$name = Input::get('name');
if ($name) {
$this->query->where('name', $name);
}
}
public function toQuery()
{
return $this->query;
}
}
And in model could be called:
public function scopeFilters($query)
{
$userQuery = new UserQuery($query)
return $userQuery->toQuery();
}
This is only my idea, maybe someone has better.
If I have the following class example:
<?php
class Person
{
private $prefix;
private $givenName;
private $familyName;
private $suffix;
public function setPrefix($prefix)
{
$this->prefix = $prefix;
}
public function getPrefix()
{
return $this->prefix;
}
public function setGivenName($gn)
{
$this->givenName = $gn;
}
public function getGivenName()
{
return $this->givenName;
}
public function setFamilyName($fn)
{
$this->familyName = $fn;
}
public function getFamilyName()
{
return $this->familyName;
}
public function setSuffix($suffix)
{
$this->suffix = $suffix;
}
public function getSuffix()
{
return $suffix;
}
}
$person = new Person();
$person->setPrefix("Mr.");
$person->setGivenName("John");
echo($person->getPrefix());
echo($person->getGivenName());
?>
I there a way in PHP (5.4 preferably), to combine these return values into one function, this way it models a little bit more like the revealing module pattern in JavaScript?
UPDATE:
OK, I am now beginning to learn that within PHP, it is normative to return a single value from a function, but you "can" return an array of multiple values. This is the ultimate answer to my question and what I will dive into some practices with this understanding.
small example -
function fruit () {
return [
'a' => 'apple',
'b' => 'banana'
];
}
echo fruit()['b'];
Also an article I ran across on stackoverflow on the topic...
PHP: Is it possible to return multiple values from a function?
Good luck!
You sound like you want the __get() magic method.
class Thing {
private $property;
public function __get($name) {
if( isset( $this->$name ) {
return $this->$name;
} else {
throw new Exception('Cannot __get() class property: ' . $name);
}
}
} // -- end class Thing --
$athing = new Thing();
$prop = $athing->property;
In the case that you want all of the values returned at once, as in Marc B's example, I'd simplify the class design for it thusly:
class Thing {
private $properties = array();
public function getAll() {
return $properties;
}
public function __get($name) {
if( isset( $this->properties[$name] ) {
return $this->properties[$name];
} else {
throw new Exception('Cannot __get() class property: ' . $name);
}
}
} // -- end class Thing --
$athing = new Thing();
$prop = $athing->property;
$props = $athing-> getAll();
Perhaps
public function getAll() {
return(array('prefix' => $this->prefix, 'givenName' => $this->giveName, etc...));
}