phalcon Dynamic Class creation fails with error? - php

I am trying to create dynimic model class with a model function inside the user model, however for some reason it unable to identify the class location. it gives me an error
Fatal error: Class 'VarEducation' not found
below is the function
public function partner()
{
$view_service=new ViewService();
$partner_vars_check=$view_service->getUserVarPartnerModelCheckMappingArray();
foreach($partner_vars_check as $partner_key=>$partner_var){
$table= str_replace("p_","var_",$partner_key);
$sql = "SELECT * FROM ".$table."
WHERE id='" . $this->{$partner_key} . "'
ORDER BY id DESC";
$partner_obj = new $partner_var();
$this->{$partner_key} = new \Phalcon\Mvc\Model\Resultset\Simple(null, $partner_obj,
$partner_obj->getReadConnection()->query($sql));
}
}
Any idea on what causing this ?
data supplied by the view service is as below
public function getUserVarPartnerModelCheckMappingArray() {
return array(
'p_education' => 'VarEducation',
'p_body' => 'VarBody',
'p_ethnicity' => 'VarEthnicity',
'p_religion' => 'VarReligion',
'p_family' => 'VarFamily',
);
}

Whatever you do, if you have model hidden in any namespace, you have to deliver full namespaces with your mapping array. Lets say, all your models are in namespace of Application\Models, you have to use it this way:
public function getUserVarPartnerModelCheckMappingArray() {
return array(
'p_education' => '\Application\Models\VarEducation',
'p_body' => '\Application\Models\VarBody',
'p_ethnicity' => '\Application\Models\VarEthnicity',
'p_religion' => '\Application\Models\VarReligion',
'p_family' => '\Application\Models\VarFamily',
);
}
Not saying that I would rather switch/case over such a small amount of options and use models directly, instead of building SQL by hand.

Phalcon should load classes correctly even if the class name is a variable. Try isolating the problem down to just:
$myClass='VarEducation';
$partner_obj = new $myClass();
Also try commenting out that particular model from your model list and see if your other models work, perhaps there's an error in finding that particular model but not others. Check your /app/config/loader.php file and make sure you're registering your models directory. Then make sure the file VarEducation.php exists in your models directory as well as your other models. Also make sure that the class name inside the file matches the file name, and you've named the class VarEducation. Also make sure it's not namespaced. Also make sure you didn't forget the leading <?php and that the file doesn't have any errors. If all else fails, you can simply load the class from your loader.php file with something like:
$loader->registerClasses(array(
'VarEducation' => $config->application->modelsDir.'/VarEducation.php'
));
Make sure a simple test for the model works: $x=new VarEducation(); I suggest doing this from your index controller under a test action. If none of this works, comment on this post with your results when trying my suggestions and I'll update my answer.

Related

TYPO3 EXT:cart_product: extend class product with own extension

I try to extend the existing class Product from the Typo3 (Version 11) cart_products extension with my own extension. Therefor I have already extended the backend and tables to store a new value called productpackagetype.
Next step for me was to implement the new class Product in myext/Classes/Domain/Model/Product/Product.php which looks like this :
<?php
namespace Vendor\myext\Domain\Model\Product;
class Product extends \Extcode\CartProducts\Domain\Model\Product\Product
{
protected $productpackagetype = '';
public function getProductpackagetype()
{
return $this->productpackagetype;
}
public function setProductpackagetype($productpackagetype)
{
$this->productpackagetype = $productpackagetype;
}
}
To tell typo3 to use the new class definition I tried the following code in the myext/ext_localconf.php
$GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'][\Extcode\CartProducts\Domain\Model\Product\Product::class] = [
'className' => \Vendor\myext\Domain\Model\Product\Product::class
];
\TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Object\Container\Container::class)
->registerImplementation(
\Extcode\CartProducts\Domain\Model\Product\Product::class,
\Vendor\myext\Domain\Model\Product\Product::class
);
As long as I write nothing to myext/ext_localconf.php nothing in the frontend changes. But as soon as I add the above code typo3 comes up with a Error 503
Return value of Extcode\CartProducts\Domain\Model\Product\Product::getBeVariants() must be an instance of TYPO3\CMS\Extbase\Persistence\ObjectStorage, null returned
So what would be the right way to bring up my extended class. And maybe someone can tell me how I tell typo3 to use my extensions private template show.html instead of using cart_products template.
Best regards
Johannes
You forgot to map your model to the products table of the extension cart_products. To do so, create a file myext/Configuration/Extbase/Persistence/Classes.php and paste these lines:
<?php
declare(strict_types = 1);
return [
\Vendor\myext\Domain\Model\Product\Product::class => [
'tableName' => 'tx_cartproducts_domain_model_product_product',
],
];
You need to flush all the caches to apply this change.
You can read more about persistence handling in TYPO3 here.
Concerning your second question: You have to configure the paths to your templates in TypoScript:
plugin.tx_cartproducts {
view {
templateRootPaths.10 = EXT:myext/Resources/Private/Templates/
partialRootPaths.10 = EXT:myext/Resources/Private/Partials/
layoutRootPaths.10 = EXT:myext/Resources/Private/Layouts/
}
}
Please note the index 10 here. You can configure multiple paths. TYPO3 will start looking for the template in the folder configured with the hightest numbered index. If the file does not exist at that location, it continues with the next lower index.
When you inspect the file cart_products/Configuration/TypoScript/setup.typoscript you will see that the extension uses the index 0. So if you don't provide any templates, the default ones will be used.
Make sure to use the same names for folders and files as in the original extension.
You can read more about template paths in TYPO3 here

CodeIgniter how to set autoloaded database dynamically

Using CodeIgniter 3, I autoload my database config, now how do I change the database connected dynamically ? I was thinking like using session to pass the database value, but session cannot be used in the database config file.
I know I can manually load database and change it, but then I have to call and load the database in every controller and I have tons of the controller, therefore I would like to avoid setting the database manually.
There is probably more than one way to do what you want. The solution shown here uses CodeIgniter’s "Hooks" feature. Specifically, it uses the "post_controller_constructor" hook to match the name of a controller with a specific database configuration defined in database.php.
After the hook does its work the application can make calls to the database in the typical CI way using $this->db->. For example...
$query = $this->db->get('mytable');
This solution is based on the assumption that only one database connection is need for any given controller. This means that all methods in that controller (or any models loaded by the controller) use the same connection.
Here's how it is done.
In application/config/config.php
$config['enable_hooks'] = TRUE;
In application/config/hooks.php
$hook['post_controller_constructor'][] = array(
'class' => '',
'function' => 'set_db_connection',
'filename' => 'post_controller_hook.php',
'filepath' => 'hooks'
);
The file post_controller_hook.php is where the work gets done. It uses lists of controller names to determine which database config is to be loaded.
The list ($controller_lists) contains sub-arrays which group controller names by the db configuration needed. A search is done through each sub-array to find the matching controller name. When a controller name is found the key of that sub-array is the db config to be loaded. If no match is found the 'default' config is used.
The $controller_lists array is hard-coded here but it could easily be loaded from a config file instead. A config file might make maintaining the lists easier.
file application/config/post_controller_hook.php
function set_db_connection()
{
$CI = get_instance();
$controller = $CI->router->class;
$loadConfig = 'default'; //if nothing found in lists we're still good
$controller_lists = array(
'config2' => ['profile'],
'config3' => ['discusion', 'home'],
'config4' => ['suppliers', 'customers', 'inventory', 'orders']
);
foreach($controller_lists as $config_name => $list)
{
if(in_array($controller, $list))
{
$loadConfig = $config_name;
break;
}
}
$CI->load->database($loadConfig);
}
The ability to not load a database for controllers that don't need one could be added if that was desirable. But I'm not going there.
As stated earlier, this solution uses the assumption that only one database configuration (connection) is used for any given controller. If certain methods of a controller need to use a different db configuration this solution becomes more complicated.
Adding the method to the search is easy. The first few lines of set_db_connection() would look like this.
function set_db_connection()
{
$CI = get_instance();
$controller = $CI->router->class;
$method = $CI->router->method;
if($method !== 'index')
{
$controller .= '/'.$method; //append method name
}
$loadConfig = 'default'; //if nothing found in lists we're still good
So now $controller will hold either 'controller/method', or just 'controller' if index() is to being called.
Consider a controller called Viewstate with three methods
class Viewstate extends CI_Controller
{
public function index(){
//uses db 'config4'
}
public function report(){
//uses db 'Config2'
}
public function process(){
//uses db 'Config3'
}
}
We have to include each 'viewstate/method' in the sub-arrays like this.
$controller_lists = array(
'config2' => ['profile', 'viewstate/report'],
'config3' => ['disscusion', 'home', 'viewstate/process'],
'config4' => ['viewstate', 'customers', 'inventory', 'orders']
);
//the rest of the function is as shown earlier
Any 'viewstate/method' not in the search lists it will be assigned the 'default' db config. So it's easy to sort the various needs of viewstate.
The problem is that every 'controller/method' in the site must now be included in the search lists. If the Profile controller has ten methods every combination must now be in the config2 sub-array. So if there are lots of controllers and controller/methods this solution is a poor choice. There might be an elegant way around this problem but that's probably a topic for a new question.

How do I connect to different databases at run time?

I am making a multi-tenant multi-database app that has one central database and many sub-databases.
The app creates an instance of an organisation in the central database, and to each organisation it creates a sub-database with different tables.
To achieve this, I have made a class class Setup that
Creates the Organisation
Creates its Database
Configures the connection to that database and connects to it
Runs the proper migrations to it.
All wrapped up in a constructor, so upon caling Setup::create all of this runs properly.
Most of the database configuration and connection are inspiried from this tutorial.
To test whether my logic goes as wanted, I interacted with my application via :
Tinker
Web Browser.
To my suprise, the outcome is different in both cases, and never as wanted as far as connecting to another database is concerned.
Interaction with tinker :
After creating calling my Setup::create and having output telling me everything went okay, I try to check for myself what database am I on right now Using:
DB::connection()->getDatabaseName()
It outputs the sub-database name we have just created for the organisation and connected to, which is logical and going accordingly.
However, I attempt to connect to another database by creating a new configuration for it and then connecting to it with the DB methods I have provided, it does not work, I am still on the sub-database I was on.
Interacting with the browser :
This time, having my Setup::create wrapped up properly in my controller's code, I attempt to test everything again, I also made a line in my layout to output me the current database :
<?php echo DB::connection()->getDatabaseName() ?>
At first, while I am still on the central database, its name appears, however after calling Setup::create, it switches to the sub-database -Which is expected- but then, after one refresh, I am on the central database again -Which is totally Unexpected-
So, what happens here? and how do I get to connect to all of my different databases how I wish when I wish?
Extra:
Testing in tinker, I have went to the point where I have commented out the migration code, and left the creation of the database and also the connection to it.
To my suprise, it does not connect to the database.
so I started thinking that the migration code has something to do with connecting to the database, or maybe tinker has different behaviors I completely ingore.
Important:
I have came across threads where solutions using QueryBuilders were mentioned
Please, do not provide such answers because my aim is to switch databases entirely to the point where I can use eloquent model's events with no problem.
By that I mean, I want to be able to use Model::create after having connected to the database instead of DB::connection()->....
Technical details:
I am using Laravel 5 with mysql-server, on Ubuntu Machine.
I stumbled upon this question and it had my answer.
I made a class called DatabaseConnection:
class DatabaseConnection extends Model
{
static $instances=array();
protected $database;
protected $connection;
public function __construct($options = null)
{
// Set the database
$database = $options['database'];
$this->database = $database;
// Figure out the driver and get the default configuration for the driver
$driver = isset($options['driver']) ? $options['driver'] : Config::get("database.default");
$default = Config::get("database.connections.$driver");
// Loop through our default array and update options if we have non-defaults
foreach($default as $item => $value)
{
$default[$item] = isset($options[$item]) ? $options[$item] : $default[$item];
}
$capsule = new Capsule;
$capsule->addConnection($default);
$capsule->setEventDispatcher(new Dispatcher(new Container));
$capsule->setAsGlobal();
$capsule->bootEloquent();
// Create the connection
$this->connection = $capsule->getConnection();
DatabaseConnection::$instances[] = $capsule;
return $this->connection;
}
}
So, whenever I am in a controller that manipulates tables of a sub-database, I simply go this way:
public function RandomActionInMyController()
{
$db_connection = new DatabaseConnection(['database' => 'name_of_db']);
$someModel = new Model/Model::find()..// Basically anything
return myreturnstuff;
}
Extra Bonus:
The use of the static attribute $instances in my DatabaseConnection
boils down to retrieving my latest database connection for ease uses.
For example, if I ever wanted to retrieve it, it would be wrapped in a function such as
function CurrentOrLatestDbConnection()
{
if( !empty(DatabaseConnection::$instances) )
{
return end(DatabaseConnection::$instances)->getConnection()->getDatabaseName();
}
}
Notes :
If you encounter errors such as Unknown class 'Container' or Capsule or anything of that kind, make sure you check the question link I have provided, and use use statements properly.
Concerning upcoming answers :
It seems to me that this database connection lives within the the brackets of the controller's action, so when I proceed to another action that specifies no connection, it returns to the central database automatically.
Which has got me thinking that there must be a way to set the database connection to the sub-database in a 'global' way to the whole function, such as a middleware or something.
I would love to see an answer, implementing such thing.
Update :
I came up with a neater way to do it.
I assume you are on the same ground as me, wanting to change databases conditionally in accordance with each controller... say, each of your controllers requires a different database, just for the sake of the argument.
What we will be using to solve this is `Middlewares.
First, to explain what we are about to do..
We are going to check for the name of the controller (and even action) and then set the proper database we wish to set.
Go to your command-line , type in:
php artisan make:middleware SetDatabaseConnectionMiddleware
To create a middleware with ready boilerplate.
Or, if you like it the hard way, go to your app_name/app/Http/Middleware and create one manually.
Go to your helper methods file( if you already have one, if not, dude make one!)
function getControllerAndActionName()
{
$action = app('request')->route()->getAction();
$controller = class_basename($action['controller']);
list($controller, $action) = explode('#', $controller);
return ['action' => $action, 'controller' => $controller];
}
This will return to you an array with both the action name and controller name, if you want to return restrictidly just the controller's name, feel free to remove 'action' => $action from the code.
Inside of your middleware, it will look this way :
namespace App\Http\Middleware;
use Closure;
use DatabaseConnection;
class SetProperDatabase
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
$database_name = '';
$controllerAndActionName = getControllerAndActionName();
$controller_name = $controllerAndActionName['controller'];
$action_name = $controllerAndActionName['action'];
if($controller_name == 'my_controller_nameController')
{
$database_name = 'your_proper_database_name';
}
else
{
$database_name = 'other_db';
}
$database_connection = new DatabaseConnection(['database' => $database_name']);
return $next($request);
}
}
4.Now, that you have created properly your middleware, let us tell your app where to find it and under what name.
Go to your app_name/app/Http/Kernel.php
In your $routeMiddleware variable, add this line
'set_proper_database' => \App\Http\Middleware\SetProperDatabase::class,
This way we know how to call it.
Finally, setting it up.
Go to your Controller.php (the Abstract class from which all of your controller's inherit)
public function __construct()
{
$this->middleware('set_proper_database');
}
And this should do it for you.
If you have any further questions, please feel free to comment.
// Resources :
1.Controller And Action Name
2.Middleware Documentation
3.Further Middleware Documentation
Notes :
I'd appreciate some edition concerning my styling and code indenting, since it seems I struggled to style my code properly in here but in vain, the indentions I used had no effeft.

how to use Lithium PHP framework with enumerated list of collections and changing collection in model

I'm looking to use Lithium framework to build my application config interface as I like its minimal approach and the document-store (i.e. Mongodb) centric model.
However, (and I know its not quite released yet), there is little-to-no information, tutorials or examples out there to move you on from the simple blog tutorial.
What I am trying to do now is build an app that will show me the collections I have in Mongodb, and then let me work with which ever collection I choose. I can't seem to figure out:
a) how would I build a model that enumerates the collections - preferably according to my internal naming scheme,
b) how do I break the convention model so I can specify the name of the collection to use?
I think there are two things i'm struggling with to answer these two questions - perhaps a fundamental misunderstanding of how to move a model in MVC beyond the simple collection-model-controller-view examples, and secondly, the actual process of telling the mongo datasource what collection to use.
any pointers or examples, gratefully received.
Chris
update::
So I figured out how to set the collection - for reference you can set source in the $_meta array like this:
protected $_meta = array(
'source' => '<<collectionName>>'
);
still no idea how to use a Model that will list me all the collections I have in my DB though. Any ideas how to do that from a philosophical and also technological manner?
further update::
so I have got a bit further thanks to the comments below. At least I might now be able to re-phrase the question a bit. I can define my model something like this:
<?php
namespace app\models;
use lithium\data\Model;
class Posts extends \lithium\data\Model{
protected $_meta = array('source' => false);
public function testcolls(){
return (self::connection()->sources());
}
}
?>
then in my view I can use:
<?php foreach ($post->testcolls() as $coll): ?>
<h2><?=$coll ?></h2>
<?php endforeach; ?>
that works - however, what I really want to do is not create a 'testcolls' method in my Model but as Medhi suggested below, I need to override the find method. I can't seem to figure out how to do that and what it would need to return. The docs are not too clear on this.
final update
based on the comment below and a bit of experimentation, I came up with the following that works for being able to call find with a collection as a parameter.
model:
class Dataqueues extends \lithium\data\Model{
protected $_meta = array('source' => false);
public static function find($filter, array $options = array()) {
if (isset($options['collection'])){
self::meta('source', $options['collection']);
}
return parent::find('all',$options);
}
}
controller:
class DataqueuesController extends \lithium\action\Controller {
public function index() {
$dataqueues = Dataqueues::find('all',array('limit'=>20,'collection'=>'W501'));
return compact('dataqueues');
}
}
getting a model that returns a list of collections was also pretty simple in the end:
class Collections extends \lithium\data\Model{
protected $_meta = array('source' => false);
public static function find($filter, array $options = array()) {
return self::connection()->sources();
}
}
note that the controller won't support options or filters.
Nothing holds you from having a Collections Model, where you set $_meta['source'] = false to prevent Lithium from looking for a Collection in your database named collections.
In this model, you can call YourModel::connection()->sources() to list all your Mongo Collections.
Docs for sources(): http://li3.me/docs/lithium/data/source/MongoDb::sources(). Basically it calls listCollections() on a MongoDB instance http://php.net/manual/en/mongodb.listcollections.php
You can override your Model::find() method to return the list of collections, instead the list of documents, or pass the collection as a param Collections::find('all', array('conditions' => ..., 'collection' => 'foo'))... or wathever you want :-)
Lithium is designed to don't force that much on you !
First of all, Lithium follows the convention over configuration approach.
What this means:
Configuration: 'source' => '<< collectionName >>'
Convention: Name your model and your collection the same thing, the framework handles the rest.
IE: A "People" collection will have a "People" model
Second, connect to your database:
Configure your connections.php file in app\bootstrap\connections.php. I know I said convention over configuration, but you still need to let the framework know where the database is and what the login info is. For details look at the http://li3.me/docs/manual/quickstart. Here is the relevant code:
// MongoDB Connection
Connections::add('default', array('type' => 'MongoDb', 'database' => 'blog', 'host' => 'localhost'));
Third, get data
Create a model, matching your collection name, and then in your controller, add this line:
$model = Models::all();
where model is the singular name for what you are storing in your collection, and Models is the name of your model. That is it.
If you put a break point after this line, you will see your Models collection. For more information, see http://li3.me/docs/manual/working-with-data/using-models.wiki
Finally, to pass it to your view, simply put this line of code at the end of your controller:
return compact('model', 'model2', 'model3');
where model would be what you just pulled in the third step. The models 2 and 3 that I tacked on is how you would pass any other collections you pulled.
In your view, you would just reference $model to and assume that the relevant fields are there. You don't have to worry about putting getters or setters or anything else like that.
For example: if you want to show the data in $model:
foreach ($model as $aModel) {
echo $aModel;
}
See Accessing View Variables in: http://li3.me/docs/manual/handling-http-requests/views.wiki
Hope this helps.

Passing two arrays from controller in cakePHP

I'm trying my best to learn MVC and cakePHP and I had a question about passing arrays to the view. Currently, I have some basic code below.
class AwarenesscampaignsController extends AppController {
public function view($id = null) {
$this->Awarenesscampaign->id = $id;
$this->set('data', $this->Awarenesscampaign->read());
}
This is what I "think" is currently happening.
AwarenesscampaignsController is set up. The view paramater requests id and matches it up with the Model, Awarenesscampaign. This matches up with the database and returns an array which is set to the variable "$data", and then the view is loaded.
My first question: is my understanding accurate?
What I would like to do is with this is to be able to pass another array, from a different model. For instance, I would like to query the table Posts (Controller: PostsController/ Model: Post).
For instance, my first attempt was to do the following inside the function:
$this->Post->find('all');
But this yields the error:
Indirect modification of overloaded property AwarenesscampaignsController::$Post has no effect [APP/Controller/AwarenesscampaignsController.php, line 20]
Additionally, I'm not sure how I would send both variables to the view.
To recap:
Was my understanding accurate?
How do I query a variable from another controller/model?
How do I sent this array to the appropriate view for that controller?
Thanks,
-M
You're on the right lines, and aren't doing it wrong per se. I would say your understanding is pretty good for a beginner.
By default Cake automatically loads a model that it thinks is directly related to the controller. So in AwarenesscampaignController, you can automatically access Awarenesscampaign (the model).
It doesn't know about any other model, though. One way you might solve this is by adding the following property to your controller:
// This has to contain ALL models you intend to use in the controller
public $uses = array('Awarenesscampaign', 'Post');
This goes at the top of the class, before you start declaring the functions. It tells Cake that you want to use other models except the 'default' one, but you have to add that one to the array too, or you'll lose access to it.
You can also use loadModel inside your action, if it's a one-off. It's then accessed the same way as you would access a model normally:
public function view($id = null) {
$this->loadModel('Post');
$posts = $this->Post->find('all');
...
}
To send this to your view, you can call set again, but you might want to change data to something more readable, and to prevent confusion:
public function view($id = null) {
...
$this->set('campaign', $this->Awarenesscampaign->read());
$this->set('posts', $this->Post->find('all'));
}
They'll be accessible as $campaign and $post respectively.
One tweak I would make, though, is to not use 'read' unless you intend to edit something. You can use findByColumnName to get the same data. Since you're using just an id, you can call findById:
$campaign = $this->Awarenesscampaign->findById($id);
There's quite a lot of magic going on there. It just means you can search for a particular value in a more short-hand format.
http://book.cakephp.org/2.0/en/models/retrieving-your-data.html
Finally, while you can access other models (as demonstrated), you can't, or generally shouldn't, try and access one controller from another. If you have code that you want to use in more than one controller, but can't go in the model, you can create Components.
http://book.cakephp.org/2.0/en/controllers/components.html#creating-a-component
The manual is fairly comprehensive. While sometimes hard to navigate, it will often have an answer to most of your questions.
http://book.cakephp.org/2.0/en/
1) Your understanding is good enough. What this is doing is basically mapping a row of database table with object. So after setting the Model id $this->Awarenesscampaign->id = $id, now Model is pointing to the row of database table that has id equals to what has been passed to view action.
2) you can query another table by calling the methods of that particular Model. If your model is somehow associated with the current Model that you are in, you can use chaining to call that Model's action. e.g. if your in Posts controller and Post Model is associated with Comment Model t get the data you can chain through.
$comments = $this->Post->Comment->find();
If however your Model of interest is not associated with current Model, there are couple of ways to perform operations of other Model. A good option is to use Class Registry. Say for example you want to use Customer Model which is not related to your current Model. In your controller you will do
$customer= ClassRegistry::init("Customer");
$customers= $customer->find();
3) to set multiple variables for the view you can set them via compact function or using associated row.
$posts = $this->Post->find();
$comments = $this->Post->Comment->find();
$this->set(compact('posts', 'comments'));
// or
$this->set('posts' => $posts, 'comments' => $comments);

Categories