ZF-5655: expunge() missing in Zend_Mail_Storage_Imap & remove expunge call inside removeMessage()



I'm using Zend_Mail_Storage_Imap to periodically check a mailbox for new mails.

First thing I see: There is no explicit function to expunge. Because I have to be sure to work with "correct" data, I need to do an expunge directly after connecting. So I had to write my own:

 * Expunge deleted messages in current folder
 * @return null
 * @throws Zend_Mail_Storage_Exception
public function expunge()
    if (!$this->_protocol->expunge()) {
        throw new Zend_Mail_Storage_Exception('expunge failed');

I also need to parse all emails and remove them. If I loop through all mails with foreach ($storage as $id=>$message) { and call removeMessage() inside this loop, I get an exception! This is caused by expunge() directly after/inside removeMessage().

Example: Assume we have the following mailbox: Unique ID | ID | Subject 12345 | 1 | First mail 12349 | 2 | Second mail 12358 | 3 | Third mail

Assume we have the following code:

foreach ($storage as $id=>$message) {
    // do something with message. parse and put into database e.g.

What happens: We first get the mail with ID 1. Parse it, and then remove it (set flag and call expunge). Then the next foreach-loop gets the mail with ID 2. But this is not "Second mail", it is "Third mail", because expunge reorganizes all mails, and "Second mail" becomes ID 1 and "Third mail" becomes ID 2. So we are not correctly looping through all mails. In the third loop, there is an exception: Uncaught exception 'Zend_Mail_Protocol_Exception' with message 'the single id was not found in response' because ID 3 (which exists at start) doesn't exists in the third loop (reorganization, ID3 becomes ID1 in second loop after removeMessage())

Conclusion: It would be best to provide an expunge() function to the developer, and let him decide when to call it. We also need to remove automatic expunge in removeMessage(). Then the developer can call expunge() right after connecting or before closing. We don't get the failure described above if we call expunge() before close(), and not after/in each removeMessage()...



Actually the behavior is the same is every storage class. If you want to remove more than one message and use the message number it's better to remove them last to first:

for ($i = count($storage); $i; --$i) {

If someone has the time to implement a message set class we can support UIDs and multiple messages in a transparent manner. Your code could become:

$storage->removeMessage($storage->getSet()->range(1, '*'));

It's clean and can be supported by all storage classes. Having one storage class behave differently is something that should be avoided if possible. That's why there is an automatic expunge.

Doesn't appear to be a bug so much as a possible improvement. Would suggest adding as an improvement for ZF2 perhaps but marking as Won't Fix since it's simple to do as described by reporter.