CakePHP, creating extensible Plugin - cakephp

I have several projects in CakePHP, and would like to move common code into plugins and use seperate GIT repositories for those.
For example, I created a UserManager plugin which contains MVC for users, groups and permissions.
My problem is: the different projects have different (additional) relations to the models from the plugin. E.g., one project should have "User belongsTo Location" in addition.
I'm now confused how to set this up properly. The manual tells how to override Plugin views, but not how this is done with models and controllers.
How can this be done in a clean way?

You can simply extend the plugin classes and override/add the necessary associations, just like you're probably already doing it with AppModel respectively UserManagerAppModel.
http://book.cakephp.org/2.0/en/plugins.html#plugin-models
Here's a basic example (assuming the user class in the plugin is named User):
App::uses('User', 'UserManager.Model');
class AppUser extends User
{
public $belongsTo = array('Location');
}
Or create the associations dynamically in case there are existing ones that need to be kept:
class AppUser extends User
{
public function __construct($id = false, $table = null, $ds = null)
{
parent::__construct($id, $table, $ds);
$this->bindModel(array('belongsTo' => array('Location')));
}
}

Related

Read table from another database

Is it possible to read tables from another database using the builting CakePHP model features? I don't mean having an entirely different configuration in DATABASE_CONFIG but using the same host, user and password. The obvious thing:
class Provincia extends AppModel {
public $useTable = 'shared_data.dbo.provincia';
}
class DebugController extends AppController {
public function index() {
/* #var $modeloProvincia Provincia */
$modeloProvincia = ClassRegistry::init('Provincia');
$provincias = $modeloProvincia->find('all');
}
}
... triggers:
Error: Table shared_data.dbo.provincia for model Provincia was not found in datasource default.
I'll share my findings so far...
Short answer: you cannot.
CakePHP magic depends heavily on information about tables and columns fetched from the INFORMATION_SCHEMA views. That information is gathered in \Sqlserver::listSources (list of tables) and \Sqlserver::describe (list of columns).
While it's possible to extend the datasource driver and reimplement these methods:
// Model/Datasource/CustomSqlserver.php
class CustomSqlserver extends Sqlserver {
}
class DATABASE_CONFIG {
public $default = array(
'datasource' => 'CustomSqlserver',
// ...
);
}
... that's just the tip of the iceberg. The data structures account for two levels:
Schema (e.g. dbo)
Table (e.g. users)
They aren't designed for an extra database level on top. As a result, you end up needing to patch so much code that it isn't worth the effort.
I've also been playing with Synonyms in SQL Server. It's a more promising path because, while you still need to write \CustomSqlserver::listSources and \CustomSqlserver::describe yourself, to most (not all) effects they behave like regular tables. The main restriction though is that there can't be duplicate table names.
You would need to add a new connection to DATABASE_CONFIG, but you could do this in the constructor so that you can inherit your default database credentials and just modify the database name:-
public function __construct() {
$this->alt = $this->default;
$this->alt['database'] = 'provincia';
}
Then in your Provincia model you can swap to the alt database connection:-
public function __construct($id = false, $table = null, $ds = null) {
parent::__construct($id, $table, $ds);
$this->useDbConfig = 'alt';
}

CakePHP update public $uses = false; in action from same Controller

I'm writing an installation script for my CakePHP web application. I have a InstallController with 6 actions: step1, step2, step3, etc.
At step1 I'm handling Config/database.php creation. Because this file is empty and no datasource is available I have to set public $uses = false; in the InstallController.
At step2 the Config/database.php file is set so I should be able to make a connection to the datasource. This is also necessary because I want to update some database fields in the following steps.
Is it possible to update the public $uses = false; in every following steps after step1?
I'm using CakePHP version 2.3.5
Have you considered loading the model within the actions? So, something like:
<?php
App::uses('AppController', 'Controller');
class InstallController extends AppController {
public $uses = false;
public function step1() {
}
public function step2() {
$this->loadModel("Install");
$this->Install->callMethod();
}
}
In CakePHP 2.x models are lazy loaded, so as long as your step1 action doesn't try to make use of a model, you can safely declare the models in your controllers $uses property, they are not being constructed until your code actually makes use of them.
However, if for some reason you'd actually need to modify $uses, well then just do it, as mentioned models are lazy loaded, so you can modify $uses whenever you want and then access the models afterwards via magic properties on the controller.

CakePHP: To Create A New Controller

I'm using CakePHP 2.0.5 (but this isn't necessarily a cakephp specific question). I have a Coupon and a User model. Each time a user prints a coupon (proccessed by: Coupon Controller):
class CouponsController extends AppController {
public function printcoupon($id = null) {
// code
}
}
I want to save the information to a "coupons_printed" table (id/coupon_id/user_id/created). Should I create a new model for this, or should I just create a function inside of the Coupon model similar to (and call it in the controller each time that page is viewed)?:
class Coupon extends AppModel {
function insertIntoPrinted($id) {
$this->query("UPDATE coupons_printed SET .....");
}
}
Whatever you do, a raw SQL query is not the best way to go. Always use CakePHP methods if at all possible (and almost always it is possible).
You should put the insertIntoPrinted() function in the CouponsPrinted model (although, as a side note, PrintedCoupon would be a more natural way to name the model...) You can then add a HasMany relationship to the Coupon model ($hasMany = array( 'CouponsPrinted' )) and call the function in the CouponsController:
public function printcoupon($id = null) {
$this->Coupon->CouponsPrinted->insertIntoPrinted( $id );
}
CakePHP's model has a thing call association.
In your case, Coupon has a hasMany association with coupons_printed.
You can create a new model, or query using the association in the Coupon model, the generated queries will be the same, I believe.
Your CouponsController already depend on Coupon Model, so not creating another model is a better solution.

Different data sets per subdomain in Cakephp?

I am writing a personal bookmarks application and am looking for a way to have totally different content between subdomains.
I have written the application and it works fine, but have yet to implement multiple collections. I have the following models:
Tag (unused so far)
Bookmark
Subject
Bookmarks are categorized in subjects and I'm planning to allow tagging in the future and see if that helps me manage my bookmarks more easily.
The current problem I have is that I'd like to separate bookmarks as a whole. I want to use subdomains like webdevelopment.bookmarks.local, languages.bookmarks.local, linux.bookmarks.local that work with an entire own set of domains and bookmarks.
I am considering adding a new model called Set (short for "bookmark sets") and defining sets based on the subdomain.
According to that plan I'd have to rewrite all $this->...->find-queries in the entire App to contain the condition "set_id" = $SubdomainBasedSetid".
While it wouldn't be that much work, I was wondering if it could be done smarter, maybe that Cake would only see the relevant bookmark set per subdomain.
well, your solution is right. But instead of using subdomains, you can use prefix, so you don't have check and set yourself. Since you use subdomains, I assume that these sets are fixed or rarely change. So you don't really need a sets table, just use the set name directly in your bookmark record, so you don't have to convert between name and id.
According to that plan I'd have to rewrite all $this->...->find-queries in the entire App to contain the condition "set_id" = $SubdomainBasedSetid".
As all models extend AppModel, it is possible to edit all queries there before they happen (ie. DRY). :)
// app/app_model.php
class AppModel extends Model {
public function beforeFind($queryData) { // old query
// make changes
return $queryData; // new query
}
However, if you don't want this functionality for all models (or even if you do for now), a better place might be a behavior as this allows you to pick and choose where and when it is loaded:
// app/models/behaviors/subdomain.php
class SubdomainBehavior extends ModelBehavior {
protected $_defaults = array('field' => 'Site.subdomain');
public function setup(&$model, $config = array()) {
$this->settings[$model->alias] = array_merge($this->_defaults, $config);
}
public function beforeFind(&$model, $queryData) {
$domain = $_SERVER['SERVER_NAME'];
$subdomain = substr($domain, 0, strpos($domain, "."));
$queryData['conditions'][$this->settings['field']] = $subdomain;
return $queryData;
}
}
// app/app_model.php
class AppModel extends Model {
$actsAs = array('Subdomain' => array('field' => 'Set.slug'));
}
Tag (unused so far)
To save reinventing the wheel, you may want to look at CakeDC's tags and utils plugins (the latter contains SluggableBehavior which will help with generating friendly URL parts, such as the subdomains).

Can I use one model inside of a different model in CakePHP?

Can I use another Model inside one model?
Eg.
<?php
class Form extends AppModel
{
var $name='Form';
var $helpers=array('Html','Ajax','Javascript','Form');
var $components = array( 'RequestHandler','Email');
function saveFormName($data)
{
$this->data['Form']['formname']=$data['Form']['formname'];
$this->saveField('name',$this->data['Form']['formname']);
}
function saveFieldname($data)
{
$this->data['Attribute']['fieldname']=$data['Attribute']['fieldname'];
}
}
?>
Old thread but I'm going to chime in because I believe the answers to be incomplete and lacking in "why". CakePHP has three ways to load models. Though only two methods work outside of a Controller, I'll mention all three. I'm not sure about version availability but this is core stuff so I believe they'll work.
App::import() only finds and require()s the file and you'll need to instantiate the class to use it. You can tell import() the type of class, the name and file path details.
ClassRegistry::init() loads the file, adds the instance to the object map and returns the instance. This is the better way to load something because it sets up "Cake" things as would happen if you loaded the class through normal means. You can also set an alias for the class name which I've found useful.
Controller::loadModel() uses ClassRegistry::init() as well as adds the Model as a property of the controller. It also allows $persistModel for model caching on future requests. This only works in a Controller and, if that's your situation, I'd use this method before the others.
You can create instances of other models from within any model/controller using one of these two methods.
If you're using Cake 1.2:
App::import('model','Attribute');
$attr = new Attribute();
$attr->save($dataYouWantToSavetoAttribute);
If you're using Cake 1.1:
loadModel('Attribute');
$attr = new Attribute();
$attr->save($dataYouWantToSavetoAttribute);
An obvious solution everyone missed is to create an association between two models, if appropriate. You can use it to be able to reference one model from inside another.
class Creation extends AppModel {
public $belongsTo = array(
'Inventor' => array(
'className' => 'Inventor',
'foreignKey' => 'inventor_id',
)
);
public function whoIsMyMaker() {
$this->Inventor->id = $this->field('inventor_id');
return $this->Inventor->field('name');
}
}
In CakePHP 1.2, it's better to use:
ClassRegistry::init('Attribute')->save($data);
This will do simply
<?php
class Form extends AppModel
{
//...
$another_model = ClassRegistry::init('AnotherModel');
//...
}
?>
In CakePHP 3 we may use TableRegistry::get(modelName)
use Cake\ORM\TableRegistry;
$itemsOb = TableRegistry::get('Items');
$items = $itemsOb->find("all");
debug($items);
If you want to use Model_B inside Model_A, add this line at the beginning of Model_A file:
App::uses('Model_B_ClassName', 'Model');
and then you will be able to use it inside Model_A. For example:
$Model_B = new Model_B_ClassName();
$result = $Model_B->findById($some_id);
var $uses = array('ModeloneName','ModeltwoName');
By using $uses property, you can use multiple models in controller instead of using loadModel('Model Name').
App::import('model','Attribute');
is way to use one model into other model. Best way will be to used association.

Resources