How to access Doctrine defined methods after custom Repository is made? - php

I am developing a site using ZF2 and Doctrine. The problem I am facing is I am using Doctrine predefined object methods like findAll(), findOneBy(), findBy() etc in my code. For some custom actions I have prepared a custom Repository for one of my entities. Now I can't access the predefined methods. I have already written code by using findAll() method. But after building a repository I can't simply access findAll() method. How can I both access my custom defined methods along with Doctrine defined methods?
For example:
I am using findOneBy() like this:
$udata = $this->em()->getRepository('Application\Entity\Usermain')->findOneBy(array('userEmail' => 'subh.laha#gmail.com'));
Now I have prepared UsermainRepository like below:
namespace Application\Entity\Repositories;
use Doctrine\ORM\EntityRepository;
use Doctrine\Common\Persistence\ObjectRepository;
class UsermainRepository extends EntityRepository
{
protected $sl;
public function __construct($sl){
$this->sl = $sl;
}
public function customFind($arr)
{
$qb = $this->sl->createQueryBuilder();
$whereStr = '';
if(count($arr)){
foreach($arr as $kvarr=>$varr){
$whereStr .= "u.$kvarr = '".$varr."'";
}
}
$qry = $qb->select('u')
->from('Application\Entity\Usermain','u')
->where($whereStr)
->getQuery()
->getResult();
return $qry;
}
}
Now I can access
$udata = $this->em()->getRepository('Application\Entity\Usermain')->customFind(array('userEmail' => 'subh.laha#gmail.com'));
But Not
$udata = $this->em()->getRepository('Application\Entity\Usermain')->findOneBy(array('userEmail' => 'subh.laha#gmail.com'));
Why? I have already written code by using doctrine defined methods. What can I do now?

I believe you are getting this error because you have overridden the repository's constructor method but aren't calling the parent constructor so required parameters aren't being properly set.

I think your code is not correct, You can use the below query instead of writing complex custom object.
$query = $this->getEntityManager()->createQueryBuilder()
->select('U.id,U.name')
->from('Application\Entity\StudentClass', 'U')
->where('U.pkStudentClass = :pkStudentClass')
->setParameter('pkStudentClass', 1)
->setMaxResults(20);
->orderBy('id', 'DESC')
->getQuery();
$result = $query->getScalarResult();

There are several problems exist in your approach;
I think trying to access the service locator's itself in an entity repository is bad idea. You shouldn't need service container in repository level.
The second detail is, when extending any class, you need to check out, read and respect the signature of the parent. In your case, you're overriding the parent's __construct. Calling parent::__construct() may seems like a solution but it's not. You'll soon realize that you also need a custom repository factory to pass additional arguments to constructor while keeping the current functionality. No way.
This is more important than others: you believe that $this->sl->createQueryBuilder() returns query builder instance. Theoretically seems like working but $this->sl is not service locator, service locator doesn't knows anything about query builders, it's just EntityManager instance which passed to your constructor.
Try this:
<?php
namespace Application\Entity\Repositories;
use Doctrine\ORM\EntityRepository;
class UsermainRepository extends EntityRepository
{
public function customFind($arr)
{
// Just pass an alias for your entity
$qb = $this->createQueryBuilder('u');
$whereStr = '';
if (count($arr)) {
foreach ($arr as $kvarr => $varr) {
$whereStr .= "u.$kvarr = '".$varr."'";
}
}
return $qb->where($whereStr)
->getQuery()
->getResult();
}
}
Finally, in your Application\Entity\Usermain entity, you'll also need telling about your custom repository to doctrine since you don't want to use default EntityRepository :
namespace Application\Entity;
/**
* #ORM\Entity(repositoryClass="Application\Entity\Repositories\UsermainRepository")
*/
class Usermain
{
}
Now in your controller (or service) level, you can test:
$em = $this->getServiceLocator()->get('doctrine.entitymanager.orm_default');
$repo = $em->getRepository('Application\Entity\Usermain');
// repo is a UsermainRepository instance
// this should work:
$udata = $repo->customFind(array('userEmail' => 'subh.laha#gmail.com'));
// this should also work
$udata = $repo->findOneBy(array('userEmail' => 'subh.laha#gmail.com'));
I strongly recommend carefully reading of Working With Objects section of the documentation before diving into deeps.

Related

Apigility + Doctrine2 QueryProvider - Can't use created function on query builder

I'm using Apigility to create a rest application, where the back-end and front-end are pretty much independent applications.
Ok, on the Back-end I'm using 'zf-apigility-doctrine-query-provider' to create queries depending on the parameters sent via url (i.e localhost?instancia=10), but I need to process information using a MS SQL database stored function, something like this:
function createQuery(ResourceEvent $event, $entityClass, $parameters){
/* #var $queryBuilder \Doctrine\ORM\QueryBuilder */
$queryBuilder = parent::createQuery($event,$entityClass, $parameters);
if (!empty($parameters['instancia'])) {
$queryBuilder->andWhere($queryBuilder->expr()->eq('chapa.instancia', 'dbo.isItSpecial(:instancia)'))
->setParameter('instancia', $parameters['instancia']);
}
return $queryBuilder;
}
However it simply won't work, it won't accept the 'dbo.isItSpecial' and seems like I can't access the ServiceLocator, nor the EntityManager or anything but the Querybuilder.
I thought about creating a native query to get the result and the using it on the main query but seems like I can't create it.
Any ideas?
In what class is this method? Add some context to your question.
You do parent::createQuery which suggests that you are in a DoctrineResource instance. If this is true it means that both the ServiceLocator and the ObjectManager are simply available in the class.
You can read on doing native queries in Doctrine here in the Documentation:
$rsm = new ResultSetMapping();
$query = $entityManager->createNativeQuery(
'SELECT id, name, discr FROM users WHERE name = ?',
$rsm
);
$query->setParameter(1, 'romanb');
$users = $query->getResult();
Turns out I've found some ways to do this.
The class that the method was, extends this class
ZF\Apigility\Doctrine\Server\Query\Provider\DefaultOrm
That means I have access to the ObjectManager. The Documentation doesn't help much, but the ObjectManager is actually an EntityManager (ObjectManager is just the interface), to discover this I had to use the get_class PHP command.
With the entity manager I could have done what Wilt sugested, something like this:
$sqlNativa = $this->getObjectManager()->createNativeQuery("Select dbo.isItSpecial(:codInstancia) as codEleicao", $rsm);
However, I created a service that execute this function (it will be used in many places), so I also made a factory that set this service to the query provider class, on the configuration file it's something like this.
'zf-apigility-doctrine-query-provider' => array(
'factories' => array(
'instanciaDefaultQuery' => 'Api\Instancia\instanciaQueryFactory',
),
),
And the factory looks like something like this (the service is executing the NativeQuery like the response from Wilt):
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Api\EleitoChapaOrgao\EleitoChapaOrgaoQuery;
class InstanciaQueryFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceManager){
$instanciaService = $serviceManager->getServiceLocator()->get('Application\Service\Instancia');
$query = new InstanciaQuery($instanciaService);
return $query;
}
}
Finally just add the constructor to the QueryProvider and the service will be avaliable there:
class InstanciaQuery extends DefaultOrm
{
protected $instanciaService;
public function __construct(Instancia $instanciaService)
{
$this->instanciaService = $instanciaService;
}
public function createQuery(ResourceEvent $event, $entityClass, $parameters)
{ /* The rest of the code goes here*/

PHPUnit and namespaces - mocked methods still being called

I am adding unit tests to an existing project that is using namespaces. I haven't ever had to use namespaces before, so it is somewhat of an adventure. My issue is that in my unit tests, it appears the mocked methods are still being called. Below is an example of the code file and the test.
private function selectFromDb($fields, $criteria = null) {
$fields = is_array($fields) ? implode(', ', $fields) : $fields;
$sql = "SELECT $fields FROM balloons";
if(!is_null($criteria)) {
$sql .= " WHERE $criteria";
}
$adapter = $this->getAdapter();
$statement = $adapter->query($sql);
$result = $statement->execute();
return $result;
}
Here is the test code:
// I'm passing in data here which isn't consequential for the question.
public function testSelectFromDb($fields, $criteria, $expectedSql) {
$statement = $this->getMockBuilder('Zend\Db\Adapter\Driver\Pdo\Statement')
->disableOriginalConstructor()
->setMethods(array('execute'))->getMock();
$statement->expects($this->once())
->method('execute')->will($this->returnValue('fake'));
$adapter = $this->getMockBuilder('Zend\Db\Adapter\Adapter')
->disableOriginalConstructor()
->setMethods(array('query'))->getMock();
$adapter->expects($this->once())
->method('query')->with($expectedSql)
->will($this->returnValue($statement));
$bm = $this->getMockBuilder('Application\Model\BalloonModel')
->setMethods(array('getAdapter'))
->disableOriginalConstructor()->getMock();
$bm->expects($this->once())
->method('getAdapter')->will($this->returnValue($adapter));
// I use reflection as the method is private to the class
$reflection = new ReflectionClass($bm);
$method = $reflection->getMethod('selectFromDb');
$method->setAccessible(true);
$result = $method->invokeArgs($bm, array($fields, $criteria));
}
At this point, I'm just trying to get the test to execute to the end, but I continue to get the following error:
Tests\Model\BalloonModelTest::testSelectFromDb with data set "singleField" ('id', NULL, 'SELECT id FROM balloon')
Zend\Db\Adapter\Exception\InvalidQueryException: Statement could not be executed
/apath/PHP/vendor/zendframework/zendframework/library/zend/db/adapter/driver/pdo/statement.php:245
/apath/PHP/vendor/zendframework/zendframework/library/zend/db/adapter/driver/pdo/statement.php:240
/apath/PHP/module/Application/src/application/model/balloonmodel.php:243
/apath/PHP/tests/Model/BalloonModelTest.php:70
Caused by
PDOException: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'id' in 'field list'
/apath/PHP/vendor/zendframework/zendframework/library/zend/db/adapter/driver/pdo/statement.php:240
/apath/PHP/module/Application/src/application/model/balloonmodel.php:243
/apath/PHP/tests/Model/BalloonModelTest.php:70
This tells me that the 'getAdapter', 'query' and 'execute' calls are still being made even though all of them are theoretically mocked. I've verified as best I can that the class names used are using the correct namespaces. Any ideas?
The problem is not in the namespaces. It's probably that you try to mock the class under test itself and I'm guessing that getAdapter() is a private method, called from within the class.
Remember that a mock is an object of a generated class that extends the original class.
Now, there's Mock_XYZ extends BalloonModel which adds the mock method getAdapter(), but if the getAdapter() method in BalloonModel itself is private, it will not be overridden but you end up with two different methods (one internal and one external if you will).
Solution
Refactor your code to use Dependency Injection. I'm not talking about IoC containers, just about creating other objects not in the class itself, but inject them with setters or constructor. Then you can do the following after creating the $adapter mock, instead of mocking getAdapter():
$bm->setAdapter($adapter);

Laravel Custom Model Methods

Whenever I add additional logic to Eloquent models, I end up having to make it a static method (i.e. less than ideal) in order to call it from the model's facade. I've tried searching a lot on how to do this the proper way and pretty much all results talk about creating methods that return portions of a Query Builder interface. I'm trying to figure out how to add methods that can return anything and be called using the model's facade.
For example, lets say I have a model called Car and want to get them all:
$cars = Car::all();
Great, except for now, let's say I want to sort the result into a multidimensional array by make so my result may look like this:
$cars = array(
'Ford' => array(
'F-150' => '...',
'Escape' => '...',
),
'Honda' => array(
'Accord' => '...',
'Civic' => '...',
),
);
Taking that theoretical example, I am tempted to create a method that can be called like:
$cars = Car::getAllSortedByMake();
For a moment, lets forget the terrible method name and the fact that it is tightly coupled to the data structure. If I make a method like this in the model:
public function getAllSortedByMake()
{
// Process and return resulting array
return array('...');
}
And finally call it in my controller, I will get this Exception thrown:
Non-static method Car::getAllSortedByMake() should not be called statically, assuming $this from incompatible context
TL;DR: How can I add custom functionality that makes sense to be in the model without making it a static method and call it using the model's facade?
Edit:
This is a theoretical example. Perhaps a rephrase of the question would make more sense. Why are certain non-static methods such as all() or which() available on the facade of an Eloquent model, but not additional methods added into the model? This means that the __call magic method is being used, but how can I make it recognize my own functions in the model?
Probably a better example over the "sorting" is if I needed to run an calculation or algorithm on a piece of data:
$validSPG = Chemical::isValidSpecificGravity(-1.43);
To me, it makes sense for something like that to be in the model as it is domain specific.
My question is at more of a fundamental level such as why is all()
accessible via the facade?
If you look at the Laravel Core - all() is actually a static function
public static function all($columns = array('*'))
You have two options:
public static function getAllSortedByMake()
{
return Car::where('....')->get();
}
or
public function scopeGetAllSortedByMake($query)
{
return $query->where('...')->get();
}
Both will allow you to do
Car::getAllSortedByMake();
Actually you can extend Eloquent Builder and put custom methods there.
Steps to extend builder :
1.Create custom builder
<?php
namespace App;
class CustomBuilder extends \Illuminate\Database\Eloquent\Builder
{
public function test()
{
$this->where(['id' => 1]);
return $this;
}
}
2.Add this method to your base model :
public function newEloquentBuilder($query)
{
return new CustomBuilder($query);
}
3.Run query with methods inside your custom builder :
User::where('first_name', 'like', 'a')
->test()
->get();
for above code generated mysql query will be :
select * from `users` where `first_name` like ? and (`id` = ?) and `users`.`deleted_at` is null
PS:
First Laurence example is code more suitable for you repository not for model, but also you can't pipe more methods with this approach :
public static function getAllSortedByMake()
{
return Car::where('....')->get();
}
Second Laurence example is event worst.
public function scopeGetAllSortedByMake($query)
{
return $query->where('...')->get();
}
Many people suggest using scopes for extend laravel builder but that is actually bad solution because scopes are isolated by eloquent builder and you won't get the same query with same commands inside vs outside scope. I proposed PR for change whether scopes should be isolated but Taylor ignored me.
More explanation :
For example if you have scopes like this one :
public function scopeWhereTest($builder, $column, $operator = null, $value = null, $boolean = 'and')
{
$builder->where($column, $operator, $value, $boolean);
}
and two eloquent queries :
User::where(function($query){
$query->where('first_name', 'like', 'a');
$query->where('first_name', 'like', 'b');
})->get();
vs
User::where(function($query){
$query->where('first_name', 'like', 'a');
$query->whereTest('first_name', 'like', 'b');
})->get();
Generated queries would be :
select * from `users` where (`first_name` like ? and `first_name` like ?) and `users`.`deleted_at` is null
vs
select * from `users` where (`first_name` like ? and (`id` = ?)) and `users`.`deleted_at` is null
on first sight queries look the same but there are not. For this simple query maybe it does not matter but for complicated queries it does, so please don't use scopes for extending builder :)
for better dynamic code, rather than using Model class name "Car",
just use "static" or "self"
public static function getAllSortedByMake()
{
//to return "Illuminate\Database\Query\Builder" class object you can add another where as you want
return static::where('...');
//or return already as collection object
return static::where('...')->get();
}
Laravel model custom methods -> best way is using traits
Step #1: Create a trait
Step #2: Add the trait to model
Step #3: Use the method
User::first()->confirmEmailNow()
app/Model/User.php
use App\Traits\EmailConfirmation;
class User extends Authenticatable
{
use EmailConfirmation;
//...
}
app/Traits/EmailConfirmation.php
<?php
namespace App\Traits;
trait EmailConfirmation
{
/**
* Set email_verified_at to now and save.
*
*/
public function confirmEmailNow()
{
$this->email_verified_at = now();
$this->save();
return $this;
}
}

doctrine 2 and zend paginator

i want to use doctrine with zend_paginator
here some example query :
$allArticleObj =
$this->_em->getRepository('Articles');
$qb = $this->_em->createQueryBuilder();
$qb->add('select', 'a')
->add('from', 'Articles a')
->setFirstResult(0)
->setMaxResults(5);
is there any example code to show we can write a zend_paginator adapter for doctrine 2 query builder?
You don't need to implement Zend_Paginator_Adapter_Interface. It is already implement by Zend_Paginator_Adapter_Iterator.
You can simply pass Doctrines' Paginator to Zend_Paginator_Adapter_Iterator, which you pass to Zend_Paginator. Then you call Zend_Paginator::setItemCountPerPage($perPage) and Zend_Paginator::setCurrentPageNumber($current_page). Like this:
use Doctrine\ORM\Tools\Pagination as Paginator; // goes at top of file
SomeController::someAction()
{
$dql = "SELECT s, c FROM Square\Entity\StampItem s JOIN s.country c ".' ORDER BY '. $orderBy . ' ' . $dir;
$query = $this->getEntityManager()->createQuery($dql);
$d2_paginator = new Paginator($query); \\
$d2_paginator_iter = $d2_paginator->getIterator(); // returns \ArrayIterator object
$adapter = new \Zend_Paginator_Adapter_Iterator($d2_paginator_iter);
$zend_paginator = new \Zend_Paginator($adapter);
$zend_paginator->setItemCountPerPage($perPage)
->setCurrentPageNumber($current_page);
$this->view->paginator = $zend_paginator; //Then in your view, use it just like your currently use
}
Then you use paginator in the view script just like you ordinarly do.
Explanation:
Zend_Paginator's constructor can take a Zend_Paginator_Adapter_Interface, which Zend_Paginator_Adpater_Iterator implements. Now, Zend_Paginator_Adapter_Iterator's constructor takes an \Iterator interface. This \Iterator must also implement \Countable (as you can see by looking at Zend_Paginator_Adapter_Iterator's constructor). Since Paginator::getIterator() method returns an \ArrayIterator, it by definition it fits the bill (since \ArrayIterator implements both \Iterator and \Countable).
See this port from Doctrine 1 to Docrine 2 of the code for "Zend Framework: A Beginner's Guide" from Doctrine 1 to Doctrine: https://github.com/kkruecke/zf-beginners-doctrine2. It includes code for paginating with Zend_Paginator using Zend_Paginator_Adapter_Iterator with Doctrine 2' Doctrine\ORM\Tools\Pagination\Paginator.
Code is here (although it might not all work with latest DoctrineORM 2.2) but the example is valid: https://github.com/kkruecke/zf-beginners-doctrine2/tree/master/ch7
I was very surprised how hard it was to find an adapter example that doesn't cause performance issues with large collections for Doctrine 2 and ZF1.
My soultion does use the the Doctrine\ORM\Tools\Pagination\Paginator from Doctrine 2.2 just like #Kurt's answer; however the difference that this will return the doctrine paginator (Which is it's self an \Iterator) from getItems without the entire query results being hydrated.
use Doctrine\ORM\Tools\Pagination\Paginator as DoctrinePaginator;
use Doctrine\ORM\Query;
class DoctrinePaginatorAdapter implements \Zend_Paginator_Adapter_Interface
{
protected $query;
public function __construct(Query $query)
{
$this->query = $query;
}
public function count()
{
return $this->createDoctrinePaginator($this->query)->count();
}
public function getItems($offset, $itemCountPerPage)
{
$this->query->setFirstResult($offset)
->setMaxResults($itemCountPerPage);
return $this->createDoctrinePaginator($this->query);
}
protected function createDoctrinePaginator(Query $query, $isFetchJoinQuery = true)
{
return new DoctrinePaginator($query, $isFetchJoinQuery);
}
}
The upshot, of course, is that you have to implement the Zend_Paginator_Adapter_Interface, which essentially means implementing the two methods:
count()
getItems($offset, $perPage)
Your adapter would accept the Doctrine query as a constructor argument.
In principle, the getItems() part is actually straightforward. Simply, add the $offset and $perPage restrictions to the query - as you are doing in your sample - and execute the query.
In practice, it's the count() business that tends to be tricky. I'd follow the example of Zend_Paginator_Adapter_DbSelect, replacing the Zend_Db operations with their Doctrine analogues.
Please change
use Doctrine\ORM\Tools\Pagination as Paginator;
to
use Doctrine\ORM\Tools\Pagination\Paginator as Paginator;

How to set the default hydrator in Doctrine?

I can't find a way to set the default hydrator in Doctrine. It should be available. Right?
http://docs.doctrine-project.org/projects/doctrine1/en/latest/en/manual/data-hydrators.html#writing-hydration-method
The above documentation page explains how to create a custom hydrator. The drawback here is that you need to "specify" the hydrator each and every time you execute a query.
I figured this out by reading Chris Gutierrez's comment and changing some stuff.
First, define an extension class for Doctrine_Query. Extend the constructor to define your own hydration mode.
class App_Doctrine_Query extends Doctrine_Query
{
public function __construct(Doctrine_Connection $connection = null,
Doctrine_Hydrator_Abstract $hydrator = null)
{
parent::__construct($connection, $hydrator);
if ($hydrator === null) {
$this->setHydrationMode(Doctrine::HYDRATE_ARRAY); // I use this one the most
}
}
}
Then, in your bootstrap, tell Doctrine about your new class.
Doctrine_Manager::getInstance()->setAttribute(Doctrine_Core::ATTR_QUERY_CLASS, 'App_Doctrine_Query');
Chris Gutierrez defined the attribute for the connection instead of globally but I have more than one connection and I want to use this default for all of them.
Now you don't have to call Doctrine_Query::setHydrationMode() every time you build a query.
Here's more information
http://www.doctrine-project.org/projects/orm/1.2/docs/manual/configuration/en#configure-query-class
EDIT: Changes below
I have found a problem with the above. Specifically, doing something like "Doctrine_Core::getTable('Model')->find(1)" will always return a hydrated array, not an object. So I have altered this a bit, defining custom execute methods for use in a Query call.
Also, I added memory freeing code.
class App_Doctrine_Query extends Doctrine_Query
{
public function rows($params = array(), $hydrationMode = null)
{
if ($hydrationMode === null)
$hydrationMode = Doctrine_Core::HYDRATE_ARRAY;
$results = parent::execute($params, $hydrationMode);
$this->free(true);
return $results;
}
public function row($params = array(), $hydrationMode = null)
{
if ($hydrationMode === null)
$hydrationMode = Doctrine_Core::HYDRATE_ARRAY;
$results = parent::fetchOne($params, $hydrationMode);
$this->free(true);
return $results;
}
}
That'd be a great idea, and on reading your question I thought it'd be something you could do via Doctrine. However, reading through the code makes me think you can't:
Doctrine_Query::create() creates a new query specifying only the first argument of Doctrine_Query_Abstract::__construct(), the connection, without specifying the second argument - the hydration mode. No calls to configuration are made. As no hydrator is passed, a new Doctrine_Hydrator is created, and its constructor equally does not look anywhere for a configuration option, and thus it has the default Doctrine::HYDRATE_RECORD setting.
Perhaps subclassing Doctrine_Query with the below factory method is the easiest option?
public static function create($conn = null)
{
return new Doctrine_Query($conn,Doctrine::HYDRATE_ARRAY);
}

Categories