In Symfony3 I'm running
php app/console generate:doctrine:entity --entity=AcmeBlogBundle:Post
It creates 2 files: Post (in entity folder) and PostRepository (in repository folder extending \Doctrine\ORM\EntityRepository)
Everything is fine until I try to run the following repository-custom-function in my controller
$rir = $this->getDoctrine()->getRepository("AcmeBlogBundle:Post");
$replacementInstruction = $rir->getOneBy(
array("id" => 6)
);
My custom repository function is as follow
public function getOneBy($option)
{
$alias = "p";
$fields = $this->prepareRequestSelectFields("p");
$qb = $this->createQueryBuilder('Post');
$qb->select($fields)
->from('AcmeBlogBundle:Post', $alias)
->where($alias . '.id = :id')
->setParameter('id', 6)
;
$result = $qb->getQuery()->getResult();
}
private function prepareRequestSelectFields($alias)
{
return $alias. ".id";
}
In my database there are 10 posts with id from 1 to 10, so I expect it to return 1 result, however it return correct Post (id 6) 10 times
Why is that?
p.s. if I move the query builder to a custom service wrapper e.g. PostManager it works just fine (returning 1)
This doesn't really answer my question but apparently createQueryBuilder() in EntityRepository and in EntityManager are different, thanks to https://maltronic.io/2014/12/22/doctrine-createquerybuilder-entitymanager-vs-entityrepository/
So in my EntityRepository it should be
...
$qb = $this->createQueryBuilder('p'); // this should be the alias
$qb->select($fields)
// ->from('AcmeBlogBundle:Post', $alias) // remove this
->where($alias . '.id = :id')
->setParameter('id', 6)
;
...
Personally I dislike how 2 different functions has the same name, but I guess it's fine because one is from Doctrine and one is from Symfony. They are decoupled
edit Actually I might be wrong, they are both from Doctrine ... sigh
Still tho, it doesn't answer why in my original code, it returns multiple time
What about getSingleResult() rather than getResult() ? And you should maybe use the doctrine method findOneBy()
You can use the helper methods provided by the repositories, allowing you to fetch one or multiples entities from repositories with dynamic method names, in your case you want to fetch one post by id:
//fetch one
$replacementInstruction = $this->getDoctrine()->getRepository("AcmeBlogBundle:Post")->findOneById(6);
For information, fetching all entities is done with ->findByProperty
Related
I have an Asset Entity that uses an embedded association:
/**
* #ORM\Entity
*/
class Asset
{
....
/**
* #ORM\Embedded(class="Money\Money")
*/
private $code;
I want to search this class and my first instinct was to do something like this:
public function findOneByCurrencyCode(string $currencyCode)
{
$qb = $this->assetRepository->createQueryBuilder('asset');
$qb->innerJoin('asset.code', 'code')
->where('code.currency = :currency')
->setParameter('currency', $currencyCode);
$result = $qb->getQuery()->getOneOrNullResult();
return $result;
}
This, however, returns the following:
[Semantical Error] line 0, col 65 near 'code WHERE code.currency': Error:
Class Domain\Asset\Asset has no association named code
How do you search embedded classes?
EDIT:
I can get a result by doing something like this, however, this I feel is a hack:
$query = "SELECT * from asset where code_currency='BTC';";
$statement = $this->objectManager->getConnection()->prepare($query);
$statement->execute();
$result = $statement->fetchAll();
return $result;
I tried a bunch of different things and managed to get the answer:
$qb = $this->assetRepository->createQueryBuilder('asset');
$qb->where('asset.code.currency = :currency')
->setParameter('currency', $currencyCode);
$result = $qb->getQuery()->getOneOrNullResult();
return $result;
Turns out no inner join is required. Not sure why and perhaps someone can answer this in time, however the above appears to work with embedded objects
Hope this helps someone else.
"Embedded associations" does NOT exist. So, you doesn't need JOINS.
An embedded is part of your entity along with the others properties.
You have to do something like that:
SELECT u
FROM User u
WHERE u.address.city
(In this query, your entity is User, your embedded is Address and *city is the property of your
embedded).
It's pretty good explained in Doctrine documentation:
Doctrine embeddables
I'm running into a problem with the Doctrine Paginator.
In my repository I have a function to retrieve a specific dataset.
I use the querybuilder for this:
{myEntityRepository}->createQueryBuilder($alias)
In order to select only specific fields I use the following:
if (count($selectFields) > 0) {
$qb->resetDQLPart('select');
foreach ($selectFields as $selectField) {
$qb->addSelect($alias . '.' . $selectField);
}
}
This works fine when I retrieve the whole set like this:
$query = $qb->getQuery();
$data = $query->getResult(AbstractQuery::HYDRATE_ARRAY);
But it fails when I use the paginator:
$paginator = new Paginator($qb, $fetchJoinCollection = false);
$total = $paginator->count(),
$data = $paginator->getQuery()->getResult(AbstractQuery::HYDRATE_ARRAY)
I get the error:
Not all identifier properties can be found in the ResultSetMapping:
relationID\vendor\doctrine\orm\lib\Doctrine\ORM\Query\Exec\SingleSelectExecutor.php(38)
Question: Why does the paginator fail when I select only specific fields?
Am I overlooking something? Or am I doing it wrong all together?
i am using this solution.
add use statements
use Zend\Paginator\Paginator;
use DoctrineORMModule\Paginator\Adapter\DoctrinePaginator as DoctrineAdapter;
use Doctrine\ORM\Tools\Pagination\Paginator as ORMPaginator;
in your action
$viewModel = new ViewModel();
$entityManager = $this->getServiceLocator()
->get('Doctrine\ORM\EntityManager');
$queryBuilder = $entityManager
->createQueryBuilder();
$queryBuilder->add('select', new Expr\Select(array('t.id', 't.name')));
$queryBuilder->add('from', 'Application\Entity\Table t');
$adapter = new DoctrineAdapter(
new ORMPaginator(
$queryBuilder
)
);
$paginator = new Paginator($adapter);
$paginator->setDefaultItemCountPerPage(20);
$page = (int)$this->params()->fromQuery('page');
if($page) $paginator->setCurrentPageNumber($page);
$viewModel->results = $paginator;
return $viewModel;
Doctrine is trying to hydrate a relationship outlined by your YAML file, using a field that doesn't exist because you've excluded it from your SELECT statement. Take a look at your mapping file to figure out what you need to add back in.
I would think that it's only complaining with the Paginator because the field is not being accessed (and therefore not being lazy-loaded) when you don't use the Paginator.
As an aside (and with zero understanding of your stack, so YMMV) I would avoid making a habit of SELECTing reduced result sets, as you'll find yourself running into odd issues like this all the time. If you do need extra performance, you'd be better off putting a good old caching layer in place...
Actually using Zend Framework 2, I am looking for a way to implement a performant ACL strategy based on a database.
The whole idea is to directly filter the DQL queries depending on the currently logged in user, and it's permissions.
I found an implementation of this mecanisme in Symfony 2 http://symfony.com/doc/current/cookbook/security/acl_advanced.html, in this case one table seems to store for each user if he has access to a single row, so we can easily dynamically load only allowed rows by joining this table.
To synthesize,I am looking for a way to define access rules to entities based on criterias, but want to be able to get results in a single query to be able to do some ordering, and pagination.
Are there any ZF2 modules to resolve this case ?
It looks like integrating the SF2 security component as standalone is not an option: Security component from Symfony 2.0 as standalone
You have to use doctrine filter for load things for current member
example of my codes adding the filter for member query :
$em = $sm->get('doctrine.entitymanager.orm_default');
$ormconfig = $sm->get('doctrine.configuration.orm_default');
$ormconfig->addFilter("member", "\PatrickCore\Script\ORM\Functional\MemberAccessFilter");
//
$currentUser = $membersService->getCurrentUser();
$uid = $currentUser->getId();
$filter = $em->getFilters()->enable("member");
$filter->setParameter('member', $uid);
and this file \PatrickCore\Script\ORM\Functional\MemberAccessFilter :
<?php
namespace PatrickCore\Script\ORM\Functional;
use Doctrine\ORM\Mapping\ClassMetaData,
Doctrine\ORM\Query\Filter\SQLFilter;
class MemberAccessFilter extends SQLFilter
{
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias)
{
// Check if the entity implements the LocalAware interface
if (!$targetEntity->reflClass->implementsInterface('\PatrickCore\Entity\MemberAccessAware')) {
return "";
}
return $targetTableAlias.'.member_id = ' . $this->getParameter('member'); // getParameter applies quoting automatically
}
}
I'm looking at Caching and how to use it in Doctrine.
I've got the following in my Zend Framework Bootstrap.php:
// Build Configuration
$orm_config = new \Doctrine\ORM\Configuration();
// Caching
$cacheOptions = $options['cache']['backendOptions'];
$cache = new \Doctrine\Common\Cache\MemcacheCache();
$memcache = new Memcache;
$memcache->connect($cacheOptions['servers']['host'], $cacheOptions['servers']['port']);
$cache->setMemcache($memcache);
$orm_config->setMetadataCacheImpl($cache);
$orm_config->setQueryCacheImpl($cache);
$orm_config->setResultCacheImpl($cache);
I'm running a very simple query on my DB using:
self::_instance()->_em->getRepository('UserManagement\Users')->find('1');
And I'm not sure if I'm using caching properly, because with it on (as
per the above config) the query seems to take twice as long to execute
as with it disabled, is this right?
Thanks in advance,
Steve
I seem to have sorted this myself, sort of related to enter link description here. Basically, from what I understand a repository query like:
self::_instance()->_em->getRepository('UserManagement\Users')->find('1');
Will not cache the results. If the same query is executed again throughout the script processing, it will not perform the search and use the result it has in memory - this isn't the same as real caching, in my case using Memcache.
The only way to achieve this, is to override the Doctrine EntityRepository find() method in a custom repository with something like:
public function find($id)
{
// Retrieve an instance of the Entity Manager
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->select('u')
->from('UserManagement\Users', 'u')
->where('u.id = :id')
->setParameter('id', $id);
$query = $qb->getQuery();
$query->useResultCache(TRUE);
$result = $query->getSingleResult();
return $result;
}
Notably, the most important line from the above is $query->useResultCache(TRUE); - this informs the Application to cache the results.
Hope this helps.
I am having an issue with EF 4.1 using "Code First". Let me setup my situation before I start posting any code. I have my DBContext class, called MemberSalesContext, in a class library project called Data.EF. I have my POCOs in a seperate class library project called Domain. My Domain project knows nothing of Entity Framework, no references, no nothing. My Data.EF project has a reference to the Domain project so that my DB context class can wire up everything in my mapping classes located in Data.EF.Mapping. I am doing all of the mappings in this namespace using the EntityTypeConfiguration class from EntityFramework. All of this is pretty standard stuff. On top of Entity Framework, I am using the Repository pattern and the Specification pattern.
My SQL Server database table has a composite primary key defined. The three columns that are part of the key are Batch_ID, RecDate, and Supplier_Date. This table as an identity column (database generated value => +1) called XREF_ID, which is not part of the PK.
My mapping class, located in Data.EF.Mapping looks like the following:
public class CrossReferenceMapping : EntityTypeConfiguration<CrossReference>
{
public CrossReferenceMapping()
{
HasKey(cpk => cpk.Batch_ID);
HasKey(cpk => cpk.RecDate);
HasKey(cpk => cpk.Supplier_Date);
Property(p => p.XREF_ID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
ToTable("wPRSBatchXREF");
}
}
My MemberSalesContext class (inherits from DBContext) looks like the following:
public class MemberSalesContext : DbContext, IDbContext
{
//...more DbSets here...
public DbSet<CrossReference> CrossReferences { get; set; }
//...more DbSets here...
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<IncludeMetadataConvention>();
//...more modelBuilder here...
modelBuilder.Configurations.Add<CrossReference>(new CrossReferenceMapping());
//...more modelBuilder here...
}
}
I have a private method in a class that uses my repository to return a list of objects that get iterated over. The list I am referring to is the outermost foreach loop in the example below.
private void CloseAllReports()
{
//* get list of completed reports and close each one (populate batches)
foreach (SalesReport salesReport in GetCompletedSalesReports())
{
try
{
//* aggregate sales and revenue by each distinct supplier_date in this report
var aggregates = BatchSalesRevenue(salesReport);
//* ensure that the entire SalesReport breaks out into Batches; success or failure per SalesReport
_repository.UnitOfWork.BeginTransaction();
//* each salesReport here will result in one-to-many batches
foreach (AggregateBySupplierDate aggregate in aggregates)
{
//* get the batch range (type) from the repository
BatchType batchType = _repository.Single<BatchType>(new BatchTypeSpecification(salesReport.Batch_Type));
//* get xref from repository, *if available*
//* some will have already populated the XREF
CrossReference crossReference = _repository.Single<CrossReference>(new CrossReferenceSpecification(salesReport.Batch_ID, salesReport.RecDate, aggregate.SupplierDate));
//* create a new batch
PRSBatch batch = new PRSBatch(salesReport,
aggregate.SupplierDate,
BatchTypeCode(batchType.Description),
BatchControlNumber(batchType.Description, salesReport.RecDate, BatchTypeCode(batchType.Description)),
salesReport.Zero_Sales_Flag == false ? aggregate.SalesAmount : 1,
salesReport.Zero_Sales_Flag == false ? aggregate.RevenueAmount : 0);
//* populate CrossReference property; this will either be a crossReference object, or null
batch.CrossReference = crossReference;
//* close the batch
//* see PRSBatch partial class for business rule implementations
batch.Close();
//* check XREF to see if it needs to be added to the repository
if (crossReference == null)
{
//*add the Xref to the repository
_repository.Add<CrossReference>(batch.CrossReference);
}
//* add batch to the repository
_repository.Add<PRSBatch>(batch);
}
_repository.UnitOfWork.CommitTransaction();
}
catch (Exception ex)
{
//* log the error
_logger.Log(User, ex.Message.ToString().Trim(), ex.Source.ToString().Trim(), ex.StackTrace.ToString().Trim());
//* move on to the next completed salesReport
}
}
}
All goes well on the first iteration of the outer loop. On the second iteration of the outer loop, the code fails at _repository.UnitOfWork.CommitTransaction(). The error message returned is the following:
"The changes to the database were committed successfully, but an error occurred while updating the object context. The ObjectContext might be in an inconsistent state. Inner exception message: AcceptChanges cannot continue because the object's key values conflict with another object in the ObjectStateManager. Make sure that the key values are unique before calling AcceptChanges."
In this situation, the database changes on the second iteration were not committed successfully, but the changes in the first iteration were. I have ensured that objects in the outer and inner loops are all unique, adhering to the database primary keys.
Is there something that I am missing here? I am willing to augment my code samples, if it proves helpful. I have done everything within my capabilities to troubleshoot this issue, minus modifying the composite primary key set on the database table.
Can anyone help??? Much thanks in advance! BTW, sorry for the long post!
I am answering my own question here...
My issue had to do with how the composite primary key was being defined in my mapping class. When defining a composite primary key using EF Code First, you must define it like so:
HasKey(cpk => new { cpk.COMPANYID, cpk.RecDate, cpk.BATTYPCD, cpk.BATCTLNO });
As opposed to how I had it defined previously:
HasKey(cpk => cpk.COMPANYID);
HasKey(cpk => cpk.RecDate);
HasKey(cpk => cpk.BATTYPCD);
HasKey(cpk => cpk.BATCTLNO);
The error I was receiving was that the ObjectContext contained multiple elements of the same type that were not unique. This became an issue in my UnitOfWork on CommitTransaction. This is because when the mapping class was instanciated from my DBContext class, it executed 4 HasKey statements shown above, with only the last one for property BATCTLNO becoming the primary key (not composite). Defining them inline, as in my first code sample above, resolves the issue.
Hope this helps someone!