Re-sorting a CActiveDataProvider - php

I am looking for a way in Yii to re-sort a CActiveDataProvider.
Currently I am using the the data provider to fetch sorted and paginated data.
I am then looping through it with a foreach loop adjusting data for specific fields, and now I need to re-sort it based upon the new adjusted data.
I can't sort the data in the model using afterFind because I need to query another DB (MySQL) to work out a calculated value and it doesn't seem to like the switching during the processing.
I don't want to use an CArrayDataProvider because there is no obvious way to paginate the data which comes back unless I put controls into a loop while adjusting the data, however I don't know how much data could come back, say 200 records, but the only adjusting 20 for the display seems counterintuitive.
This all is then pushed to a CGridView widget.
so now I need to readjust on the array below in ascending order for example.
$dataProvider = new CActiveDataProvider( blah );
foreach ( $dataProvider->getData() as $data ) {
$data->Score += SomeModel::model()->findByPk(1)->NewScore;
}
array(
'Score' => 7
),
array(
'Score' => 6
)
$this->render('blah', array('data' => $dataProvider);

It sounds like you should simply implement a new CDataProvider of your own that wraps an existing CDataProvider. You will need to override several methods, but for most of them you can simply forward to the wrapped data provider. The code might end up looking something like this:
$dataProvider = new CActiveDataProvider(...);
$myProvider = new CustomDataProvider($dataProvider);
// you could make CustomDataProvider work like this:
$myProvider->dataMutator = function(&$row, $key) {
$row->Score = SomeModel::model()->findByPk(1)->NewScore;
};
$this->render('blah', array('data' => $myProvider);
CustomDataProvider::fetchData might look like this:
protected function fetchData()
{
$data = $this->wrappedProvider->fetchData();
array_walk(&$data, $this->dataMutator);
uasort($data, function($a, $b) { return $a->Score - $b->Score; });
return $data;
}
I 've hardwired the sort here -- this works for prototyping, but to specify the post-processing sort cleanly is not trivial because:
CDataProvider exposes a sort property which is ignored. Integrating that property fully would either be non-trivial work (you have to write code that respects multiple sort criteria etc) or change the semantics of CustomDataProvider.sort compared to sort on its base class. CDataProvider is married to CSort, so if you wanted to go for the cleanest solution from an OO perspective it would be best to implement IDataProvider from scratch.
It's not intuitive to the user of CustomDataProvider how sorting works if a single 'sortproperty is used, becausesort` cannot reflect both what the wrapped data provider will do and what the "post-processing" sort will do at the same time; however, both of the above will affect the end result.
I suggest that you get a prototype working and then have a good think about what the object model should be.

Related

Return an associative array with certain key for a findBy doctrine repository function

I want this function
$entityManager
->getRepository('AppBundle:Example')
->findBy(array('id' => $id, 'active' => true));
to return an associative array of Example objects with ID as keys.
For example:
array(
'100' => 'Example object 1',
'135' => 'Example object 2'
)
I can achieve this using foreach after retrieving the data, but I'd like to know if there is a proper way to do this.
You need to set up a custom method in your Repository, and there you can pass a second parameter to createQueryBuilder(), like this:
public function findActiveObjectsIndexedById(): array
{
return $this->createQueryBuilder('o', 'o.id')
// ...
->getQuery()
->getArrayResult();
}
I just learned this from https://stackoverflow.com/a/51689187/1668200 and couldn't find any better documentation than https://www.doctrine-project.org/api/orm/latest/Doctrine/ORM/QueryBuilder.html#method_indexBy
What you need for doing what you're suggesting, would be to define your own Custom Hydrator. This allows you to define the logic on how to prepare the result after fetching it from the storage engine.
But before going that route, I think you should ask yourself: What's the use case that desperately needs introducing another layer of complexity to my application in order to get the result in a particular way directly from the database? Do you have critical performance requirements? If so, please go ahead with the custom hydrator. Otherwise, just map your results from the objects into the desired structure before returning it, encapsulate that logic into its own class and unit test it.
If you want to skip the hydration into objects step completely, simply fetch the results directly into an array with $query->getArrayResult().

Message "You should never see this warning." in Extbase-based Typo3 extension

I have an Extbase-based extension for Typo3, which has a hierarchical data model. I had to insert an additional layer to this model, ie the original structure was Project contains multiple items. Now, I have Project contains multiple sub-projects and Sub-project contains multiple items. Everything is modelled using MM-relation tables and works in the backend. I can add, remove, sort the sub-projects and items.
However, the fluid template does not show anything and if I pass eg the sub-Project to t3lib_utilities_debug::Debug, I get
You should never see this warning. If you do, you probably used PHP
array functions like current() on the
Tx_Extbase_Persistence_ObjectStorage. To retrieve the first result,
you can use the rewind() and current() methods.
when printing the ObjectStorage for the items. I assume that the MM-relation I added is somehow broken, but I cannot figure out how. Furthermore, it seems that the __construct method of the domain model is not called (I have added a debug output, which is not printed).
The enumeration works if I pass the result of a call to findAll of the repository, but it does not work for my filtered calls (which worked before I added the additional layer). The filtering method looks like eg for the item
public function findBySubProject(SubProject $p) {
$query = $this->createQuery();
$query->getQuerySettings()->setRespectStoragePage(false);
$query->matching($query->equals('subproject', $p));
return $query->execute();
}
As I said, the query yields results, but they are somehow broken wrt. their relations.
Any ideas how to fix that?
I don't know on which version of Extbase you're developing on.
But on TYPO3 4.6+ you should be aware of the object and reflection caching. During development you can disable this caching by:
$GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['extbase_reflection']['backend'] = 't3lib_cache_backend_NullBackend';
$GLOBALS['TYPO3_CONF_VARS']['SYS']['caching']['cacheConfigurations']['extbase_object']['backend'] = 't3lib_cache_backend_NullBackend';
Since your problem has something to do with modifications in your model you should try to truncate the tables cf_extbase_object, cf_extbase_object_tags, cf_extbase_reflection and cf_extbase_reflection_tags after any change.
If this doesn't help you to resolve your problem then you should give us more insight about your configuration (especially the TCA configuration because Extbase rely on it).
How to test a Extbase QueryResult
$items = $this->itemRepository->findAll();
echo count($items);
if ($items) {
echo '<pre>';
foreach ($items as $item) {
print_r($item);
}
echo '</pre>';
}
-- edit --
Did you define the field subproject in your TCA? It should be atleast available as type passtrough:
'subproject' => array(
'config' => array(
'type' => 'passthrough',
),
),
I case someone else encounters the same issue: I accidentally used an object without items as test object. If you try to enumerate/debug/display an empty ObjectStorage, the warning is printed.

Is there an easier way to iterate through a list of objects properties in Laravel

I have a Laravel app where I am using a bit of code which feels really unintuitive.
In the code I return a list of objects ($occupied) which all have the the column 'property'. I then go on to create an array of the list of objects 'property's ($occupiedproperty) just to use it in a whereNotIn call.
if ($occupied = Residency::currentResidents()){
// Here is the pointless part //////
$occupiedproperty = array();
foreach ($occupied as $occ) {
array_push($occupiedproperty, $occ->property);
}
///////////////////////////////////
return Property::whereNotIn('id', $occupiedproperty)->get();
}
This code works fine to do the job but creating a new array when I already have a list of objects seems lazy. I tried looking at eloquent's documentation but I couldn't figure this out.
I need to be able to access the 'property' column of $occupied so I can run something like whereNotIn('id', $occupied->property)
Thank you
Can't test it right now, but this should work (it should work even without casting to array the $occupied collection):
$occupiedProperties = array_pluck((array)$occupied, 'property');
It uses the array_pluck() helper method: http://laravel.com/docs/4.2/helpers#arrays

Doctrine_Query, criteria as objects, & the Visitor Pattern. Possible?

I've been working on a bunch of projects lately with Doctrine 1.2 integrated into Zend Framework 1.11, and it has been really fun.
One of the most common methods I have implemented in my service layers are methods which returns a collection of my domain models according to a set of criteria being passed as arguments.
I've been creating them with interfaces like this:
//All books
$books = $service->getBooks();
//Books under certain categories and authored which matches the search term 'Hitchens'
$books = $service->getBooks(array(
'category' => array(1,2,3,4),
'author' => array('like' => '%Hitchens%', 'diedBefore' => Zend_Date::now()),
'orderBy' => 'bookTitle',
'limit' => 10
));
And the implementation look like this:
public function getBooks(array $options = null)
{
$query = Doctrine_Query::create()->from('LibSys_Model_Book as book');
if($options !== null){
if(isset($options['author']){
//Manipulate Doctrine_Query object here....
}
//More 'ifs' and manipulations to the Doctrine_Query object...(additional criterias, orderBy clauses, limits, offsets, etc.)
}
}
And as my the need for more criteria rises, the messier the implementations get. And needless to say, there are a lot of code re-use everywhere, and updating the code is so such a tedious task.
I've been thinking that it would be a lot better if I can pass in the criteria as objects instead of array segments, utilizing the Visitor Pattern with something like this:
$authorCriteria = new LibSys_Criteria_Author();
$authorCriteria->like('%Hitchens%');
$authorCriteria->diedBefore(Zend_Date::now());
$books = $service->getBooks(array(
$authorCriteria,
new LibSys_Criteria_Category(array(1,2,3,4))
));
With LibSys_Criteria_Category and LibSys_Criteria_Author implementing a common interface which might look like this:
interface LibSys_Doctrine_Criteria_Interface
{
public function applyCriteria(Doctrine_Query $query);
}
Therefore, it will just be a matter of looping through the set of criteria objects provided and pass along the Doctrine_Query object to them as they take turns in manipulating it to suit the needs:
public function getBooks(array $criteria = null)
{
$query = Doctrine_Query::create()->from('LibSys_Model_Book as book');
if($criteria !== null){
foreach($criteria as $criterion){
if($criterion instanceof LibSys_Doctrine_Criteria_Interface){
$criterion->applyCriteria($query);
}
}
}
}
This way not only does it make them easier to use, but also make the services extendible, the criteria objects reusable, and everything much easier to maintain.
In order to make this work robustly, though, one would need to be able to inspect the Doctrine_Query object and check any existing joins, conditions, etc. This is because it is not impossible that two totally different criteria may require same set of conditions or same set of joins. In which case, the second criteria can either simply re-use an existing join and/or adjust accordingly. Also, it wouldn't be hard to imagine cases where a criterion's set of conditions are mutually incompatible with another, in which case should should warrant an exception to thrown.
So my question is:
Is there a way to inspect a Doctrine_Query and get information regarding which components has been joined, or any set of conditions already applied to it?

MVC Fundamentals: Passing work along to the view?

I'm using Doctrine2 and CodeIgniter2, and am new to both, as well as to OOP/MVC, so please use simple explanations :)
For testing purposes, I have a Controller a Model and a View. I want to display data from a table that contains user information. First name, last name, ID number, and so forth.
My controller makes a call to the model- which retrieves data from the doctrine entity, and then the controller passes that data to the view.
(controller)
class Test_v_to_m extends CI_Controller {
public function index() {
$this->load->model('testing/test_v_to_m_model');
$data = $this->test_v_to_m_model->display_user_info();
$this->load->view('testing/test_v_to_m_view', $data );
}
}
(model)
class Test_v_to_m_model extends CI_Model{
public function display_user_name() {
$query = $this->doctrine->em->createQuery("select u from ORM\Dynasties2\Users u");
return $query->getResult();
(view)
//print_r($data);
First question is: How do I pass the object or array along to the view in a useful way? This works if I'm just dealing with a single variable:
(controller)
$user = $this->doctrine->em->find('Entities\User', $user_id);
$data['firstname'] = $user->getFirstName();
$this->load->view('testing/test_v_to_c_view_2',$data);
(view)
echo $firstname;
But I don't know how to do something similar when its an array, or a multidimensional array.
The second question is whether or not to let the view do any real work (php logic, loops, foreach, etc) or to do all of that in the controller and have the view only do formatting and display.
Yes, You can just pass multi-dimensional array to the view and then access it as required.
e.g.
$template_date['result_arr'] = array(
array('firstname' => 'abc', 'lastname' => 'xyz')
, array('firstname' => 'abc', 'lastname' => 'xyz')
);
in your view file -
foreach($result_arr as $key => $row) {
echo $row['firstname'].' <br />';
}
Re your 2nd question - As per my understanding - it's fine to use some foreach, for loops in the view but it's best if business logic is kept to controllers and models. Hope it makes sense to you.
As for your first question, I don't know the answer off the top of my head (sorry!). I would imagine, however, that an array can be passed as part of the data (as a single item), but you would need to iterate though it in the view (see below). Just a guess, however...
As for your second question, the principle of MVC is to have only display logic in the view - so all of the "real work" should be done in the controller.
Now, if you want to have a loop to display data in a table, that's "real work" being done in the view, but since it's part of formatting and display that would be acceptable.
Regarding your first question, it's actually quite simple:
$data = array(
'firstname' => 'string',
'array' => array(1, 2, 3),
'multidimensional_array' => array('ocean' => 'deep')
);
In the view, you can access these as:
$firstname;
$array;
$multidimensional_array;
They're just exported to the view, so you can treat each key in the $data array as a variable, and the values in the $data array as the variables' values.
Regarding the second question, it is generally best if you have the view only do formatting and display. In some cases, it might be useful to use ifs or loops, for example, if you want to display different messages based on a certain variable, or if you want to fill a table with a bunch of rows. However, I strongly recommend that you keep out as much logic as possible. The view is meant to receive all the data it needs and display it in a way that suits it.
There are plenty of reasons for this, namely maintainability (if your logic changes, you don't need to update the view), reusability (if you make views as general as possible, you can reuse them very easily) and even the ability to create new views or to replace that view with a different one, without worrying about the logic.
I hope this helps. :)

Categories