Criteria and caching in Doctrine - php

I have a doctrine entity called Site with the following field:
/**
* #ORM\OneToMany(targetEntity="SiteAsset", mappedBy="site")
*/
protected $assets;
I also have a function for getting an element of $assets with a given value of its url field:
public function getAssetByUrl($url) {
$c = Criteria::create()->
where(Criteria::expr()->eq('url',$url));
$matching = $this->assets->matching($c);
return $matching[0];
}
This function behaves very strangely. It appears to work if I run it immediately after fetching the entity from the database. But after a few database operations have been queued up, it begins to fail. I know it is failing, as I can find the asset I want as follows:
public function getAssetByUrl($url) {
foreach($this->assets as $asset) {
if($asset->getUrl() === $url) {
return $asset;
}
}
}
Furthermore, if I combine the two functions into:
public function getAssetByUrl($url) {
foreach($this->assets as $asset) {
if($asset->getUrl() === $url) {
error_log('found');
}
}
$c = Criteria::create()->
where(Criteria::expr()->eq('url',$url));
$matching = $this->assets->matching($c);
error_log($matching[0] ? 'found' : 'not found');
return $matching[0];
}
Then the Criteria always fails to find a match (i.e., when the function is called it always prints out 'found' followed by 'not found'). This would suggest that Doctrine is failing to find the entity I want in the case when it has already cached the entities in memory.
How can I ensure that matches are always found, while still making use of Doctrine's Criteria filtering system?

The issue was with the following line:
return $matching[0];
This returns the right answer if the PersistentCollection has not yet been fully fetched from the database. This is because the criteria is converted to an SQL query that returns an array containing only matching items; therefore getting the item at index 0 succeeds.
This returns the wrong answer if the PersistentCollection has already been fully fetched. This is because, internally, doctrine calls array_filter on the in-memory collection and returns the result. This means that the first element in the result is not necessarily at index 0, as array_filter preserves keys.
The solution was to wrap $matching in array_values($matching).

Related

How to count specific objects in an array collection based on a getter that return a string

I have an array of student objects. Each student has subjects as an array collection. Each subject has a function getStatus() that is calculated based on different arguments etc, thus there is no real property for status in the subject entity.
How can I count the number of subjects that are completed, in progress, and pending and display it per student in a table?
I could retrieve my students in my controller like this:
$students = $em->getRepository(Student::class)->findAll();
and then perhaps with loops count it somehow, but I don't get how.
I thought of creating a function that implements the filter on the array collection like seen in this answer, but I don't understand how to use that to filter on getStatus().
I also thought to implement ->matching with a criteria like this:
public function getSubjectsByStatus(string $status): ?Collection
{
$subjects = $this->subjects->matching(
Criteria::create()->where(Criteria::expr()->eq($this->getStatus(), $status))
);
return $subjects ?? null;
}
and then do a count on the returned collection, but the first parameter of eq() should be a string and I don't have a status property in the subject entity that can be used as a string, and adding a property now is not a good idea.
How can I count all subjects, pending subjects, completed subjects, and in progess subjects the best way?
You probably should consider making your status value an actual field in your database, since your problem would be easy to solve with a SQL/DQL query.
Without that being the case, here is how you could implement your getSubjectsByStatus method:
public function getSubjectsByStatus(string $status): ?Collection
{
return $this->tl1Configs->filter(function ($element) use ($status) {
return $element->getStatus() == $status;
});
}
But if you call that method three times to just count the amount of all status values, you are looping over your collection three times as well.
A "better" solution would probably be to make a specialized method to explicitly get these counts. This is just one way of achieving what you want though. A method to return an array of sub-collections instead of just status counts could is another solution if you want to actual work with the sub-collections (all depends on your actual usecase).
public function getSubjectStatusCounts(): array
{
$statusCounts = [];
foreach ($this->tl1Configs as $subject) {
$statusCounts[$subject->getStatus()] = ($statusCounts[$subject->getStatus()] ?? 0) + 1;
}
return $statusCounts;
}

Iterating through an array to return a id field from the database symfony2 controller method

I am trying to iterate over the arrays from the database doctrine query. On getting the value of the id field, set it as the return type value of the method. On getting the endpoint uri it returns status 200 - the response is empty
This is my attempt.
/**
* #Route("/get/{email}")
*/
public function emailAction($email)
{
$var = "";
$singleresult = $this->getDoctrine()->getRepository('Api3Bundle:Influ')->findOneBy(array('email' => $email));
if ($singleresult === null) {
return new View("user not found", Response::HTTP_NOT_FOUND);
}
//area of problem
foreach($singleresult as $users)
{
$var = $users.id;
}
return $var;
//end of area of problem
}
I am trying to use email to return the id. The above attempt show through postman that the response is empty
If $singleresult has just one result, then why are you using foreach() on it?
If $singleresult has more than one result, you are going to loop through them all and return the last one (because all previous $var's will be overwritten.
Can't you access it directly $singleresult->id without the loop?
If the id is empty, perhaps you could start doing some debugging by returning everything inside of $singleresult to see why there is no id.

With php finding out if variable is this exact class instance

I have run into trouble figuring out how to compare two variables which might contain the exact same class instance.
An abstract class (part of which is shown below) has a method fetch_mother() designed to identify the object that should contain it and return that or simply return itself because it is at the bottom of the stack. In theory, that stack should be no more than 5 deep.
Most instances represent things like categories.
With the get get_full_path() method:
Expected output was: [siteurl] /system/drafts/example-one/also-dev-notes/
Actual output was: [siteurl] /drafts/drafts/[snip]/drafts/drafts/example-one/also-dev-notes/
Which means that the sanity check kicks in and breaks a loop. It also means that I have not correctly tested for the returned object being the same as $this.
How can I confirm is $var===$this?
Code where the problem takes place:
<?php
namespace modules\content\classes;
use modules\core\interfaces as i;
use modules\core\classes as c;
abstract class content_object extends c\module_lib {
// vars
// ...
protected $mother;
protected $map
// ... code ...
public function get_object_map(){
return $this->map;
}
/**
* Get the stream holding this item
* #return \modules\content\classes\error|\modules\content\classes\content_object
*/
public function &fetch_mother(){
if(isset($this->mother) && is_object($this->mother)){
return $this->mother;
}
$mother = $this->module()->find_object_stream($this);
if(!($mother instanceof \modules\core\error) && is_object($mother) && $mother != $this){
$this->mother = $mother;
return $mother;
}else{
// I am my own mother ? \\
return $this;
}
}
protected function fetch_full_path_from_mother($path='',$sanity=10){
$map = $this->get_object_map();
$mother = $this->fetch_mother();
$path = $map . '/' . $path;
if($this==$mother || !is_object($mother) || $sanity<1){
return $path;
}
$sanity--;
return $mother->fetch_full_path_from_mother($path,$sanity);
}
public function get_full_path(){
$home = $this->get_core()->factory()->get_config('home');
return $home . $this->fetch_full_path_from_mother();
}
}
The answer here is non-obvious.
<?php
$foo = $this;
if($foo==$this){
echo 'It is';
}else{
echo 'It is not';
}
The output of the above would be It is.
That's because if the two objects are the same instance then the == comparison would be enough to determine this.
Likewise (as per the comments) spl_object_hash($mother)==spl_object_hash($this) is also true only if it is the same object. However, if another object with the same properties were created the above would be false because they are separate objects.
This question and answers deals with that exact same topic: spl_object_hash matches, objects not identical
The assumption in my question (which I did not see at first) is that the lookup function is acting as a factory and caching objects. The differential conclusion must be that a copy or second instance is being returned.
Thus, the problem must be with the fetch_mother() method.
(Further investigation did indeed show that this was the problem.)
Solutions include checking for matching properties (which in this case works as there are several unique fields pulled from the database) or comparing print_r output.
if(print_r($mother,true)==print_r($this,true)){
// code
}
That particular solution is ugly, inelegant and not very reliable.
A better solution would be to implement an object cache higher up the stack. (Which is what I will be proposing).
TL;DR: Objects with identical properties are still not the same.

Is there another way than passing by reference to do what I want

I have the following data structure:
A Contract has an array projects, which can have X number of projects. Each Project has an array, subProjects, which contain the same Project type, so theoretically you could have an infinite tree of Project-SubProjects-Project...
Anyway, each project has a unique ID, and I need to search for a given project AND make a modification to that project, starting at the top level, and then store the changed contract back to my session. Currently, I'm doing it via a recursive function that returns a reference to the project it finds, but the more I'm searching, the more it seems people don't like PHP references. I'm not sure why, could someone explain the problems? Is there a better way to do what I want?
Some code:
// Get the associative array version of the contract (it's stored as JSON)
$contract = json_decode($contract, true);
if(array_key_exists('projects', $contract))
{
$resultProject = &$this->findProject($contract['projects'], $projectId);
if($resultProject)
{
$resultProject[$inputData['propertyName']] = $inputData['value'];
\Session::put('workingContract', json_encode($contract));
// return 200
}
}
// Return 404
/**
* Performs a depth-first search to find a project.
*
* #param array $projects
* #param $projectId
* #return null
*/
private function &findProject(array &$projects, $projectId)
{
foreach($projects as &$project)
{
if($project['_id']['$id'] == $projectId)
{
return $project;
}
if(array_key_exists('subProjects', $project))
{
$result = &$this->findProject($project['subProjects'], $projectId);
return $result;
}
}
$null = null; // TODO: shitty hack for inability to return null when function returns a reference. Need to rethink use of references in general. Is there another way???
return $null;
}
Why not just create an array with all your projects (a flat array), indexed by ID. Let each Project object have an ->id property that you can refer to. Problem solved?
Also, if the Project doesn't exist in the flat array, I see absolutely no problem in returning null.
class Contract {
private $projects_flat = array();
....
private function get_project($id) {
return (isset($this->projects_flat[$id]) ? $this->projects_flat[$id] : null)
}
}

Is there a way to check what type of a value a method returns?

I am writing a method which can call any method from any class (this process is dynamic).
In my method, I need to find out what type is the returned value, based on the returned value type,I will proceed on to the next step.
For example:
<?php
function identifyReturnType($className, $methodName) {
$result = $className->$methodName();
//Here I need to find out the $result type
}
?>
I have many classes where methods return bool, string, int etc.
and there are a few methods which do not return anything, those methods set the values in object or the object has resource pointer :
<?php
function getCategories() {
$this->query("SELECT * FROM categories");
}
function getItems() {
$this->query("SELECT * FROM items");
$this->getValues();
}
?>
PHP gettype($var) method finds out what is the value type but for this, my method must return a value. I have cases (as I explained above) where method just sets the query object.
Please share your ideas.
Thank you so much.
This really depends on your implementation. Some follow architecture where every function will return data as array. Even for query returned data is returned in small chunks of array. That is completely on how you optimize or write your script. Say you are getting all contacts and if you have say 10,000 contacts in DB and you return all in an array, thats a bad idea. Rather use pagination and return in small numbers if you want the function to return data as array.
I have had this issue, where we have a big web application written in PHP/Mysql. Over the time we have thousands of functions across different classes. Now we have to develop a REST API which will have different functionality. The main problem was we do not have used different functions to return query object, some to return array, some to return Boolean and so on. The API should return data as JSON. Now we have to choice use the existing code for different functionality or re-write new code for the API. The 2nd choice is more expensive so we are left with first choice. But the problem as I mentioned is far from over the methods will return different type and do we need to really write more codes to check which function is called and if the say function "xyz()" is called and we know its returning query object then loop through it generate array and then json. No thats a bad idea and will take a lot of effort and its better to write seperate code then.
So we follow the following approach.
Our api call looks like
www.api.oursite.com/api/v1/Resource/Method?param=....
Now we catch the Resource and Method where resource is a Class name and Method is a method name for that Class.
so we know we have to call Resource->Method()
Now we have a class called ResourceMethodMap.class.php and it contains the array as
static $resource_method_map = array(
"Users"=>array(
"getUserInfo"=> // gets the user info
array(
"return"=>"array",
"accessToken"=>true
)
),
....
...
)
So the API request processing code does something like
public function call_method($resource = "",$method=""){
if($resource == "") $resource = $this->get_resource();
if($method == "") $method = $this->get_api_method();
if (class_exists($resource)) {
$resource_obj = new $resource();
// Parse the method params as array
$param_array = $this->parse_method_params($resource,$method);
if(false !== $param_array){
$result = call_user_func_array(array($resource_obj, $method), $param_array);
}else{
$result = $resource_obj->$method() ;
}
return $this->process_return_data($resource,$method,$result,$resource_obj);
}else{
$this->setMessage("Invalid Resource");
return false ;
}
}
Here the function process_return_data() will do the returned data conversion as
function process_return_data($resource,$method,$ret_val,$resource_obj = NULL){
if(array_key_exists("return",ResourceMethodMap::$resource_method_map[$resource][$method])){
$return_type = ResourceMethodMap::$resource_method_map[$resource][$method]["return"];
$return_array= array();
switch($return_type){
case 'boolean':
if(false === $ret_val){
return false ;
}else{
if(is_array($ret_val)){
return $ret_val ;
}elseif(true === $ret_val){
return $ret_val ;
}else{
$return_array[] = $ret_val ;
return $return_array ;
}
}
break;
case 'array':
return $ret_val ;
break;
}
.....
}
}
So Yes it completely on the developer how they want their data to be returned. The above example is just one real time scenario how we have implemented.
I have posted the complete code her http://codepad.org/MPY1gVed have look
If i understood your question right you can do this by passing in an argument as a reference.
Here's an example i made for you, if it is any help.
http://php.net/manual/en/language.references.pass.php
Another solution can be to return an array with both the return value and the type.
Do you real need a method to call other methods? You could just instantiate the class and call it manually
In adittion i would recommend checking like so:
if(is_callable($className, $methodName)){
$className->$methodName();
}

Categories