Could not view with composite pK CakePHP4 - cakephp

In cakephp-4.x I could not access Controller's view action for composite primary key table. (http://localhost:8765/invoice-items/view/1)
Here are samples of the code created by cake bake:
InvoiceItemsTable calss in which the primary key is defined as composite.
class InvoiceItemsTable extends Table
{
/**
* Initialize method
*
* #param array $config The configuration for the Table.
* #return void
*/
public function initialize(array $config): void
{
parent::initialize($config);
$this->setTable('invoice_items');
$this->setDisplayField(['item_id', 'invoice_id', 'unit_id']);
$this->setPrimaryKey(['item_id', 'invoice_id', 'unit_id']);
$this->addBehavior('Timestamp');
$this->belongsTo('Items', [
'foreignKey' => 'item_id',
'joinType' => 'INNER',
]);
$this->belongsTo('Invoices', [
'foreignKey' => 'invoice_id',
'joinType' => 'INNER',
]);
$this->belongsTo('Units', [
'foreignKey' => 'unit_id',
'joinType' => 'INNER',
]);
}
...
InvoiceItemsController view method:
/**
* View method
*
* #param string|null $id Invoice Item id.
* #return \Cake\Http\Response|null|void Renders view
* #throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
*/
public function view($id = null)
{
$invoiceItem = $this->InvoiceItems->get($id, [
'contain' => ['Items', 'Invoices', 'Units'],
]);
$this->set(compact('invoiceItem'));
}
Finally a screen shot of invoice_items table structure from phpmyadmin:
I have tried to access the view like (http://localhost:8765/invoice-items/view/1,1,6) but I got the same error ... with primary key ['1,1,6']. I do not know how to represent the composite primary key in the URL? or what is the problem?
I use CakePHP version 4.4.2

Table::get() expects composite keys to be passed as arrays, eg [1, 1, 6].
Assuming you're using the fallback routes from the default app skeleton, you can pass additional arguments as path parts, eg:
/invoice-items/view/1/1/6
and accept them in your controller action like:
public function view($itemId, $invoiceId, $unitId)
and build an array from the accordingly to pass to get() as the "id":
$this->InvoiceItems->get([$itemId, $invoiceId, $unitId], /* ... */)
In case you're using custom routes with fixed parameters, add additional ones in whatever form you like, for example with dashes:
$routes
->connect(
'/invoice-items/view/{itemId}-{invoiceId}-{unitId}',
['controller' => 'InvoiceItems', 'action' => 'view']
)
->setPass(['itemId', 'invoiceId', 'unitId'])
->setPatterns([
'itemId' => '\d+',
'invoiceId' => '\d+',
'unitId' => '\d+',
]);
then your URL would look like:
/invoice-items/view/1-1-6
See also
Cookbook > Routing > Route Elements
Cookbook > Routing > Passing Parameters to Action
Cookbook > Routing > Fallbacks Method

Related

Cakephp 3.5 - Entity - Virtual Field not showing

I have defined in an entity this :
protected $_virtual = [ 'full_name' ];
protected function _getFullName()
{
return( $this->_properties['firstname'] . ' ' . $this->_properties['lastname'] );
}
But the full_name field is not retrieved by any query ( paginator or find('all') ) ... when the table is referred as an associated table.
The main table is GenPersons.
In that case, the field is showed correctly.
But then the I make
$this->paginate = [ 'contain' => ['GenPersons'] ]
from the AerPilots controller ( AerPilots is a model ) ; and try to get the field as
$aerPilot->gen_person->full_name;
nothing is showed.
Am I forgetting something ?
I found it.
The problem was in the association on the model.
The GenPersons table belongs to General plugin.
The AerPilots table belongs to Aeronautical plugin.
When I baked the model for AerPilots, it generated this code :
$this->belongsTo('GenPersons', [
'foreignKey' => 'gen_person_id',
'className' => '**Aeronautical**.GenPersons'
]);
And it must be :
$this->belongsTo('GenPersons', [
'foreignKey' => 'gen_person_id',
'className' => '**General**.GenPersons'
]);

Creating Association with condition using other association in CakePHP 3

I'm building a cake php 3 app. My app model includes 3 Tables (amongst others):
Structures
MeasuringPoints
DeviceTypes
where each Strcuture can have multiple MeasuringPoints:
// StrcuturesTable.php
...
public function initialize(array $config)
{
parent::initialize($config);
...
$this->hasMany('MeasuringPoints', [
'foreignKey' => 'structure_id'
]);
}
Further, each measuring point is of a certain device type:
// MeasuringPointsTable.php
...
public function initialize(array $config)
{
parent::initialize($config);
...
$this->belongsTo('DeviceTypes', [
'foreignKey' => 'device_type_id',
'joinType' => 'INNER'
]);
}
What i'm lookong for, is how to create a 'SpecialMeasuringPoints' association in the Structure table.
Somewhat like:
// MeasuringPointsTable.php
...
$this->hasMany('SpecialMeasuringPoints',[
'className' => 'MeasuringPoints',
'foreignKey' => 'structure_id',
'conditions' => ['MeasuringPoints.DeviceTypes.id'=>1]
]);
As you may see, I want only those measuring points, whose associated device type has the id 1.
However, the previous association condition is not valid; and i have no clue how to correctly implement this.
Any help is appreciated.
Correct, that condition is invalid, for a number of reasons. First of all paths aren't supported at all, and even if they were, you already are in MeasuringPoints, respectively SpecialMeasuringPoints, so there would be no need to indicate that again.
While it would be possible to pass a condition like:
'DeviceTypes.id' => 1
That would require to alawys contain DeviceTypes when retrieving SpecialMeasuringPoints.
I would suggest to use a finder, that way you can easily include DeviceTypes and match against your required conditions. Something like:
$this->hasMany('SpecialMeasuringPoints',[
'className' => 'MeasuringPoints',
'foreignKey' => 'structure_id',
'finder' => 'specialMeasuringPoints'
]);
In your MeasuringPoints class define the appropriate finder, for example using matching(), and you should be good:
public function findSpecialMeasuringPoints(\Cake\ORM\Query $query) {
return $query
->matching('DeviceTypes', function (\Cake\ORM\Query $query) {
return $query
->where([
'DeviceTypes.id' => 1
]);
});
}
Similar could be done via the conditions option when passing a callback, which however is less DRY:
$this->hasMany('SpecialMeasuringPoints',[
'className' => 'MeasuringPoints',
'foreignKey' => 'structure_id',
'conditions' => function (
\Cake\Database\Expression\QueryExpression $exp,
\Cake\ORM\Query $query
) {
$query
->matching('DeviceTypes', function (\Cake\ORM\Query $query) {
return $query
->where([
'DeviceTypes.id' => 1
]);
return $exp;
}
]);
It should be noted that in both cases you need to be aware that such constructs are not compatible with cascading/dependent deletes, so do not try to unlink/delete via such associations!
See also
Cookbook > Database Access & ORM > Retrieving Data & Results Sets > Custom Finder Methods
Cookbook > Database Access & ORM > Retrieving Data & Results Sets > Filtering by Associated Data

Symfony 2 override entity field property

I want to override the entity field property. I need to get data from another database table (mapped by id). It should be a combination of "artikelnummer" and a field called "name" from another database table.
$builder->add('schlauch', 'entity', array(
'class' => 'SchlauchBundle:Artikelspezifikation',
'property' => 'artikelnummer',
'attr' => array(
'class' => 'extended-select'
),
'data_class' => null
));
The field "artikelnummer" outputs something like "12345" but I need to add the name (from another database table called "schlauch"), so it should look like "12345 Articlename". I tried a query in the entity file, but I dont't want to manipulate the output everywhere.
Is it possible to use a query for property and override it?
You can simple solve that by adding new getter to you entity:
class Artikelspezifikation
{
//…
/**
* #var Schlauch
*
* #ORM\ManyToOne(targetEntity="Schlauch", inversedBy="artikelspezifikations")
*/
private $schlauch;
//…
/**
* Get display name
*
* #return string
*/
public function getDisplayName()
{
return $this->artikelnummer . ' ' . $this->schlauch->getArtikelName();
}
//…
/**
* Set schlauch
*
* #param \SchlauchBundle\Entity\Schlauch $schlauch
*
* #return Artikelspezifikation
*/
public function setCategory(\SchlauchBundle\Entity\Schlauch $schlauch = null)
{
$this->schlauch = $schlauch;
return $this;
}
/**
* Get schlauch
*
* #return \SchlauchBundle\Entity\Schlauch
*/
public function getCategory()
{
return $this->schlauch;
}
}
And in your form class just change property:
$builder->add('schlauch', 'entity', array(
'class' => 'SchlauchBundle:Artikelspezifikation',
'property' => 'displayName',
'attr' => array(
'class' => 'extended-select'
),
'data_class' => null
));

Applying sessions in cakephp 3.2

Im using cakephp 3.2 to build an application. Im using the bookmarks tutorial as a basis for my project. in one of my bookmarks .ctp view files I would like to have a number of select boxes with data specific to the user loggged in. i have two tables namely users and bookmarks. My bookmarks table contains foreign key from users table user_id.
Here's my bookmark table with the fields i would like the dropdowns. id, user_id, title, systemregistration, systemroles, country, province, metropolitan.
Code for my appcontroller
namespace App\Controller;
use Cake\Controller\Controller;
use Cake\Event\Event;
/**
* Application Controller
*
* Add your application-wide methods in the class below, your controllers
* will inherit them.
*
* #link http://book.cakephp.org/3.0/en/controllers.html#the-app-controller
*/
class AppController extends Controller
{
/**
* Initialization hook method.
*
* Use this method to add common initialization code like loading components.
*
* e.g. `$this->loadComponent('Security');`
*
* #return void
*/
/*public function initialize()
{
parent::initialize();
$this->loadComponent('RequestHandler');
$this->loadComponent('Flash');
}*/
public function initialize()
{
$this->loadComponent('Flash');
$this->loadComponent('Auth', [
'authenticate' => [
'Form' => [
'fields' => [
'username' => 'email',
'password' => 'password'
]
]
],
'loginAction' => [
'controller' => 'Users',
'action' => 'login'
],
//'storage' => 'Session'
'Session'
]);
// Allow the display action so our pages controller
// continues to work.
$this->Auth->allow(['display']);
}
/*public function initialize()
{
$this->loadComponent('Flash');
$this->loadComponent('Auth', [
'loginRedirect' => [
'controller' => 'Bookmarks',
'action' => 'index'
],
'logoutRedirect' => [
'controller' => 'Pages',
'action' => 'display',
'home'
]
]);
}
public function beforeFilter(Event $event)
{
$this->Auth->allow(['index', 'view', 'display']);
}*/
/**
* Before render callback.
*
* #param \Cake\Event\Event $event The beforeRender event.
* #return void
*/
public function beforeRender(Event $event)
{
if (!array_key_exists('_serialize', $this->viewVars) &&
in_array($this->response->type(), ['application/json', 'application/xml'])
) {
$this->set('_serialize', true);
}
}
}
//BookmarksController looks like this
namespace App\Controller;
use App\Controller\AppController;
/**
* Bookmarks Controller
*
* #property \App\Model\Table\BookmarksTable $Bookmarks
*/
class BookmarksController extends AppController
{
public function internalprotocol()
{
$bookmark = $this->Bookmarks->newEntity();
$users = $this->Bookmarks->Users->find('list', ['limit' => 200]);
$tags = $this->Bookmarks->Tags->find('list', ['limit' => 200]);
$this->set(compact('bookmark', 'users', 'tags'));
$this->set('_serialize', ['bookmark']);
$bookmarks = $this->paginate($this->Bookmarks);
$this->set(compact('bookmarks'));
$this->set('_serialize', ['bookmarks']);
}
}
//my internalprotocol.ctp looks like this
<div>
<?php echo $this->Form->input('user_id', ['options' => $bookmarks]); ?>
<?php echo $this->Form->input('title', ['options' => $bookmarks]); ?>
<?php echo $this->Form->input('systemregistration', ['options' => $bookmarks]); ?>
<?php echo $this->Form->input('systemroles', ['options' => $bookmarks]); ?>
<?php echo $this->Form->input('country', ['options' => $bookmarks]); ?>
</div>
I would like to populate each of the fields with data specific to the user logged in. Could you please help!
You don't need to do anything. If a login is successful you can access the logged in user details through the Auth component using $this->Auth->user();
If you need to add any more information to the session you can use the Session component like $this->Session->write('User.AscociatedData', $AscociatedData);
Easiest way to access this data in the view is to set authenticated user as a view variable in the controller:
$this->set('user',$this->Auth->user());
then you can accesses the users info in the view with $user e.g$user->fieldName
Not entirely sure what your asking but I hope one of my answers is relevant.
we only need to show bookmarks for the currently logged in user.
We can do that by updating the call to paginate().Make your index() action from Controller/BookmarksController.php look like:
public function index()
{
$this->paginate = [
'conditions' => [
'Bookmarks.user_id' => $this->Auth->user('id'),
]
];
$this->set('bookmarks', $this->paginate($this->Bookmarks));
$this->set('_serialize', ['bookmarks']);
}
We should also update the tags() action and the related finder method as we done for bookmarks above
Please read the tutorial
http://book.cakephp.org/3.0/en/tutorials-and-examples/bookmarks/part-two.html#fixing-list-view-and-forms

How to change the join type of a contained association per find() call?

How to reset the type of joins in different places?
Here are my tables:
Tenancy Table
class TenancyTable extends Table
{
/**
* Initialize method
*
* #param array $config The configuration for the Table.
* #return void
*/
public function initialize(array $config)
{
parent::initialize($config);
$this->table('tenancy');
$this->displayField('id');
$this->primaryKey('id');
$this->addBehavior('Timestamp');
$this->belongsTo('Properties', [
'foreignKey' => 'property_id',
'className' => 'property',
'joinType' => 'INNER'
]);
Property Table
class PropertyTable extends Table
{
/**
* Initialize method
*
* #param array $config The configuration for the Table.
* #return void
*/
public function initialize(array $config)
{
parent::initialize($config);
$this->table('property');
$this->displayField('id');
$this->primaryKey('id');
$this->addBehavior('Timestamp');
$this->hasMany('Tenancies', [
'foreignKey' => 'property_id',
'className' => 'tenancy',
'joinType' => 'LEFT'
]);
For example change the Tenancy join type to Right
$setting = [
'contain' => [
'Tenancies' => [
'joinType' => 'RIGHT'
]
]
];
$properties = $this->find('all', $setting)
->hydrate(false)
->select(['Property.id', 'Property.company_id', 'Property.address1', 'Property.postcode'])
You would do it exactly the way you are showing it, ie by specifying the joinType option in contain settings.
However, since you are querying from the Property table, this will have no effect, as hasMany associations are being retrieved in a separate query, so no joins involved. It would work for hasOne and belongsTo associations, which are being retrieved in the same query.

Resources