How to serialize a RedBean object and then deserialize it for update?

2,183 views
Skip to first unread message

gregorinator

unread,
Mar 2, 2012, 7:34:00 PM3/2/12
to redbe...@googlegroups.com
I have what must be a dumb newbie question, but I haven't been able to
figure it out. It started with the document database MongoDB: Like,
I think, many others, I've been developing applications that pass a
document to the Web browser and then use jQuery to build a page via
DOM manipulation. It's been so productive and effective that I
started looking for an ORM so I could do the same with my relational
databases. When I found RedBean, about two weeks ago, I thought I was
in heaven. But since then I've been reading and trying various
techniques, and now I'm stumped.

My question is, using RedBean, how to I read an object from the
database, manipulate it with some external program or language (that
doesn't understand RedBean), and then save it back? Take a simple
example:

$invoice = R::dispense('Invoice');
$invoice->Customer = 'Greg';

$lineItems = R::dispense('LineItem', 2);
$lineItems[0]->LineNumber = 1;
$lineItems[0]->Amount = 2.50;
$lineItems[1]->LineNumber = 2;
$lineItems[1]->Amount = 10.00;
$invoice->ownLineItem = $lineItems;
$id = R::store($invoice);

Now I have an invoice bean with one attribute, Customer, and this
invoice has two line item beans, each with a LineNumber and an Amount.
In preparation for editing this data on my Web page, I can read it
and convert it to an array like this:

$exportInvoice = R::findOne('Invoice');
$invoiceArray = R::exportAll($exportInvoice);
$invoiceArray = $invoiceArray[0]; // ::exportAll() created an
array of one bean; we're only interested in the one
// if I print_r($invoiceArray), I get a nicely formed Invoice
array with an embedded ownLineItem array... looks good!

So far, so good. If I wanted, I could json_encode() the array and
send it to my Web page, then json_decode() the response to get the
edited array back. But let's do something simpler, and just edit the
array in PHP. Let's change the amount of line item 1 to 5.00:

foreach ($invoiceArray['ownLineItem'] as $lineItemId => $lineItem)
{
if ($lineItem['LineNumber'] == 1)
{
$invoiceArray['ownLineItem'][$lineItemId]['Amount'] = 5.00;
}
}

Now I want to convert it back to RedBean and save it:

$importInvoice = R::dispense('Invoice');
$importInvoice->import($invoiceArray);
// if you print_r($importInvoice) at this point, the ownLineItem
beans are there!
R::begin();
$id = R::store($importInvoice);
R::commit();

The ::store() throws this error: "SQLSTATE[HY000]: General error: 1
table `lineItem` already exists", which of course it does, but why
should RedBean be trying to create it?

I've been reading the documentation, searching this list's archive,
searching the Internet, and trying many, many workarounds, with no
success. BeanCan doesn't seem to include the own Beans. The last
thing I tried was based on some clever code on the RedBean site from
someone named Robin, which I can't seem to find again, but it
iterated through the PHP array before importing it, and saved the own
Beans in a separate array, and then unset them from $invoiceArray. I
did this, then imported what was left in $invoiceArray (which was just
the Invoice) to an Invoice bean, and then imported each LineItem array
extracted from $invoiceArray into a new LineItem bean, and then
associated the LineItem beans with the Invoice bean and attempted to
save it. That seemed like more hoop-jumping than I should have had to
go through, but it didn't work anyway: The LineItem beans didn't get
updated.

This seems like such a common requirement -- to be able to edit a bean
and its associated beans in some external screen or program, and then
update them back to the RedBean object -- that there must be some
simple solution that I'm missing. If someone can just point me to a
resource, I will be grateful.

gs

Jake Chapa

unread,
Mar 3, 2012, 5:50:50 PM3/3/12
to redbe...@googlegroups.com
R::dispense() creates a new bean. You want to load in the bean from the database first, then modify that, then save. Like so:

$importInvoice = R::findOne('Invoice', 'id=?', array($invoiceArray['id']));


$importInvoice->import($invoiceArray);
// if you print_r($importInvoice) at this point, the ownLineItem
beans are there!
R::begin();
$id = R::store($importInvoice);
R::commit();

> --
> You received this message because you are subscribed to the Google Groups "redbeanphp" group.
> To post to this group, send email to redbe...@googlegroups.com.
> To unsubscribe from this group, send email to redbeanorm+...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/redbeanorm?hl=en.
>

gregorinator

unread,
Mar 5, 2012, 11:08:50 AM3/5/12
to redbe...@googlegroups.com
On 3/3/12, Jake Chapa <jake...@gmail.com> wrote:
> R::dispense() creates a new bean. You want to load in the bean from the
> database first, then modify that, then save. Like so:
> $importInvoice = R::findOne('Invoice', 'id=?',
> array($invoiceArray['id']));
> $importinvoice->import($arrayinvoice);

That makes sense. Thank you! However, it didn't entirely clear up my
problems. First, I continued to get the error "table 'lineItem'
already exists", but I suspected that this might be caused by my use
of upper-case letters in the table names, so I changed all my table
names to all lower-case, the way they are in the examples in the
RedBean manual, and that did in fact clear up that error message.

Now, I read the bean from the database and then import from my array,
as above, but when I store it I get this error:

"Array may only contain RedBean_OODBBeans"

I did a print_r() on the $importinvoice bean after executing the
->import(), and I'm wondering if the problem might be that the first
ownLine objects aren't marked as RedBean_OODBBeans. This is the snip
from the print_r() (the full dump is at the end of this message:

[ownLine] => Array ( [0] => Array ( [id] => 1 [LineNumber] => 1
[Amount] => 5 [invoice_id] => 7 ) [1] => Array ( [id] => 2
[LineNumber] => 2 [Amount] => 10 [invoice_id] => 7 ) )

The line items are merely array elements, not RedBean_OODBBean Objects.

I will appreciate any insight. Thank you!

In case it would be helpful, this is the full print_r() of the invoice
bean, with the line own beans, after the ->import():

RedBean_OODBBean Object ( [null:RedBean_OODBBean:private] =>
[properties:RedBean_OODBBean:private] => Array ( [id] => 7 [Customer]
=> Greg [ownLine] => Array ( [0] => Array ( [id] => 1 [LineNumber] =>
1 [Amount] => 5 [invoice_id] => 7 ) [1] => Array ( [id] => 2
[LineNumber] => 2 [Amount] => 10 [invoice_id] => 7 ) ) )
[__info:RedBean_OODBBean:private] => Array ( [type] => invoice
[sys.id] => id [tainted] => 1 [sys.shadow.ownLine] => Array ( [1] =>
RedBean_OODBBean Object ( [null:RedBean_OODBBean:private] =>
[properties:RedBean_OODBBean:private] => Array ( [id] => 1
[LineNumber] => 1 [Amount] => 2.5 [invoice_id] => 7 )
[__info:RedBean_OODBBean:private] => Array ( [type] => line [sys.id]
=> id [tainted] => ) [beanHelper:RedBean_OODBBean:private] =>
RedBean_BeanHelperFacade Object ( )
[fetchType:RedBean_OODBBean:private] => ) [2] => RedBean_OODBBean
Object ( [null:RedBean_OODBBean:private] =>
[properties:RedBean_OODBBean:private] => Array ( [id] => 2
[LineNumber] => 2 [Amount] => 10 [invoice_id] => 7 )
[__info:RedBean_OODBBean:private] => Array ( [type] => line [sys.id]
=> id [tainted] => ) [beanHelper:RedBean_OODBBean:private] =>
RedBean_BeanHelperFacade Object ( )
[fetchType:RedBean_OODBBean:private] => ) ) )
[beanHelper:RedBean_OODBBean:private] => RedBean_BeanHelperFacade
Object ( ) [fetchType:RedBean_OODBBean:private] => )

Jake Chapa

unread,
Mar 5, 2012, 3:00:21 PM3/5/12
to redbe...@googlegroups.com
Yes it's because ownLine is not an array of redbeans, its an array of StdClass. How are you populating that?

gregorinator

unread,
Mar 5, 2012, 4:48:19 PM3/5/12
to redbe...@googlegroups.com
On 3/5/12, Jake Chapa <jake...@gmail.com> wrote:
> Yes it's because ownLine is not an array of redbeans, its an array of
> StdClass. How are you populating that?

This is the entire program that demonstrates the error. It's
completely self-contained -- it erases the tables and repopulates with
its own test data each time, so you can just run it as-is without
having to set anything up (except the SQLite database). I've removed
the code that changes the data after it's been exported to the array,
because that isn't necessary to cause the problem to occur. Thanks so
much for helping me out with this!

<?php

require_once 'phpclasses/rb.php';

try
{
R::setup('sqlite:ConfigurationProfiles.db'); //,'user','password');

// Start each test run by erasing the tables
R::wipe('invoice');
R::wipe('line');

// Create a new invoice for the test run
$invoice = R::dispense('invoice');
$invoice->Customer = 'Greg';
$lineArray = R::dispense('line', 2);
$lineArray[0]->LineNumber = 1;
$lineArray[0]->Amount = 2.50;
$lineArray[1]->LineNumber = 2;
$lineArray[1]->Amount = 10.00;
$invoice->ownLine = $lineArray;
$id = R::store($invoice);

// Export the bean to an array
$exportinvoice = R::findOne('invoice');
$arrayinvoice = R::exportAll($exportinvoice);
$arrayinvoice = $arrayinvoice[0]; // Since ::exportAll() can
export multiple objects, it creates an array. We only want the single
entry.

// Import the bean from an array and try to save it to the database
$importinvoice = R::findOne('invoice', 'id=?', array($arrayinvoice['id']));
$importinvoice->import($arrayinvoice);
print_r($importinvoice);print '<p>';
$id = R::store($importinvoice); // throws this error: "Array may
only contain RedBean_OODBBeans"

R::close();
}
catch (Exception $ex)
{
die ($ex->getMessage());
}

?>

Jake Chapa

unread,
Mar 5, 2012, 4:51:31 PM3/5/12
to redbe...@googlegroups.com
ownLine is imported as an array. Since you're not using it, simply unset it:

<?php

require_once 'phpclasses/rb.php';

try
{
R::setup('sqlite:ConfigurationProfiles.db'); //,'user','password');

// Start each test run by erasing the tables
R::wipe('invoice');
R::wipe('line');

// Create a new invoice for the test run
$invoice = R::dispense('invoice');
$invoice->Customer = 'Greg';
$lineArray = R::dispense('line', 2);
$lineArray[0]->LineNumber = 1;
$lineArray[0]->Amount = 2.50;
$lineArray[1]->LineNumber = 2;
$lineArray[1]->Amount = 10.00;
$invoice->ownLine = $lineArray;
$id = R::store($invoice);

// Export the bean to an array
$exportinvoice = R::findOne('invoice');
$arrayinvoice = R::exportAll($exportinvoice);
$arrayinvoice = $arrayinvoice[0]; // Since ::exportAll() can export multiple objects, it creates an array. We only want the single entry.

// Import the bean from an array and try to save it to the database
$importinvoice = R::findOne('invoice', 'id=?', array($arrayinvoice['id']));

unset($arrayinvoice['ownLine']);


$importinvoice->import($arrayinvoice);
print_r($importinvoice);print '<p>';
$id = R::store($importinvoice); // throws this error: "Array may only contain RedBean_OODBBeans"

R::close();
}
catch (Exception $ex)
{
die ($ex->getMessage());
}

?>

gregorinator

unread,
Mar 6, 2012, 8:31:36 AM3/6/12
to redbe...@googlegroups.com
On 3/5/12, Jake Chapa <jake...@gmail.com> wrote:
> ownLine is imported as an array. Since you're not using it, simply unset it:

I'm sorry for the confusion: In my real application, I AM using
ownLine, and in my first post on this thread, I included code that
changed the amount column on line item 1 from 2.50 to 5.00. I removed
that code from the example in my previous post because it's not
necessary to change ownLine to make the error happen, and I was trying
to streamline the code to the simplest and shortest example that would
cause the error. In my real-world example, I need to be able to
import ownLine and update the line beans in the database.

This is the real-world scenario: I read the invoice object -- the
invoice bean and the related line beans -- from the database, export
them to an array, convert the array to JSON, and send the JSON to a
Web page. There, the user edits the data -- for example, changes the
amount on line item 1 from 2.50 to 5.00. The Web page sends the
updated JSON back to my application, which converts the JSON to an
array and (this is the part I can't get to work) converts the array
back to RedBean beans that can be saved in the database. So I can't
afford to unset ownLine.

The most basic question is, how can RedBean objects, which include own
beans, be converted such that they can be edited in an application
outside of RedBean (like a Web page), and then converted back so that
RedBean can store them successfully. RedBean claims to provide
NoSQL-like functionality with relational databases, and this is basic,
fundamental functionality that is easy and natural to do with a NoSQL
database like MongoDB. It seemed to me that the exportAll() and
import() functions of RedBean should be the way to go, but I can't get
them to work. I can't be the only person who wants to do something so
simple and basic. How have other people solved this problem using
RedBean?

gs

Jake Chapa

unread,
Mar 6, 2012, 1:37:14 PM3/6/12
to redbe...@googlegroups.com
Sounds like you want the cooker:

gregorinator

unread,
Mar 6, 2012, 3:52:27 PM3/6/12
to redbe...@googlegroups.com
On 3/6/12, Jake Chapa <jake...@gmail.com> wrote:
> Sounds like you want the cooker:
> http://redbeanphp.com/manual/forms_and_cooker

I'll give that a try. I assume that I don't have to pay attention to
this warning on the RedBean site:

"Be careful. Do NOT use the Cooker for publicly available forms. The
Cooker does NOT check ID integrity. People could tamper with IDs or
change fields they are not allowed to change. Only use the Cooker for
trusted forms and feeds or with a thought-out ACL/rights system."

...since an ORM that can't be used for publicly available forms
wouldn't be good for much. :)

Thank you,
gs

gabor de mooij

unread,
Mar 6, 2012, 5:05:14 PM3/6/12
to redbe...@googlegroups.com
Hi,

The Cooker is not the whole ORM.
You can use the Cooker in public forms but it cannot do security checks for you (ids).
So either you have to integrate it with ACL or model based security or you have to write your own controllers.
RedBeanPHP is an ORM library not an overall framework this means there are 'loose ends', where RedBeanPHP ends and your code (or a framework) begins. The same is true for things like templates, routing, caching etc.

Cheers
Gabor


gs

Jake Chapa

unread,
Mar 6, 2012, 7:17:46 PM3/6/12
to redbe...@googlegroups.com
I noticed that redbean doesn't throw an error when i call R::exec() on a bad query (like a missing column name). Is there an equivalent to mysql_error() ?

gregorinator

unread,
Mar 7, 2012, 10:00:35 AM3/7/12
to redbe...@googlegroups.com
On 3/6/12, Jake Chapa <jake...@gmail.com> wrote:
> Sounds like you want the cooker:
> http://redbeanphp.com/manual/forms_and_cooker

The first line on the cooker page:

"The cooker is a tool to turn arrays (forms, XML, JSON) into beans."

That's EXACTLY what I was looking for! Thank you, thank you, thank you.

On 3/6/12, gabor de mooij <gabord...@gmail.com> wrote:
> You can use the Cooker in public forms but it cannot do security checks for
> you (ids).
> So either you have to integrate it with ACL or model based security or you
> have to write your own controllers.

I hope everyone caught the smiley emoticon in my message when I said I
could disregard the warning; of course I was speaking tongue-in-cheek.
I will check and sanitize all inputs, as I would with any other ORM.

> RedBeanPHP is an ORM library not an overall framework

Whatever you call it, I think RedBean is pretty darned awesome. It
basically makes a relational database as easy to use as a document
database. Thank you!

gs

Reply all
Reply to author
Forward
0 new messages