Controller HABTM variables not loading in view - cakephp-3.7

I have two related models in a HasAndBelongsToMany association: Subcategoria has many Paquetes, Paquete has many Subcategorias.
In my subcategory page I need to load the related paquetes, and according to my debug-kit toolbar the variables are being loaded correctly. The problem is the view is showing me an "undefined property" error code. I'm sure I'm missing something, but I can't quite get what's the problem.
My models:
class SubcategoriasTable extends Table
{
public function initialize(array $config)
{
parent::initialize($config);
$this->setTable('subcategorias');
$this->setDisplayField('title');
$this->setPrimaryKey('id');
$this->addBehavior('Timestamp');
$this->belongsTo('Categorias', [
'foreignKey' => 'categoria_id',
'joinType' => 'INNER'
]);
$this->belongsToMany('Paquetes');
}
}
class PaquetesTable extends Table
{
public function initialize(array $config)
{
parent::initialize($config);
$this->setTable('paquetes');
$this->setDisplayField('name');
$this->setPrimaryKey('id');
$this->addBehavior('Timestamp');
$this->belongsToMany('Subcategorias', [
'foreignKey' => 'paquete_id',
'targetForeignKey' => 'subcategoria_id',
'joinTable' => 'paquetes_subcategorias',
'dependant' => false
]);
}
}
The controller I'm having troubles with:
namespace App\Controller;
use App\Controller\AppController;
use Cake\ORM\Query;
class SubcategoriasController extends AppController
{
public function content($slug = null)
{
$subcategory = $this->Subcategorias
->findBySlug($slug)
->select(['title', 'id'])
->contain('Paquetes', function (Query $q) {
return $q
->select(['name', 'slug','cost','currency'])
->where(['Paquetes.status' => true]);
});
$this->set(compact('subcategory',$subcategory));
}
}
The corresponging view template (content.ctp):
<?php
$subcategoryName = $subcategory->title;
$title = $subcategoryName;
$this->assign('title', $title);
?>
<h1><?= $title ?></h1>
<?php foreach($subcategory->paquetes as $paquete) : ?>
<!-- Each Paquete's loaded fields. -->
<?php endforeach; ?>
So far I'm getting the following error message:
Notice (8): Undefined property: Cake\ORM\Query::$title [APP/Template\Subcategorias\content.ctp, line 2]

You never call first() or firstOrFail() on the query you built, so in your view $subcategory is still just a Query object, not an Entity.
You probably meant:
$subcategory = $this->Subcategorias
->findBySlug($slug)
->select(['title', 'id'])
->contain('Paquetes', function (Query $q) {
return $q
->select(['name', 'slug','cost','currency'])
->where(['Paquetes.status' => true]);
})
->firstOrFail(); // This is the line that fetches the result and marshals it into an Subcategorias Entity

Related

Create custom module for render custom forms through a controller in Drupal 8

I need to render a custom form which is created using Drupal\Core\Form\FormBase and Drupal\Core\Form\FormStateInterface through a controller in custom Drupal 8 module. Is there any guidence or reference to follow to do this?
Actually I tried to render form directly and through a controller. But both ways are not working. Only render the submit button. I refer the drupal 8 documentation also. But I couldn't find a solution for this. Please be kind enough to find my coding samples below. If there are anything wrong. Please correct me.
my_module.routing.yml
partner.content:
path: '/partner'
defaults:
_controller: '\Drupal\partner\Controller\PartnerController::add'
_title: 'Add Partner'
requirements:
_permission: 'access content'
partner.addform:
path: '/partner/add'
defaults:
_form: '\Drupal\partner\Form\AddForm'
_title: 'Add Partner'
requirements:
_permission: 'access content'
AddForm.php
namespace Drupal\my_module\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
class AddForm extends FormBase
{
/**
* Returns form id
*
* #return string
*/
public function getFormId(): string
{
return 'my_module_add_form';
}
/**
* Build form array
*
* #param array $form
* #param FormStateInterface $formState
* #return array
*/
public function buildForm(array $form, FormStateInterface $form_state): array
{
// First name
$form['first_name'] = [
'#type' => 'textField',
'#title' => t('First Name'),
'#required' => true,
];
// Other input fields...
$form['submit'] = array(
'#type' => 'submit',
'#value' => $this->t('Save Changes'),
'#button_type' => 'primary',
);
return $form;
}
public function validateForm(array &$form, FormStateInterface $form_state) {}
public function submitForm(array &$form, FormStateInterface $form_state) {}
}
MyModuleController.php
<?php
namespace Drupal\my_module\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\my_module\Form\AddForm;
class MyModuleController extends ControllerBase
{
public function add()
{
$addForm = new AddForm();
$form = \Drupal::formBuilder()->getForm($addForm);
return [
'#theme' => 'form_my_module_add',
'#form' => $form,
];
}
}
Happy to find out the solution with Hemantha Dhanushka on my comment.
To make it clear this question has a correct answer, here I past the validated comment.
I would recommend you to use the first approach (using routing::_form instead
of Controller). Also, it seems you use the wrong #type for your
first_name field. Try textfield instead of textField.
Also, for people who want to go further, here are some links to implement a proper
routing::_form approach to expose a form as a page instead of using a Controller: https://www.valuebound.com/resources/blog/step-by-step-method-to-create-a-custom-form-in-drupal-8.
For people looking for more help about existing Form Element Reference (textfield, checkboxes, entity_autocomplete, ...) here is an excellent up-to-date article https://drupalize.me/tutorial/form-element-reference?p=2766
You can use buildForm() method for it. Check below code example:
public function add()
{
$form_state = new Drupal\Core\Form\FormState();
$form_state->setRebuild();
$form = \Drupal::formBuilder()->buildForm('Drupal\my_module\Form\AddForm', $form_state);
return [
'#theme' => 'form_my_module_add',
'#form' => $form,
];
}
Reference: https://api.drupal.org/api/drupal/core!lib!Drupal!Core!Form!FormBuilder.php/function/FormBuilder::getForm/8.2.x

Cakephp 3 use mock to test controller

I want to test a controller using a mock.
In my controller
public function myAction() {
$email = new MandrillApi(['template_name'=>'myTemplate']);
$result = $email
->subject('My title')
->from('no-reply#test.com')
->to('dest#test.com')
->send();
if ( isset($result[0]['status']) && $result[0]['status'] === 'sent' )
return $this->redirect(['action' => 'confirmForgotPassword']);
$this->Flash->error(__("Error"));
}
In test
public function testMyAction() {
$this->get("users/my-action");
$this->assertRedirect(['controller' => 'Users', 'action' => 'confirmForgotPassword']);
}
How do I mock the class MandrillApi ? thank you
In your controller-test:
public function controllerSpy($event){
parent::controllerSpy($event);
if (isset($this->_controller)) {
$MandrillApi = $this->getMock('App\Pathtotheclass\MandrillApi', array('subject', 'from', 'to', 'send'));
$this->_controller->MandrillApi = $MandrillApi;
$result = [
0 => [
'status' => 'sent'
]
];
$this->_controller->MandrillApi
->method('send')
->will($this->returnValue($result));
}
}
The controllerSpy method will insert the mocked object once the controller is setup correctly. You don't have to call the controllerSpy method, it gets executed automatically at some point after you make the $this->get(... call in your test.
Obviously you have to change the App\Pathtotheclass-part of the mock-generation to fit the location of your MandrillApi-class.

CakePHP - Adding Form Validation based on Database Fields

I need to validate shipping information entered when goods are dispatched. (I am having a nightmare using Cake!)
Each shipping company has a different format for their tracking references. I have written some regex to validate, and these are stored in my database.
All the validation for CakePHP happens in the model so I cannot use $this to retrieve the correct regex.
The regex is available in the view; is there anyway to use this to validate before the form is submitted?
I am currently sending the data through an ajax call
Controller
public function editTracking() {
$this->autoRender = false;
if ($this->request->is('ajax')) {
if($this->GoodsOutNote->save($this->request->data['GoodsOutNote'])){
$this->GoodsOutNote->save($this->request->data['GoodsOutNote']);
print_r($this->request->data['GoodsOutNote']['tracking_details']);
}else{
print_r($errors = $this->GoodsOutNote->validationErrors);
}
}
}
View
<?php echo $this->Form->create('GoodsOutNote',array(
'action'=>'editTracking','default' => false)); ?>
<fieldset>
<?php
echo $this->Form->input('id',array(
'default'=>$goodsOutNote['GoodsOutNote']['id']));
echo $this->Form->input('tracking_details',array(
'default'=>$goodsOutNote['GoodsOutNote']['tracking_details']));
?>
</fieldset>
<?php echo $this->Form->end(__('Submit'));
$data = $this->Js->get('#GoodsOutNoteEditTrackingForm')->serializeForm(array(
'isForm' => true, 'inline' => true));
$this->Js->get('#GoodsOutNoteEditTrackingForm')->event('submit',
$this->Js->request(
array('action' => 'editTracking', 'controller' => 'goods_out_notes'),
array(
'update' => '#tracking_details,#GoodsOutNoteTrackingDetails',
'data' => $data,
'async' => true,
'dataExpression'=>true,
'method' => 'PUT'
)
)
);
echo $this->Js->writeBuffer();
?>
In the view, I can use $goodsOutNote['ShippingMethod']['valid_regex'] to access the correct format but I am lost as to how I can pass this to the form.
I have fixed this using the following approach.
In my controller, I retrieve the full record that I am about to edit. I can then validate the input using preg_match(). I would really appreciate any comments on this - is there a better approach?
public function editTracking() {
$this->autoRender = false;
if ($this->request->is('ajax')) {
$id = $this->request->data['GoodsOutNote']['id'];
$options = array('conditions' => array('GoodsOutNote.' . $this->GoodsOutNote->primaryKey => $id));
$goodsOutNote = $this->GoodsOutNote->find('first', $options);
$trackingRef = $this->request->data['GoodsOutNote']['tracking_details'];
$regex = "/".$goodsOutNote['ShippingMethod']['valid_regex']."/";
if(preg_match($trackingRef,$regex)){
if($this->GoodsOutNote->save($this->request->data['GoodsOutNote'])){
$this->GoodsOutNote->save($this->request->data['GoodsOutNote']);
print_r($trackingRef);
}
else{
print_r($errors = $this->GoodsOutNote->validationErrors);
}
}
else {
print_r($errors = $trackingRef.'is not valid');
}
}
}

how to mock laravel eloquent model

I keep fumbling over this - how do I mock a model that extends form Eloquent in Laravel 4 for my unit test?
I keep getting the following error w/ my current way
ErrorException: Trying to get property of non-object
Example
use \Repository\Text\EloquentText;
use \Faker\Factory as Faker;
class EloquentTextTest extends TestCase {
public function setUp()
{
parent::setUp();
$stub = $this->getMock('Text');
$stub->expects($this->any())->method('save');
$this->_fixture = new EloquentText($stub);
}
/**
* #test
*/
public function createShouldCreateNewTextEntry()
{
$faker = Faker::Create();
$data = [
'title' => $faker->sentence,
'content' => $faker->text,
'level_id' => $faker->randomDigit,
'is_public' => $faker->numberBetween(0, 1),
'is_visible' => $faker->numberBetween(0, 1),
];
$text = $this->_fixture->create($data);
$this->assertEquals($data['title'], $text->title);
$this->assertEquals($data['content'], $text->content);
$this->assertEquals($data['level_id'], $text->level_id);
$this->assertEquals($data['is_public'], $text->is_public);
$this->assertEquals($data['is_visible'], $text->is_visible);
return $text;
}

Accessing Model in CakePHP Controller Test

I'm new to CakePHP, and I just started writing my first tests. Usually doing Ruby on Rails, my approach to testing a Controller::create action would be to call the create action, and then comparing the number of models before and after that call, making sure it increased by one.
Would anyone test this any other way?
Is there an easy (builtin) way to access models from a ControllerTest in CakePHP? I couldn't find anything in the source, and accessing it through the Controller seems wrong.
I ended up doing something like this:
class AbstractControllerTestCase extends ControllerTestCase {
/**
* Load models, to be used like $this->DummyModel->[...]
* #param array
*/
public function loadModels() {
$models = func_get_args();
foreach ($models as $modelClass) {
$name = $modelClass . 'Model';
if(!isset($this->{$name})) {
$this->{$name} = ClassRegistry::init(array(
'class' => $modelClass, 'alias' => $modelClass
));
}
}
}
}
Then my tests inherit from AbstractControllerTestCase, call $this->loadModels('User'); in setUp and can do something like this in the test:
$countBefore = $this->UserModel->find('count');
// call the action with POST params
$countAfter = $this->UserModel->find('count');
$this->assertEquals($countAfter, $countBefore + 1);
Note that I'm new to CakePHP but came here with this question. Here's what I ended up doing.
I got my idea from #amiuhle, but I just do it manually in setUp, like how they mention in the model tests at http://book.cakephp.org/2.0/en/development/testing.html.
public function setUp() {
$this->Signup = ClassRegistry::init('Signup');
}
public function testMyTestXYZ() {
$data = array('first_name' => 'name');
$countBefore = $this->Signup->find('count');
$result = $this->testAction('/signups/add',
array(
'data' => array(
'Signup' => $data)
)
);
$countAfter = $this->Signup->find('count');
$this->assertEquals($countAfter, $countBefore + 1);
}
I am not sure why it is necessary to test how many times a model is called or instantiated from the controller action.
So, if I was testing Controller::create... my ControllerTest would contain something like:
testCreate(){
$result = $this->testAction('/controller/create');
if(!strpos($result,'form')){
$this->assertFalse(true);
}
$data = array(
'Article' => array(
'user_id' => 1,
'published' => 1,
'slug' => 'new-article',
'title' => 'New Article',
'body' => 'New Body'
)
);
$result = $this->testAction(
'/controller/create',
array('data' => $data, 'method' => 'post')
);
if(!strpos($result,'Record has been successfully created')){
$this->assertFalse(true);
}
}
The main things you want to test for is whether you are getting the right output for the input. And you can use xDebug profiler to easily find out what classes get instnantiated in a particular action and even how many times. There is no need to test for that manually!