Welcome Guest, Not a member yet? Register   Sign In
[Deprecated] DMZ 1.6.2 (DataMapper OverZealous Edition)
#61

[eluser]OverZealous[/eluser]
[quote author="chadbob" date="1260881301"]Can someone help me figure out how to sort results by a related count?[/quote]

First, before I post anything, don't load models! They are already loaded automatically. It might not say this in the manual (although I thought it did, but I couldn't find it).

Also, if you've already called get(), don't call count(), because that runs another query. Instead use:
Code:
$my_count = count($t->report->all);

I haven't tested it, but try this using include_related_count (you'll need v1.6):

Code:
$s = new Statute();
$s->include_related_count('report')->order_by('report_count', 'DESC');
$s->get();
// to limit to the top 5, do this instead:
// $s->get(5);

foreach($s->all as $t){
    $t->report->get();
    $t->description;
    // because we used include_related_count, we can do this:
    $report_count = $t->report_count;
    // alternatively, if we hadn't used include_related_count:
    // $report_count = count($t->report);
}
#62

[eluser]The Hamburgler[/eluser]
Got a weird bug here, hopefully somebody will be able to shed some light on it.

I'm working on an XML extension plugin for the DMZ library. Calling to $obj->from_xml($xml_string) will convert an xml string into dm objects.

Code:
function from_xml($object, $xml_str, $fields = '', $callback = NULL)
{
        // get object class name
        $class = get_class($object);
        
        // have fields been defined to limit import?
        if(empty($fields))
        {
            // no, use all fields
            $fields = $object->fields;
        }
        
        // check for callback
        if(empty($callback))
        {
            $result = array();            
        }
        else
        {
            $result = 0;
        }
        
        // parse xml string into xml object
        $xml = new SimpleXMLElement($xml_str);
        
        // loop through all records in the xml document
        foreach($xml->{$object->table}->children() as $record)
        {
            // create the object to save
            $o = new $class();
            
            // loop through all defined fields
            foreach($fields as $field)
            {
                // check field element is present
                if (isset($record->{$field}))
                {
                    $o->{$field} = $record->{$field};
                    //log_message('error', 'Record = '.$record->{$field});
                }
            }
            
            if(empty($callback))
            {
                $result[] = $o;
            }
            else
            {
                $test = call_user_func($callback, $o);
                
                if($test === 'stop')
                {
                    break;
                }
                if($test !== FALSE)
                {
                    $result++;
                }
            }
        }
            
        return $result;        
}

This seems to be working fine, i've used a similar technique to the included csv and json extensions. The method returns an array of new dm objects with each field set.

In my controller code when I loop through this array and attempt to save each object I get a CI database error!
Code:
INSERT INTO `people` (`first_name`, `last_name`, `email`, `created`, `updated`) VALUES (Terry, Test, [email protected], '2009-12-15 11:26:12 +0000', '2009-12-15 11:26:12 +0000')

Obviously the error here is that the constructed sql is not escaping the name and email string values... Why???
#63

[eluser]OverZealous[/eluser]
@The Hamburgler

I'm guessing the values you are assigning from the XML object aren't actually strings. You might need to force coersion for this line:
Code:
$o->{$field} = $record->{$field};

becomes:
Code:
$o->{$field} = (string)$record->{$field};

You can verify this by using var_dump on the result.

If that isn't the case, then you have a problem in your controller or model.
#64

[eluser]The Hamburgler[/eluser]
arrrgh damit!
Yup you're right, The SimpleXMLElement does not return a string.

Casting the value to a string:
Code:
(string)$record->{$field};
Resolved the issue, thanks!
#65

[eluser]12vunion[/eluser]
What would be an optimal way to get objects that are missing an association?

Example: I have a question object. Each question could have many responses (associated to many response objects). I want to get all of the questions that have no responses.

This is probably a task for subqueries, but is there a faster, more optimal way of doing this?
#66

[eluser]OverZealous[/eluser]
[quote author="12vunion" date="1260923840"]What would be an optimal way to get objects that are missing an association?

This is probably a task for subqueries, but is there a faster, more optimal way of doing this?[/quote]

Try this:
Code:
$q = new Question();
$q->where_related('response', 'id', NULL);
$q->get();

If that doesn't work, here's an example with subqueries:
Code:
$r = new Response();
$r->select('COUNT', '*', 'count');
$r->where_related('question', 'id', '${parent}.id');

$q = new Question();
$q->where_subquery($r, 0);
$q->get();

It's a bit manual, but it should work. I might look into making a special case for ${query}_related_count that is based on include_related_count.
#67

[eluser]12vunion[/eluser]
[quote author="OverZealous" date="1260927013"]
Try this:
Code:
$q = new Question();
$q->where_related('response', 'id', NULL);
$q->get();
[/quote]
Amazingly, this works on join tables. Thanks a lot.

[quote author="OverZealous" date="1260927013"]
I might look into making a special case for ${query}_related_count that is based on include_related_count.[/quote]
This sounds like a winner.
#68

[eluser]TheJim[/eluser]
First, Phil, thanks a lot for DMZ. I'm glad I found it, because I liked DM, but I was getting frustrated with its limitations, and you've basically done most of what I was starting to plan out in my mind. Best of all, you've implemented it all rather well.

That said, I do have some bug fixes to contribute. Since they're relatively small, I think they should fit in the post just fine, but if it's not clear, I'd be happy to send a patch or whatever works for you. Maybe you've already caught these, but here we go:

Code:
(Line 1400)
    function _delete($related_field, $arguments)
    {
        $this->delete($arguments[0], $related_field);
    }

This should have a return so we pass through the success/failure when deleting relations.

Next, I believe the array handling in _save_itfk should really be:

Code:
(Line 1112)
        if(is_array($o))
        {
            $this->_save_itfk($o, $rf);
            if (empty($o))
            {
                unset($objects[$index]);
            }
            else
            {
                $objects[$index] = $o;
            }
        }

so that any unsets that take place recursively are carried through.

And finally, another ITFK problem that I'm not positive about. I might be wrong about this, but instantiating a related object when relating via ITFK seems to leave out the related object's ID because of the field collision detection (as the related record's ID field has the same alias as the ITFK field). The changed code with the instantiation check moved up:

Code:
(Line 3726)
        foreach ($fields as $field)
        {
            $new_field = $append . $field;

            if($instantiate) {
                $property_map[$new_field] = $field;
            }

            // prevent collisions
            if(in_array($new_field, $this->fields)) {
                continue;
            }
            if (!empty($selection))
            {
                $selection .= ', ';
            }
            $selection .= $table.'.'.$field.' AS '.$new_field;
        }

That way we include the related object's ID into the instantiation field map, but we still don't select the matching field in the query. Like I said, this one I'm not positive about. Maybe I'm not noticing something, but it seems to me that this is the way it should work.

Additionally, I hope I don't sound too critical, but in using DMZ on a site where I sometimes to have instantiate a couple hundred objects for a page (online store with product category galleries with images via include_related), I wasn't completely thrilled with the performance (which is a downside of DM in general). So, I spent a few hours with a profiler to see if I could get the low-hanging fruit, and if you're interested in the results of that, I'd be happy to share with you my findings and code changes. There are some significant performance gains that can be had with fairly minor code changes. But those changes would probably be better communicated outside of a forum post.

Thanks again for all your work on DMZ.

Jim
#69

[eluser]TheJim[/eluser]
And, not to take over the thread, but someone might be interested in this. Like I said above, DM could be more efficient when instantiating more than a few objects. Besides general performance enhancement, I wanted to optimize the common case of looping over objects, maybe accessing some class methods on them or whatever, but not caring about the objects outside of the loop. That is, storing a hundred objects in the all array (and going through full instantiation on each) -- although it makes for a very simple API -- is often a bit of a waste of both memory and processing, so I came up with the following. Unfortunately, it won't work in an extension, as _process_query has to be overloaded:

Code:
class Extended_DataMapper extends DataMapper
{
    protected $capture_query = FALSE;
    protected $query = NULL;

    // Same as get, except that in many cases, we're looping through results and don't need to store an object
    //   beyond that, so this will return an iterator that can be used to loop without storing objects
    function get_streaming($limit = NULL, $offset = NULL)
    {
        $this->capture_query = TRUE;
        $this->get($limit, $offset);
        $this->capture_query = FALSE;

        $iterator = new DM_Iterator($this, $this->query);
        $this->query = NULL;

        return $iterator;
    }

    function _process_query($query)
    {
        if ($this->capture_query)
        {
            $this->query = $query;
            return;
        }

        parent::_process_query($query);
    }
}

// Used with get_streaming
class DM_Iterator implements Iterator, Countable
{
    protected $object;
    protected $result;
    protected $count;
    protected $pos;

    function __construct($object, $query)
    {
        $this->object = $object->get_clone();
        $this->object->clear();

        $this->result = $query->result();
        $this->count = count($this->result);
        $this->pos = 0;
    }

    function current()
    {
        $this->object->_to_object($this->object, $this->result[$this->pos]);
        return $this->object;
    }

    function key()
    {
        return $this->result[$this->pos]->id;
    }

    function next()
    {
        $this->pos++;
    }

    function rewind()
    {
        $this->pos = 0;
    }

    function valid()
    {
        return ($this->pos < $this->count);
    }

    function count()
    {
        return $this->count;
    }
}

which could be used as:

Code:
class User extends Extended_DataMapper
{
...
}

$u = new User();
$iterator = $u->where(...)->order_by(...)->get_streaming();
echo count($iterator), ' user record(s):<br/>';

foreach ($iterator as $user)
{
    echo $user->name, '<br/>';
}

So, the iterator that get_streaming returns can be mostly used in place of the "all" array, with the obvious exception that you can't access a particular element using array bracket notation. But foreach and count work as expected, and when it fits what you're doing, it's a large performance gain. Of course, since the iterator changes the values of a single object, you wouldn't want to store the returned objects and access them outside the loop without doing some cloning (and I don't think you'd want to do that either, as you'd have basically reinvented the standard get).

I haven't tested all functionality on the resulting object, so it's possible that some changes would have to be made to support everything DMZ does, but I believe since I just capture the get and use _to_object to copy a new row to the object, any necessary changes should be simple. In my mind though, I associate get_streaming with read-only loops just in case.

I hope someone finds that useful for dealing with a lot of objects.

Jim
#70

[eluser]OverZealous[/eluser]
@TheJim

I'm looking over your suggestions. The first is a simple mistake, thanks for catching it.

I don't agree that your second suggestion is necessary:
1) In the first case (an empty array), there is no harm, because at most you might get an extra function call. Since this is saving (not getting), there isn't any real performance boost.

2) In the second case (not empty), the array should always be edited by reference in PHP 5+, which is all that is supported. Re-assigning it isn't really necessary.

3) Finally, the saving isn't really intended to truly be recursive. It's just better than the old method (manually supporting N-levels deep).


I think you are correct in your third suggestion. However, using the solution you provided will not work, as all fields will be copied over, even if there is a collision. I'll work up a solution.

--------------------

As for your speed improvement suggestion, I think that has some real possibility. However, I don't think you really need to override the built-in DMZ method. Instead, take advantage of the get_sql method introduced in DMZ 1.6.

Your code (as an extension) would look like this:
Code:
function get_streaming($object, $limit = NULL, $offset = NULL)
{
    $sql = $object->get_sql($limit, $offset);
    $query = $object->db->query($sql);
    $iterator = new DM_Iterator($object, $query);
    return $iterator;
}

It has one limitation (which you can manually copy from the get method, or just document): traditional related queries won't work. This means that queries like this:
Code:
$user->bug->get_streaming();

Would have to be rewritten as:
Code:
$bug = new Bug();
$bug->where_related($user)->get_streaming();

If you want to duplicate the existing functionality, look at the first if block of the get method.




Theme © iAndrew 2016 - Forum software by © MyBB