How do I obtain all documents referenced by a single document? - doctrine-mongodb

If I have a document Shop that has many Activities defined as ReferenceMany, is there a way I can directly query for the list of Activities for a Shop without hydrating a Shop instance?
For example:
{
"_id": fd390j09afj09dfj,
"activities": [
...
]
}
All I want is to be able to say "get me the array activities where _id is fd390j09afj09dfj, and hydrate them as Activity instances.
Here's the first solution I came up with:
/**
* Gets all activities configured for a shop.
*
* #param string $shopId
* #return \BikeShed\Domain\Activity[]|\Doctrine\Common\Collections\ArrayCollection
*/
public function findByShopId($shopId) {
/** #var \BikeShed\Domain\Repository\Shop $shopRepository */
$shopRepository = $this->dm->getRepository('BikeShed\Domain\Shop');
$shop = $shopRepository->findOneById($shopId);
return $shop->getActivities();
}
It's simply fetching the Shop and then getting all the Activities via the defined relation.
Here's a working example of how you would implement jmikola's last suggestion:
/**
* #param string $shopId
* #return ActivityModel[]
*/
public function findByShopId($shopId) {
$partialShopData = $this->dm->getRepository('BikeShed\Domain\Shop')->createQueryBuilder()
->hydrate(false)
->field('activities')
->getQuery()
->getSingleResult()
;
$activityIds = [];
if(!empty($partialShopData['activities']))
foreach($partialShopData['activities'] as $activity)
if(!empty($activity['$id']))
$activityIds[] = $activity['$id'];
return $this->createQueryBuilder()
->field('id')
->in($activityIds)
->getQuery()
->toArray()
;
}

You cannot directly query the Shop collection or (or ODM repository) and receive Activity instances; however, you can use the Query Builder API to specify a projection with select('activities'). The executed query will still return Shop instances, but the activities field should be the only thing hydrated (as a PersistentCollection of Activity instances). In this case, you shouldn't modify any of the non-hydrated Shop fields, as ODM will detect any non-null value as a change.
It should be trivial to add a convenience method on ShopRepository that issues the above query with its select() and returns the collection (or an array) of Activity documents instead of the Shop. Keeping the Shop inaccessible should also protect you from inadvertently modifying other non-hydrated fields within it.
The down-side with this method is that the Activities will be proxy objects and lazily loaded. You can mitigate this with reference priming. With priming, you'll end up doing two queries (one for the Shop and one for all referenced Activity documents).
Regarding your follow-up question about putting this method on the Activity repository, you do have another option. Firstly, I agree that ActivityRepository::findByShopId() is preferable to calling a method on ShopRepository that returns Activity objects.
Each repository has a reference to the document manager, which you can use to access other repositories via the getRepository() method. An ActivityRepository::findByShopId() could do the following:
Access the Shop repository through the document manager
Query for the Shop by its ID, projecting only the activities field and disabling hydration completely
Collect the identifiers from the activities array. Depending on whether the Activity references are simple or not, the elements in that array may be the raw _id values or DBRef objects.
Execute a query for all Activity objects (easy, since we're already in that repository) where the ID is $in the array of identifiers

Related

Symfony doctrine, How reduce number of queries

I have a lot of queries in my profiler, So I try to add join() and select() in my repository for reduce the number of queries like usual
But this time, I reload the page and I have more queries than before
/**
* #return ForumDiscussion[] Returns an array of Page objects
*/
public function findBySortUpdatedAt()
{
return $this->createQueryBuilder('f')
// Doesn't work, make more queries
// ->select(array('f', 'user', 'forumCategory'))
// ->leftJoin('f.user', 'user')
// ->leftJoin('f.forumCategory', 'forumCategory')
->orderBy('f.updatedAt', 'DESC')
->setMaxResults(10)
->getQuery()
->getResult()
;
}
Someone know why it's happen ?
Thank you
Your User entity must have relations to other Entities. By default Doctrine uses the "LAZY" fetch mode when fetching entities.
The "EXTRA_LAZY" will reduce the number of queries by returning dummy objects instead of actual hydrated entities concerning the User related objects.
To use the "EXTRA_LAZY" fetch mode in the User entity you can just pass it as a parameter of your association annotations.
Here is a link to the official documentation: https://www.doctrine-project.org/projects/doctrine-orm/en/2.11/tutorials/extra-lazy-associations.html

Compare two database fields in extbase repository

I am using TYPO3 8. In my extension I have a database table "company" in which I store for each company the total number of places (number_places) and the number of occupied places (occupied_places).
Now I want to limit the search to companies which have available places left.
In MySQL it would be like this:
SELECT * FROM company WHERE number_places > occupied_places;
How can I create this query in the extbase repository?
I tried to introduce the virtual property placesLeft in my model but it did not work.
I don't want to use a raw SQL statement as mentioned below, because I already have implemented a filter which uses plenty of different constraints.
Extbase query to compare two fields in same table
You can do it like this in your repository class, please note the comments inside the code:
class CompanyRepository extends \TYPO3\CMS\Extbase\Persistence\Repository
{
public function findWithAvailablePlaces(bool $returnRawQueryResult = false)
{
// Create a QueryBuilder instance
$queryBuilder = $this->objectManager->get(\TYPO3\CMS\Core\Database\ConnectionPool::class)
->getConnectionForTable('company')->createQueryBuilder();
// Create the query
$queryBuilder
->select('*')
->from('company')
->where(
// Note: this string concatenation is needed, because TYPO3's
// QueryBuilder always escapes the value in the ExpressionBuilder's
// methods (eq(), lt(), gt(), ...) and thus render it impossible to
// compare against an identifier.
$queryBuilder->quoteIdentifier('number_places')
. \TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder::GT
. $queryBuilder->quoteIdentifier('occupied_places')
);
// Execute the query
$result = $queryBuilder->execute()->fetchAll();
// Note: this switch is not needed in fact. I just put it here, if you
// like to get the Company model objects instead of an array.
if ($returnRawQueryResult) {
$dataMapper = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper::class);
return $dataMapper->map($this->objectType, $result);
}
return $result;
}
}
Notes:
If you have lots of records to deal with, I would - for performance reasons - not use the data mapping feature and work with arrays.
If you want to use the fluid pagination widget, be sure you don't and build your own pagination. Because of the way this works (extbase-internally), you'd get a huge system load overhead when the table grows. Better add the support for limited db queries to the repository method, for example:
class CompanyRepository extends \TYPO3\CMS\Extbase\Persistence\Repository
{
public function findWithAvailablePlaces(
int $limit = 10,
int $offset = 0,
bool $returnRawQueryResult = false
) {
// ...
$queryBuilder
->setMaxResults($limit)
->setFirstResult($offset);
$result = $queryBuilder->execute()->fetchAll();
// ...
}
}
I think you cant do this using the default Extbase Query methods like equals() and so on. You may use the function $query->statement() for your specific queries like this.
You also can use the QueryBuilder since TYPO3 8 which has functions to compare fields to each other:
https://docs.typo3.org/typo3cms/CoreApiReference/latest/ApiOverview/Database/QueryBuilder/Index.html#quoteidentifier-and-quoteidentifiers
It's fine to use this QueryBuilder inside Extbase repositories. After this you can use the DataMapper to map the query results to Extbase models.
In case of using "statement()" be aware of escaping every value which may cause any kind of SQL injections.
Based on the current architecture of TYPO3, the data structure is such that comparing of two tables or, mixing results from two tables ought to be done from within the controller, by injecting the two repositories. Optionally, you can construct a Domain Service that can work on the data from the two repositories from within the action itself, in the case of a routine. The service will also have to be injected.
Note:
If you have a foreign relation defined in your table configuration, the results of that foreign relation will show in your defined table repository. So, there's that too.

OOP web application class design

I'm trying to build a web application using OOP.
In my application i have Courses and Subscribers.
Each Course can have multiple Subscribers (1-N relation).
Now i need to perform some operations on Courses (check some expire dates and perform actions on it's subcribers, send some emails to admins) and, after performing them, perform other operations on each Subscriber (send emails).
I created a Course class and a Subscriber class.
Course class contain course data like title, dates, current status and a group of Subscriber objects (those who partecipate to it).
Subscriber class contains name, last name, subscription status etc.
I have a problem.
My Course class need to be aware of it's Subscribers.
My Subscriber class need to be aware of the Course it belongs to (to exctract data like title, dates...) and aware of how much subscribers are and their status.
How can i redesign my class structure to solve this?
I was thinking about using some kind of observer pattern...
PS. i'm using PHP
No need for a special design pattern, this is a normal bidirectional association. I get from your description that any subscriber only subscribes to one course, otherwise there should be two classes Student and CourseSubscription instead.
How to simply construct the association in PHP:
class Course
{
/**
* #var Subscriber[]
*/
protected $subscribers = array();
public function addSubscriber(Subscriber $subscriber)
{
$this->subscribers[] = $subscriber;
}
}
class Subscriber
{
/**
* #var Course
*/
protected $course;
public function __construct(Course $course, $name, ...)
{
$this->course = $course;
$course->addSubscriber($this);
$this->name = $name;
...
}
}
A subscriber object can only exist with a course, so you pass the course as parameter to the constructor. There the newly created subscriber registers itself for the course.
it sounds like an observer pattern till the point you said
and aware of how much subscribers are and their status.
You need a version of Observer Pattern which expose limited information of its observers.
This point is something like for each subscriber of a course , you need to get all of the subscribers from the course object so you can create a method in Course object which will give you the limited information for each subscriber of the course.

Retrieving data from WCF service

Let's say I have database with two tables - Groups and Items.
Table Groups has only two columns: Id and Name.
Table Items has three columns: Id, GroupId and Name.
As you can see, there is one-to-many relation between Groups and Items.
I'm trying to build a web service using WCF and LINQ. I've added new LINQ to SQL classes file, and I've imported these two tables. Visual Studio has automatically generated proper classes for me.
After that, I've create simple client for the service, just to check if everything is working. After I call GetAllGroups() method, I get all groups from Groups table. But their property Items is always null.
So my question is - is there a way to force WCF to return whole class (whole Group class and all Items that belong to it)? Or is this the way it should behave?
EDIT: This is function inside WCF Service that returns all Groups:
public List<Group> GetAllGroups()
{
List<Group> groups = (from r in db.Groups select r).ToList();
return groups;
}
I've checked while debugging and every Group object inside GetAllGroups() function has it's items, but after client receives them - every Items property is set to null.
Most likely, you're experiencing the default "lazy-loading" behavior of Linq-to-SQL. When you debug and look at the .Items collection - that causes the items to be loaded. This doesn't happen however when your service code runs normally.
You can however enforce "eager-loading" of those items - try something like this:
(see Using DataLoadOptions to Control Deferred Loading or LINQ to SQL, Lazy Loading and Prefetching for more details)
public List<Group> GetAllGroups()
{
// this line should really be where you *instantiate* your "db" context!
db.DeferredLoadingEnabled = false;
DataLoadOptions dlo = new DataLoadOptions();
dlo.LoadWith<Group>(g => g.Items);
db.LoadOptions = dlo;
List<Group> groups = (from r in db.Groups select r).ToList();
return groups;
}
Do you get the Items collection populated now, when you call your service?

Mapping two tables to one entity in Doctrine2

I'm looking at using doctrine for an application I'm working on - but after reading the documentation I'm having trouble conceptualizing how to represent the database structure we have in terms of entities.
I have many tables which have partner tables which hold translation data like the following....
Where I would like to have one Entity (Navigation Element) which had access to the 'label' field depending on what Language I set in my application. The following from the Doctrine documentation seems to suggest that you need to define one (single) table which is used to persist an entity
http://www.doctrine-project.org/docs/orm/2.0/en/reference/basic-mapping.html
By default, the entity will be
persisted to a table with the same
name as the class name. In order to
change that, you can use the #Table
annotation as follows:
Or do I need to define two entities and link them (or allow the translation table to inherit from the element table).
And what strategy would I use to always insert a language_id clause to the Join (to ensure I'm pulling the right label for the currently set language). Is this something I would define in the entity itself, or elsewhere?
This seems to suit a One-To-Many Bidirectional association. This is the scenario from that page translated to your situation:
/** #Entity */
class NavigationElement
{
// ...
/**
* #OneToMany(targetEntity="NavigationElementTranslation", mappedBy="navigationElement")
*/
private $translations;
// ...
public function __construct() {
$this->translations = new \Doctrine\Common\Collections\ArrayCollection();
}
}
/** #Entity */
class NavigationElementTranslation
{
// ...
/**
* #ManyToOne(targetEntity="NavigationElement", inversedBy="translations")
* #JoinColumn(name="navigation_element_id", referencedColumnName="id")
*/
private $navigationElement;
// ...
}
You could add a getLabel($languageId) method to the NavigationElement entity that searches through the translations to get the correct label:
public function getLabel($languageId) {
foreach($this->translations as $trans) {
if($trans->languageId == $languageId)
return $trans->label;
}
throw new InvalidArgumentException();
}
And you could use the following DQL to ensure you only load the translation you want into the $translations property:
$query = $em->createQuery(
"SELECT ne, net
FROM Entity\NavigationElement ne
JOIN ne.translations net WITH net.languageId = :langId"
);
$query->setParameter('langId', $languageId);
$navigationElements = $query->execute();
This situation sounds like one where you would want to cache aggressively. Make sure you look into Doctrine 2's caching mechanisms too.
Also, internationalization can be handled reasonably well in PHP with gettext if you find join tables for translations start to become unmanageable.
I would also direct anyone who has to tackle this same problem to take a look at the following doctrine extension.
http://www.gediminasm.org/article/translatable-behavior-extension-for-doctrine-2