Closed benmajor closed 2 years ago
Looking at this in a little more detail, it appears as though the constant is only used inside of the Bean Helper, so we could in theory port it out into the Facade, and handle it that way. I'll put together a PR to demonstrate.
Here's a working example of how you could achieve that my making your own BeanHelper if you're interested:
<?php
namespace {
include 'rb-sqlite.php';
class MyBeanHelper extends \RedBeanPHP\BeanHelper\SimpleFacadeBeanHelper {
private $namespace = '';
public function __construct($namespace) {
$this->namespace = $namespace;
}
public function getModelForBean(\RedBeanPHP\OODBBean $bean) {
$type = $bean->getMeta('type');
if ($this->namespace) {
$modelName = $this->namespace . '\\' . $type;
$obj = self::factory($modelName);
$obj->loadBean($bean);
return $obj;
} else {
return parent::getModelForBean($bean);
}
}
}
}
namespace Store {
class User extends \RedBeanPHP\SimpleModel {
public function whoami() {
echo "I'm a store user." . PHP_EOL;
}
}
}
namespace Support {
class User extends \RedBeanPHP\SimpleModel {
public function whoami() {
echo "I'm a support user." . PHP_EOL;
}
}
}
namespace {
R::setup( 'sqlite:/tmp/sqlitetest.db' );
R::freeze( FALSE );
R::nuke();
R::usePartialBeans( TRUE );
R::getRedBean()->setBeanHelper( new MyBeanHelper('\\Store') );
$storeUser = R::dispense('user');
R::store($storeUser);
$storeUser->whoami(); // "I'm a store user."
R::getRedBean()->setBeanHelper( new MyBeanHelper('\\Support') );
$supportUser = R::dispense('user');
R::store($supportUser);
$supportUser->whoami(); // "I'm a support user."
}
Thanks @Lynesth, I've just put together a PR that makes this possible, and should maintain backwards compatible with the use of the REDBEAN_MODEL_PREFIX
constant too. Please see https://github.com/gabordemooij/redbean/pull/878/
This PR makes sense for those relying on namespaces and working with different databases. Not a very big crowd, but still useful I think for complex situations/advanced usage.
Here's a working example of how you could achieve that my making your own BeanHelper if you're interested:
<?php namespace { include 'rb-sqlite.php'; class MyBeanHelper extends \RedBeanPHP\BeanHelper\SimpleFacadeBeanHelper { private $namespace = ''; public function __construct($namespace) { $this->namespace = $namespace; } public function getModelForBean(\RedBeanPHP\OODBBean $bean) { $type = $bean->getMeta('type'); if ($this->namespace) { $modelName = $this->namespace . '\\' . $type; $obj = self::factory($modelName); $obj->loadBean($bean); return $obj; } else { return parent::getModelForBean($bean); } } } } namespace Store { class User extends \RedBeanPHP\SimpleModel { public function whoami() { echo "I'm a store user." . PHP_EOL; } } } namespace Support { class User extends \RedBeanPHP\SimpleModel { public function whoami() { echo "I'm a support user." . PHP_EOL; } } } namespace { R::setup( 'sqlite:/tmp/sqlitetest.db' ); R::freeze( FALSE ); R::nuke(); R::usePartialBeans( TRUE ); R::getRedBean()->setBeanHelper( new MyBeanHelper('\\Store') ); $storeUser = R::dispense('user'); R::store($storeUser); $storeUser->whoami(); // "I'm a store user." R::getRedBean()->setBeanHelper( new MyBeanHelper('\\Support') ); $supportUser = R::dispense('user'); R::store($supportUser); $supportUser->whoami(); // "I'm a support user." }
This is a really beautiful solution by the way.
@Lynesth @benmajor I think the PR makes sense, but I'd rather not change the Simple BeanHelper, can we not just merge the two solutions? I mean I understand that @benmajor wants to use the namespace through a parameter, that's fine, but @Lynesth 's solution is probably the cleanest OO-way to do it.
Thanks @gabordemooij. Indeed, the solution proposed by @Lynesth is exactly what I have implemented while awaiting for the review of this PR (with some slight amendments to work with our custom kernel), and I have to say that it is very elegant.
We effectively added the R::selectDatabase()
call into our kernel's selectDatabase()
method, and inside of that assign the Bean Helper based on the assigned prefix. It is a very clean solution to the problem, and I agree that the scenario we are discussing here is probably a very specific edge-case, but I just feel that the PR helps to maintain the 'automagical' nature of RedBean.
I'm using Lynesth's helper in order to be able to separate models into different directories, and it's great. However, I did find that I had to add a file existence test to the code when the system tried to look for a model in my extra directory for a bean that did not have one defined otherwise it gave a missing file error.
Slightly different solution:
https://github.com/gabordemooij/redbean/pull/887
Should work like this:
R::addDatabase(
'support',
'mysql://tmp/d1.db',
'{USER}',
'{PASSWORD}',
$frozen,
DBPrefix( '\\App\\Entity\\Support\\')
);
Where the convience function DBPrefix() is short for:
new DynamicBeanHelper( '\\App\\Entity\\Support\\');
I'm not sure whether this is actually possible given RedBean's use of constants for this, but I thought it would be worth asking anyway. This may be an edge case, but it's something I've run into on a couple of projects using RedBean now.
Sometimes, it would super-handy to define the model prefix depending upon the database that is currently defined.
Here's an example:
We have two databases, one named
store
and one namedsupport
. The former holds product catalogue data and so forth, and the latter exists as a CRM database. We can implement both databases into our project quite easily by giving each DB a unique name, and usingR::selectDatabase( 'store')
andR::selectDatabase('support')
respectively. Using the traditional method, we would have defined the model prefix as follows:And this works, until we have Beans that exist in both the
store
andsupport
databases, such asstore.user
(relates to store users; such as customers) andsupport.user
(which relates to admin staff, customers and support agents). If we have defined an autoloaded model atApp/Entity/User.php
, beans from both databases will use the same model. Sometimes though, we need to define custom methods depending upon the database the bean has been located from, so my suggestion would be to update the method signature foraddDatabase()
to something like:By passing in the model prefix as the last parameter, we could then have
User.php
exist in the following locations:App/Entity/Store/User.php
App/Entity/Support/User.php
I had initially thought that we may be able to implement this functionality by adding an event listener that responds to
selectDatabase()
method, but since we have generally defined the model prefix as a constant, this approach won't work. I'm also open to other suggestions to achieve this goal, though!