How to write a join query with two database in cakephp. Here is my code and I want to connect two db connection but I have a single Db Connection.
<?php
namespace App\Controller;
use App\Controller\AppController;
use Cake\Datasource\ConnectionManager;
use src\Model\Table\classScheduleDbConnTable;
class TimeTableController extends AppController {
public function index() {
$jpDB = ConnectionManager::get('default');
$ClsDB = ConnectionManager::get('test');
$result = $ClsDB->execute('SELECT * FROM t_class_schedule')->fetchAll('assoc');
$data=array();
foreach ($result as $key => $value) {
$data[$key]['id'] = $value['id'];
$dates = date("Y-m-d", strtotime($value['classDate']));
$data[$key]['fromTime'] = $value['fromTime'];
$data[$key]['toTime'] = $value['toTime'];
$data[$key]['keyWord'] = $value['keyWord'];
$data[$key]['classDate'] = $dates;
}
$this->set('datas',$data);
}
}
I don't believe you can just "connect" two ConnectionManagers in this way.
You probably have two options:
Option A - Specify the database name statically as part of the Table's initialization per the example in this post:
public function initialize(array $config)
{
$this->table('databaseName.tableName');
// ...
}
Option B - Specify an alternative connection & Lazy Load
You can specify an alternative connection names for a specific Model at the Table level:
class ClassSchedule extends Table
{
public static function defaultConnectionName() {
return 'test';
}
}
Note that this can't be used in a join on other Tables using the default connection. You'll instead have to "lazy load" associated data manually. As a general example (I don't know what your associations are, just a random example):
$student->class_schedules = TableRegistry::get('ClassSchedules')->find()
->where(['student_id'=>$student-id])
->toArray();
Related
I want to update a record:
//Update: id is set to a numerical value
$this->Recipe->id = 2;
$this->Recipe->save($this->data);
This is working fine.
But it won't work if I try to update the record based on tmp_id:
$this->Recipe->tmp_id = 2;
$this->Recipe->save($this->data);
I am aware of function Model::updateAll(). But I want to use Model::save() instead.
The following should work:
$this->Recipe->primaryKey = 'tmp_id';
$this->Recipe->id = 2;
$this->Recipe->save($this->data);
Or you could make changes permanent by defining tmp_id as the primary key in your Model:
class Recipe extends AppModel {
public $primaryKey = 'tmp_id';
}
Update statement in cakephp :
public function update_data($tmp_id){
if($this->request->is('post'){
$this->Recipe->id = $tmp_id;
$this->Recipe->save($this->data);
}
}
Here update_data is Dummy function for your understanding.
I know we have result set to get a row as object But How can I get every field as a separate object ? consider of this database row :
user_id address_id product_id shop_id
5 3 134 2
I want to retrieve and save the row as follows :
userEntity AddressEntity ProductEntity ShopEntity
This is not how the TableDataGateway is supposed to be used, since what you are looking for are more complex features such as the ones of Doctrine 2 ORM and similar data-mappers.
Here is one possible solution to the problem, which involves using a custom hydrator (docs). My example is simplified, but I hope it clarifies how you are supposed to build your resultset.
First, define your entities (I'm simplifying the example assuming that UserEntity is the root of your hydration):
class UserEntity {
/* fields public for simplicity of the example */
public $address;
public $product;
public $shop;
}
class AddressEntity { /* add public fields here for simplicity */ }
class ProductEntity { /* add public fields here for simplicity */ }
class ShopEntity { /* add public fields here for simplicity */ }
Then, build hydrators specific for the single entities:
use Zend\Stdlib\Hydrator\HydratorInterface as Hydrator;
class AddressHydrator implements Hydrator {
// #TODO: implementation up to you
}
class ProductHydrator implements Hydrator {
// #TODO: implementation up to you
}
class ShopHydrator implements Hydrator {
// #TODO: implementation up to you
}
Then we aggregate these hydrators into one that is specifically built to hydrate a UserEntity:
class UserHydrator extends \Zend\Stdlib\Hydrator\ObjectProperty {
public function __construct(
Hydrator $addressHydrator,
Hydrator $productHydrator,
Hydrator $shopHydrator
) {
$this->addressHydrator = $addressHydrator;
$this->productHydrator = $productHydrator;
$this->shopHydrator = $shopHydrator;
}
public function hydrate(array $data, $object)
{
if (isset($data['address_id'])) {
$data['address'] = $this->addressHydrator->hydrate($data, new AddressEntity());
}
if (isset($data['product_id'])) {
$data['product'] = $this->productHydrator->hydrate($data, new ProductEntity());
}
if (isset($data['shop_id'])) {
$data['shop'] = $this->shopHydrator->hydrate($data, new ShopEntity());
}
return parent::hydrate($data, $object);
}
}
Now you can use it to work with your resultset. Let's define the service for your UserEntityTableGateway:
'UserEntityTableGateway' => function ($sm) {
$dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
$resultSetPrototype = new ResultSet();
$resultSetPrototype->setArrayObjectPrototype(new UserHydrator());
return new TableGateway('user', $dbAdapter, null, $resultSetPrototype);
},
These are all simplified examples, but they should help you understanding how powerful hydrators can be, and how you can compose them to solve complex problems.
You may also check the chapters in the documentation about the Aggregate Hydrator and Hydration Strategies, which were designed specifically to solve your problem.
I like to produce some universal function for my project
in a harcode php mode it would be like :
<select id="segment" name="segment">
<option value=''></option>
<?php
$sql = "select segment_id, segment_name, segment_parentid
from test_segment where segment_parentid = 'root' and segment_status = '1' order by segment_name asc";
$sql = $koneksi_db->sql_query($sql);
while($row = $koneksi_db->sql_fetchrow($sql)){
printf( "<option value=$row[segment_id] disabled>".ucwords($row['segment_name'])."</option>" );
$sql1 = "select segment_id, segment_name, segment_parentid from test_segment where convert(varchar, segment_id) <> 'root' and
segment_parentid= '$row[segment_id]' and segment_status = '1' order by segment_name asc";
$sql1 = $koneksi_db->sql_query($sql1);
while($row1 = $koneksi_db->sql_fetchrow($sql1)){
printf( "<option value=$row1[segment_id]> |_".ucwords($row1['segment_name'])."</option>" );
}
}
?>
</select>
How to convert that code into cakephp MVC method code? the Controller and Model
, I shall not to breaking MVC method with hardcode way for this code.
note : $koneksi_db->sql_query, $koneksi_db->sql_fetchrow was my own function for query process, and for query I cannot change into cakephp method, because it was restrict procedure from my mentor.
One way is to create a model representing the dataset you want. Then load the model for any controller that needs access to the data and that controller's view using that data in the in the select.
Step1: Create a MySQL view to represent the two SQL statements:
CREATE VIEW segments AS
SELECT segment_id, UPPER(segment_name), segment_parentid, UPPER(segment_name) AS sortfield
FROM test_segment
WHERE segment_parentid = 'root' AND segment_status = '1'
UNION
SELECT UPPER(test_segment.segment_id), test_segment.segment_name, test_segment.segment_parentid, UPPER(CONCAT(parent.segment_name, test_segment.segment_name))
FROM test_segment
JOIN test_segment AS parent
ON test_segment.segment_parentid = parent.segment_id
AND parent.segment_status = '1'
WHERE convert(varchar, test_segment.segment_id) <> 'root'
AND test_segment.segment_status = '1';
Step2: Create Model for this
<?php
App::uses('AppModel', 'Model');
class Segment extends AppModel {
// the name of the view
public $useTable = 'segment';
public $displayField = 'segment_name';
public $primaryKey = 'segment_id';
}
Step3: In whatever controller method you want this, Use this code
$this->loadModel('Segment');
$segments = $this->Segment->find('list', array('order => array('sortfield')));
set('segments', $segments);
Step4: Then in that controller's view, add the element (I'm assuming you will use the form helper, otherwise you can write the html out:
echo $this->Form->select('segments',$segments);
I'm wondering if there's a way to add created_by and modified_by similar to how created and modified work in CakePHP?
I like the fact that cake recognizes those fields and takes care of them automatically, regardless of the model, without me having to implement them at all. I would like to add a similar feature using the current user id (in my application, there is always a userid, even if it sometimes may be 0).
I assume the starting place is before_save() in app_model?
--
Also, is there any way for me to get cake to recognize this as a foreign key to the user table automatically (similar to how it recognizes user_id), or will I have to add the has/belongs to relationship manually? I ask because this will go on most of my models, so I'd prefer to reduce redundant code.
Thank you!
For the first part of your question, I use this Behavior code to do exactly what you are looking for:
class UserLinkBehavior extends ModelBehavior
{
/**
* The string to use to retrieve the user id from CakeSession
*
* #var string
*/
var $user_id_key = 'Auth.User.id';
function setup(&$model, $settings)
{
if(isset($settings['user_id_key']))
{
$this->user_id_key = $settings['user_id_key'];
}
}
function beforeSave(&$model)
{
App::uses('CakeSession', 'Model/Datasource');
$logged_user_id = CakeSession::read($this->user_id_key);
if(isset($logged_user_id))
{
$this->set_user_on_current_model($model, $logged_user_id);
}
return true;
}
/**
* Set the created_by and modified_by user id on the current model
*
* #param Model $model
* #param int $logged_user_id
* #return void
*/
private function set_user_on_current_model(&$model, $logged_user_id)
{
if(isset($logged_user_id))
{
/*
* Id is not set -> it is a creation
*/
if($model->hasField('created_by') && (!isset($model->data[$model->alias]['id']) || empty($model->data[$model->alias]['id'])))
{
if(!isset($model->data[$model->alias]['created_by']))
{
$model->data[$model->alias]['created_by'] = $logged_user_id;
/*
* If the save is called with a whitelist, add 'created_by' to the whitelist
* in order to have this field saved as well
*/
if(!empty($model->whitelist) && !in_array('created_by', $model->whitelist))
{
$model->whitelist[] = 'created_by';
}
}
}
/*
* Id is set -> it is an update
*/
if($model->hasField('modified_by') && isset($model->data[$model->alias]['id']) && !empty($model->data[$model->alias]['id']))
{
$model->data[$model->alias]['modified_by'] = $logged_user_id;
/*
* If the save is called with a whitelist, add 'modified_by' to the whitelist
* in order to have this field saved as well
*/
if(!empty($model->whitelist) && !in_array('modified_by', $model->whitelist))
{
$model->whitelist[] = 'modified_by';
}
}
}
}
}
Then just declare it in your Model or your AppModel
var $actsAs = array('UserLink');
For the second part of your question, you could probably add a beforeFind() callback to the behavior and use the model->bindModel() function to link the model having the created_by and modified_by fields with a User model. Personaly I prefere to declare these links in each model manually when I need them.
It cannot be like the created and modified field but you can add this fields in the controller method wherever you want.
eg. in the add method you can add like follows.
$this->request->data['ModelName']['created_by'] = $this->Auth->user['userid'];
I found this one and working good for me its easy to implement and understand
<?php
App::uses('Model', 'Model');
class AppModel extends Model {
//get current logged-in user
public function getCurrentUser() {
App::uses('CakeSession', 'Model/Datasource');
$Session = new CakeSession();
$user = $Session->read('Auth.User');
return $user['id'];
}
//populate created_by and modified_by
public function beforeSave($options = array()) {
parent::beforeSave($options);
//find all fields from table created_by/modified_by exists
$fields = array_keys($this->getColumnTypes());
//get modal name to feed in data in appropriate array key
$modal = array_keys($this->data);
$modal = $modal[0];
//add created_by value
if(in_array('created_by', $fields) && !isset($this->data[$modal]['id'])){
//correct this line
$this->data[$modal]['created_by'] = $this->getCurrentUser()==null?-1:$this->getCurrentUser();
return true;
}elseif(in_array('modified_by', $fields)){
$this->data[$modal]['modified_by'] = $this->getCurrentUser()==null?-1:$this->getCurrentUser();
return true;
}
return true;
}
}
How (if possible at all) do you change the entity type with Doctrine2, using it's Class Table Inheritance?
Let's say I have a Person parent class type and two inherited types Employe and Client. My system allows to create a Person and specify it's type - that's fairly easy to implement - but I'd also like to be able to change the person from an Employe to a Client, while maintaining the Person-level information (it's id and other associated records).
Is there a simple way to do this with Doctrine2?
I was looking for this behaviour yesterday also.
In the end, after speaking with people in #doctrine on freenode, I was told that it is not possible.
If you want to do this, then you have to go through this:
Upgrading a User
Grab the Person Entity.
Update the discrimator column so that it is no longer a 'person' and change it to 'employee'
Create a corresponding row inyour Employee table for this inheritance.
Removing Inheritance
Likewise if you want to remove inheritance, you have to..
Grab the Person Entity.
Update the discrimnator column so that it is no longer an 'employee' and change it to a 'person'.
Delete the corresponding row in your Employee table. (Yes you have to delete it, just change the discrimator coumn is not sufficient).
This might be 7 months late, but it is at least the correct answer for anything else looking to suport such a feature.
PHP doesn't have support for object casting, so Doctrine doesn't support it. To workaround the problem I write this static method into parent classes:
public static function castToMe($obj) {
$class = get_called_class();
$newObj = New $class();
foreach (get_class_vars(get_class($newObj)) as $property => $value) {
if (method_exists($obj, 'get' . ucfirst($property)) && method_exists($newObj, 'set' . ucfirst($property))) {
$newObj->{'set' . ucfirst($property)}($obj->{'get' . ucfirst($property)}());
}
}
return $newObj;
}
You can create this method in class Person and use it to cast from Employe to Client and viceversa:
$employe = New Employe();
$client = Client::castToMe($employe);
Now, if you want, you can remove the $employe entity.
You could do something like this though:
This Trait can be used on your Repository class:
namespace App\Doctrine\Repository;
trait DiscriminatorTrait
{
abstract public function getClassMetadata();
abstract public function getEntityManager();
private function updateDiscriminatorColumn($id, $class)
{
$classMetadata = $this->getClassMetadata();
if (!in_array($class, $classMetadata->discriminatorMap)) {
throw new \Exception("invalid discriminator class: " . $class);
}
$identifier = $classMetadata->fieldMappings[$classMetadata->identifier[0]]["columnName"];
$column = $classMetadata->discriminatorColumn["fieldName"];
$value = array_search($class, $classMetadata->discriminatorMap);
$connection = $this->getEntityManager()->getConnection();
$connection->update(
$classMetadata->table["name"],
[$column => $value],
[$identifier => $id]
);
}
}
There still might be some extra work you need to put in, like clearing values in fields that are only present on one of your sub-classes
In Doctrine2, when you have your parent entity class, Person set as:
/**
* #Entity
* #InheritanceType("JOINED")
* #DiscriminatorColumn(name="discr", type="string")
* #DiscriminatorMap({"person" = "Person", "employee" = "Employee", , "client" = "Client"})
*/
class Person
{
// ...
}
and sub classes such as Client set as:
/** #Entity */
class Client extends Person
{
// ...
}
when you instantiate Person as:
$person = new Person();
Doctrine2 checks your #DiscriminatorMap statement (above) for a corresponding mapping to Person and when found, creates a string value in the table column set in #DiscriminatorColumn above.
So when you decide to have an instance of Client as:
$client = new Client();
Following these principles, Doctrine2 will create an instance for you as long as you have declared the parameters in the #DiscriminatorMap. Also an entry will be made on the Person table, in the discr column to reflect that type of entity class that has just been instantiated.
Hope that helps. It's all in the documentation though
i use this method
trait DiscriminatorTrait
{
// ...
public function updateDiscriminatorColumn($id, $class)
{
// ... other code here
$connection->update(
"Person", // <-- just there i put my parent class
[$column => $value],
[$identifier => $id]
);
}
}
and i use call like this after :
$this->em->getRepository(Client::class)->updateDiscriminatorColumn($cCenter->getId(), Employe::class);
$this->em->close();
// I update the data directly without going through doctrine otherwise it will create a new Person
try {
$query = "
INSERT INTO Employe (id, /* ... other fields */)
VALUES ({$callCenter->getId()}, /* ... other fields */)
";
$results = $this->connection->executeQuery($query)->execute();
} catch (\Exception $exception) {
echo $exception->getMessage().PHP_EOL;
}
$this->em->close();
// i restart the connection
/** #var EntityManagerInterface $entityManager */
$entityManager = $this->em;
if ($this->em->isOpen() === false) {
$this->em = $entityManager->create(
$this->em->getConnection(),
$this->em->getConfiguration(),
$this->em->getEventManager()
);
}
// and there a get Employer en update him
$employe = $this->em->getRepository(Employe::class)->find($id);
$employe->setFirstname($callCenter->getFirstName());
// other code
And it is work for me