Get behavior related configuration from code - php

Are behavior related configurations accessible? In this specific case, a behavior was attached to a table. I would like to know if it's possible to get the fields property in some way, later on in code?
<?php
class MyRandomTable extends Table
{
public function initialize(array $config)
{
parent::initialize($config);
...
// Add Cipher behavior
$this->addBehavior('CipherBehavior.Cipher', [
'fields' => [
'original' => 'string',
'changed' => 'string',
]
]);
}
...
}
?>
If I load the table and dump the content, I do not see the behavior listed:
$table = TableRegistry::get('MyRandomTable');
var_dump($table);
Partial dump content:
protected '_behaviors' =>
object(Cake\ORM\BehaviorRegistry)[170]
protected '_table' =>
&object(Cake\ORM\Table)[172]
protected '_methodMap' =>
array (size=0)
empty
protected '_finderMap' =>
array (size=0)
empty
protected '_loaded' =>
array (size=0)
empty
protected '_eventManager' =>
object(Cake\Event\EventManager)[165]
protected '_listeners' =>
array (size=0)
...
protected '_isGlobal' => boolean false
protected '_eventList' => null
protected '_trackEvents' => boolean false
protected '_eventClass' => string '\Cake\Event\Event' (length=17)
What I'd like to do, in a controller, is to get the fields and pass those on to a view.
Edit #1
Using CakePHP v3.3.16
Edit #2
I'm seeing behavior information as I had missed the plugin prefix when loading the table:
$table = TableRegistry::get('PluginName.MyRandomTable');
Shows:
protected '_behaviors' =>
object(Cake\ORM\BehaviorRegistry)[143]
protected '_table' =>
&object(PluginName\Model\Table\MyRandomTable)[94]
protected '_methodMap' =>
array (size=4)
'timestamp' =>
array (size=2)
...
'touch' =>
array (size=2)
...
'encrypt' =>
array (size=2)
...
'decrypt' =>
array (size=2)
...
protected '_finderMap' =>
array (size=0)
empty
protected '_loaded' =>
array (size=2)
'Timestamp' =>
object(Cake\ORM\Behavior\TimestampBehavior)[181]
...
'Cipher' =>
object(CipherBehavior\Model\Behavior\CipherBehavior)[192]
...
protected '_eventManager' =>
object(Cake\Event\EventManager)[175]
protected '_listeners' =>
array (size=4)
...
protected '_isGlobal' => boolean false
protected '_eventList' => null
protected '_trackEvents' => boolean false
protected '_eventClass' => string '\Cake\Event\Event' (length=17)

First of all your table class file is incorrect, it needs a namespace, otherwise it cannot be found, and you'll end up with an instance of \Cake\ORM\Table (a so called auto/generic-table) instead of concrete subclass thereof, hence your behavior is missing.
That being said, it depends on how the behavior has been programmed. If it follows the default configuration pattern, then you can access the configuration via its config() or getConfig() (as of CakePHP 3.4) methods.
Of course you have to access the behavior, not just the table class to which it is attached. This is done using the behavior registry, which is available via the Table::behaviors() method:
$fields = $table->behaviors()->get('Cipher')->config('fields');
See also
Cookbook > Database Access & ORM > Behaviors > Accessing Loaded Behaviors
Cookbook > Database Access & ORM > Behaviors > Re-configuring Loaded Behaviors
API > \Cake\ORM\BehaviorRegistry
API > \Cake\ORM\Behavior
Cookbook > Configuration > Disabling Generic Tables

You can get Column names of a table by schema()->columns().
Example -
$getColumnArray = $this->Users->schema()->columns();//return Users Table Colums Name Array
$getColumnArray = $this->Users->associations()->keys()//return Users assocation table key

Related

How to extends properly Cms Page and Block WebAPI

I would like to extends/override the APIs of CMS Pages and Blocks to add custom fields (for example, an array of store_ids to allow the creation of a CMS block on different stores with an webapi call, it's my main goal).
I tried many things, but this does not seem clean, standards-compliant. I have the impression of overloading the whole module CMS (data interface, repository interface, model, repository factory, etc..) just for adding new fields in webapi.
I will explain what I have done for now :
1/ I wrote an new data api \BlockInterface who extends the core interface to add my new field (store_id) and declare his getter and setter methods.
namespace Reflet\CmsExtension\Api\Data;
interface BlockInterface extends \Magento\Cms\Api\Data\BlockInterface
{
/**
* New fields
*/
const STORE_ID = 'store_id';
/**
* Get Store Ids
*
* #return array
*/
public function getStoreId();
/**
* Set Store Ids
*
* #param array $storeIds
* #return \Reflet\CmsExtension\Api\Data\BlockInterface
*/
public function setStoreId($storeIds);
}
2/ I implements the Data Interface in the new \Block model object.
3/ With many in the di.xml file, I override the block model type.
4/ I reimplement the api \BlockRepositoryInterface and the webapi.xml file to change the data type used in the repository.
5/ I testing the getById() and the save() methods for the block #1 with simple repository call and webapi.
Here, is my result :
1/ If, I get the block #1 with the repository, the store_id is load in the result object.
array (size=12)
'row_id' => string '1' (length=1)
'block_id' => string '1' (length=1)
'created_in' => string '1' (length=1)
'updated_in' => string '2147483647' (length=10)
'title' => string 'Catalog Events Lister' (length=21)
'identifier' => string 'catalog_events_lister' (length=21)
'content' => string '{{block class="Magento\\CatalogEvent\\Block\\Event\\Lister" name="catalog.event.lister" template="lister.phtml"}}' (length=113)
'creation_time' => string '2017-02-14 14:33:20' (length=19)
'update_time' => string '2017-02-14 14:33:20' (length=19)
'is_active' => string '1' (length=1)
'store_id' =>
array (size=1)
0 => string '0' (length=1)
'stores' =>
array (size=1)
0 => string '0' (length=1)
2/ If, I get the block #1 with the WebAPI, the store_id is also load in the result object.
public 'store_id' =>
array (size=1)
0 => string '0' (length=1)
public 'id' => int 1
public 'identifier' => string 'catalog_events_lister' (length=21)
public 'title' => string 'Catalog Events Lister' (length=21)
public 'content' => string '{{block class="Magento\\CatalogEvent\\Block\\Event\\Lister" name="catalog.event.lister" template="lister.phtml"}}' (length=113)
public 'creation_time' => string '2017-02-14 14:33:20' (length=19)
public 'update_time' => string '2017-02-14 14:33:20' (length=19)
public 'active' => boolean true
3/ If, I save the block #1 with the WebAPI, an error occurs, he load the wrong BlockInterface. I need to reimplement the BlockRepository ? I don't think it's the good and cleaner method (...) ?
public 'message' => string 'Property "StoreId" does not have corresponding setter in class "Magento\Cms\Api\Data\BlockInterface".' (length=101)
public 'trace' => string '#0 /var/www/vhosts/reflet.com/storemanager/vendor/magento/framework/Reflection/NameFinder.php(59): Magento\Framework\Reflection\NameFinder->findAccessorMethodName(Object(Zend\Code\Reflection\ClassReflection), 'StoreId', 'getStoreId', 'isStoreId')
#1 /var/www/vhosts/reflet.com/storemanager/vendor/magento/framework/Webapi/ServiceInputProcessor.php(158): Magento\Framework\Reflection\NameFinder->getGetterMethodName(Object(Zend\Code\Reflection\ClassReflection), 'StoreId')
#2 /var/www/vhosts/reflet.com/storemanag'... (length=2239)
For this example, I use the 'store_id' only, but in the future, it's must be works with all custom fields in Page or Block object.
How do you recommend this modification to be as clean as possible and limit conflicts in the future ? Do you have an example ?
If you want to test, you can find in this archive, my two current Magento modules (Reflet_Api an simple adapter to call REST WebApi and Reflet_CmsExtension to override CMS Blocks) : https://drive.google.com/file/d/0B12trf96j0ALSjhZdmJPTzBPT0U/view
Thank you for your answers.
Best regards.
Jonathan

Setting data in a nested fieldset collection in zend 2 - Returns empty (parent fieldset works)

I have a nested fieldset collection item inside another fieldset Ill that I am trying to set element variables for. I can successfully set elements for the Ill fieldset but am unable to generate or item fieldsets within the collection.
I have a very similar layout to this example: https://framework.zend.com/manual/2.0/en/modules/zend.form.collections.html
The layout of nested forms is:
ILLForm(Form)->Ill(FieldSet)->Item(Collection/Fieldsets)
(the Ill can have many Item fieldsets)
Here is the Indexcontroller:
$ill = new Ill('ill', $this->ILLCategories, $this->campuses, $this->getFromOptions);
$ill->setName($session->name);
$ill->setEmail($session->email);
$item_array = array();
if ( isset($session->department))
{
$ill->setDepartment($session->department);
$ill->setDivision($session->division);
if ( isset($session->formData->items))
{
$item_iterator = $session->formData->items;
$i = -1;
foreach ( $item_iterator as $item)
{
$i++;
$item_fieldset = new Item('Item '.($i+1), $this->ILLCategories, $this->getFromOptions);
$item_fieldset->setILLType($item->ILLType);
$item_fieldset->setGetFrom($item->getFrom);
$item_array[] = $item_fieldset;
}
}
else
{
$item_fieldset = new Item('Item 1', $this->ILLCategories, $this->getFromOptions);
$item_array[] = $item_fieldset;
}
}
else
{
$item_fieldset = new Item('Item 1', $this->ILLCategories, $this->getFromOptions);
$item_array[] = $item_fieldset;
}
$ill->items = $item_array;
$this->ILLForm->bind( $ill );
When I view the result in the view controller no items appear. Here is an example of the data I am trying to bind to the fieldsets:
object(ILL\Entity\Ill)[452]
protected 'name' => string 'Test' (length=4)
protected 'email' => string 'yay#yay.com' (length=11)
protected 'division' => string 'Test' (length=4)
protected 'department' => string 'Test' (length=4)
protected 'contact' => null
protected 'phone' => null
protected 'idNumber' => null
protected 'campus' => null
public 'item' =>
array (size=1)
0 =>
object(ILL\Entity\Item)[453]
private 'ILLType' => null
private 'requiredBy' => null
private 'urgent' => null
private 'citation' => null
private 'copyright' => null
private 'getFrom' => null
It could be something simple that I have overlooked in the way I have structured the data but it eludes me.
OK so it seems the bind() function does not set the nested fieldset collections (but does everything else).
There is another function that does not have this issue called setData(). I basically changed this part of the code:
$this->ILLForm->bind( $ill );
to this:
// If previously submitted then run the validation to generate messages, otherwise bind autogenerated data to form (username etc...)
if ( isset($session->formData))
{
$this->ILLForm->setData($session->formData);
$this->ILLForm->isValid($ill);
}
else
{
$this->ILLForm->bind($ill);
}
As per the comment in the code, if there is a session set then the data is pushed into the fields via the setData() function (and then validated).
If its a fresh load then the model is bound to the fields via the bind() function.

Doctrine query WHERE IN - many to many

I am building out a hotel website in Symfony2. Each hotel can provide many board basis options such as Self Catering, All-Inclusive etc.
On my search form the users can filter by all of the usual fields such as location, price, star rating and board basis. Board basis is a multiple select check box.
When a user selects multiple board basis options, I am currently handling it in this way... (which is throwing errors)
$repo = $this->getDoctrine()->getRepository("AppBundle:Accommodation");
$data = $form->getData();
$qb = $repo->createQueryBuilder("a")
->innerJoin("AppBundle:BoardType", "b")
->where("a.destination = :destination")
->setParameter("destination", $data['destination'])
->andWhere("a.status = 'publish'");
if (count($data['boardBasis']) > 0) {
$ids = array_map(function($boardBasis) {
return $boardBasis->getId();
}, $data['boardBasis']->toArray());
$qb->andWhere($qb->expr()->in("a.boardBasis", ":ids"))
->setParameter("ids", $ids);
}
Here is the property declaration on a hotel entity
/**
* #ORM\ManyToMany(targetEntity="BoardType")
* #ORM\JoinTable(name="accommodation_board_type",
* joinColumns={#ORM\JoinColumn(name="accommodation_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="board_type_id", referencedColumnName="id")}
* )
*/
private $boardBasis;
The error I am currently getting is:
[Semantical Error] line 0, col 177 near 'boardBasis I': Error: Invalid PathExpression. StateFieldPathExpression or SingleValuedAssociationField expected.
On submitting the form and using var_dump on the board types I am getting:
object(Doctrine\Common\Collections\ArrayCollection)[3043]
private 'elements' =>
array (size=2)
0 =>
object(AppBundle\Entity\BoardType)[1860]
protected 'shortCode' => string 'AI' (length=2)
protected 'id' => int 1
protected 'name' => string 'All-Inclusive' (length=13)
protected 'description' => null
protected 'slug' => string 'all-inclusive' (length=13)
protected 'created' =>
object(DateTime)[1858]
...
protected 'updated' =>
object(DateTime)[1863]
...
1 =>
object(AppBundle\Entity\BoardType)[1869]
protected 'shortCode' => string 'BB' (length=2)
protected 'id' => int 2
protected 'name' => string 'Bed & Breakfast' (length=15)
protected 'description' => null
protected 'slug' => string 'bed-breakfast' (length=13)
protected 'created' =>
object(DateTime)[1867]
...
protected 'updated' =>
object(DateTime)[1868]
...
I cannot seem to find the correct syntax for this query, I have done a few times in the past (and it was a pain each time), but I just cannot remember how it is done. I have tried without mapping the ID's, passing the ArrayCollection directly in.
The only thing I can think of at the moment is to switch it to using createQuery and using DQL and see if that makes any difference.
Any help with this issue would be appreciated, Thank you
It looks to me like you're join is not fully complete. You were missing a statement describing what field to join on:
$qb = $repo->createQueryBuilder("a")
->innerJoin("AppBundle:BoardType", "b")
->where("a.boardBasis = b.id")
...
Or you could join like that:
$qb = $repo->createQueryBuilder("a")
->innerJoin("a.boardBasis", "b")
...
Then you can do add your WHERE IN statement like so:
$qb->andWhere('b.id IN (:ids)')
->setParameter('ids', $ids);

Autoload and use simple HTML dom external librarie in Codeigniter 3.0.1

I'm having problems with including / using external libraries in CodeIgniter 3.0. So this is what i did so far:
I have placed simple_html_dom.php file in my application/libraries folder
Then i'm auto loading it with this line of code in
/*
example of CI
$autoload['libraries'] = array('user_agent' => 'ua');
*/
$autoload['libraries'] = array('simple_html_dom' => 'shd');
And this is my controller
public function index()
{
$html = $this->shd->str_get_html('<html><body>Hello!</body></html>');
var_dump($html);
die();
$this->load->view('parser');
}
Which provides me an error of:
A PHP Error was encountered
Severity: Error
Message: Call to undefined method simple_html_dom::str_get_html()
Filename: controllers/Parser.php
Line Number: 8
Documentation of simple_html_dom can be found on this link
To me it looks like librarie gets loaded, but i can't use its functions.
I hope somebody can help. Thank you in advance!
I found a solution. After checking documentation of simple html dom, i found out that you can also use object oriented way. So my controller looks like this now:
$html = new simple_html_dom();
$html->load('<html><body>Hello!</body></html>');
var_dump($html);
And i have result:
object(simple_html_dom)[17]
public 'root' =>
object(simple_html_dom_node)[18]
public 'nodetype' => int 5
public 'tag' => string 'root' (length=4)
public 'attr' =>
array (size=0)
empty
public 'children' =>
array (size=1)
0 =>
object(simple_html_dom_node)[19]
...
public 'nodes' =>
array (size=1)
0 =>
object(simple_html_dom_node)[19]
...
public 'parent' => null
public '_' =>
array (size=2)
0 => int -1
1 => int 4
public 'tag_start' => int 0
private 'dom' =>
&object(simple_html_dom)[17]
public 'nodes' =>
array (size=4)
0 =>
object(simple_html_dom_node)[18]
public 'nodetype' => int 5
public 'tag' => string 'root' (length=4)
public 'attr' =>
array (size=0)
...
public 'children' =>
array (size=1)
...
public 'nodes' =>
array (size=1)
...
public 'parent' => null
public '_' =>
array (size=2)
...
public 'tag_start' => int 0
private 'dom' =>
&object(simple_html_dom)[17]
you can use
$this->load->library("simple_html_dom"); //class name should come here
And make sure simple_html_dom.php class name is simple_html_dom
Try this -
$autoload['libraries'] = array('simple_html_dom');
You can do this like that:
$this->load->library("simple_html_dom");
$this->simple_html_dom->your_method();

How to properly create different logins for different Modules in Yii framework

In my Yii 1.x application I defined new Admin module. In the init method of the admin module I defined new user component like this:
$this->setComponents(array(
'user'=>array(
'class' => 'CWebUser',
// enable cookie-based authentication
'allowAutoLogin'=>true,
'baseUrl'=>Yii::app()->createUrl("admin/user/login"),
'stateKeyPrefix' => '_admin',
),
));
Now, I expect I can do the following:
Yii::app()->getModule("admin")->user->login($this->_identity,$duration)
or
Yii::app()->getModule("admin")->user->logout();
but it is not working.
When I print my module (var_dump(Yii::app()>getModule("admin"))) I can see that user component is not defined.
object(AdminModule)[14]
public 'defaultController' => string 'default' (length=7)
public 'layout' => null
public 'controllerNamespace' => null
public 'controllerMap' =>
array (size=0)
empty
private '_controllerPath' (CWebModule) => null
private '_viewPath' (CWebModule) => null
private '_layoutPath' (CWebModule) => null
public 'preload' =>
array (size=0)
empty
public 'behaviors' =>
array (size=0)
empty
private '_id' (CModule) => string 'admin' (length=10)
private '_parentModule' (CModule) => null
private '_basePath' (CModule) => string '/srv/www/htdocs/public/project/application/protected/modules/admin' (length=71)
private '_modulePath' (CModule) => null
private '_params' (CModule) => null
private '_modules' (CModule) =>
array (size=0)
empty
private '_moduleConfig' (CModule) =>
array (size=0)
empty
private '_components' (CModule) =>
array (size=0)
empty
private '_componentConfig' (CModule) =>
array (size=1)
'user' =>
array (size=4)
'class' => string 'CWebUser' (length=8)
'allowAutoLogin' => boolean true
'baseUrl' => string '/project/application/index.php/admin/user/login' (length=52)
'stateKeyPrefix' => string '_admin' (length=11)
private '_e' (CComponent) => null
private '_m' (CComponent) => null
Please, code from working project.
Defined user component in module main class:
public function init() {
parent::init();
$this->setImport(array(
'admin.models.*',
'admin.components.*',
));
Yii::app()->setComponents(array(
'user'=>array(
'class'=>'AdminWebUser',
'allowAutoLogin'=>true,
'loginRequiredAjaxResponse'=>'Dear admin, your session expired, login and try again',
'stateKeyPrefix'=>'admin_',
'authTimeout'=>14400,
),
), false);
}
and default user component defined in main config file:
'user'=>array(
'class'=>'WebUser',
'loginRequiredAjaxResponse'=>'Your session expired, login and try again',
'autoRenewCookie'=>false,
'allowAutoLogin'=>false,
'stateKeyPrefix'=>'user_',
'loginUrl'=>'/home/login',
),
So you can "have two sessions for one user" depending on module (in module other session, in root -other). For example var_dump(Yii::app()->user->stateKeyPrefix) out of module gives user_, but in module gives admin_.
In fact yii uses the same session file, but sets and gets data depending on stateKeyPrefix (when we use setState() or getState() ).
So If you keep userName in admin module using Yii::app()->user->setState('userName', 'John'), it puts in session file 'admin_userName' => 'John', it means you can get that value out of admin module (for example in root) by using Yii::app()->session->get('admin_userName').
If you try to get it (out of module) using getState it can not return you the correct value, because it must find user_userName (because in root, for user component 'stateKeyPrefix' => 'user_') instead of admin_userName.
Thank you and sorry for long expression ))
By default method setComponents (setComponent) checks if component already defined it only merge new params with old ones.
So you must set second parameter to false for redefining new component (components):
public function init() {
parent::init();
$this->setComponents(array(
'user'=>array(
'class' => 'CWebUser',
// enable cookie-based authentication
'allowAutoLogin'=>true,
'baseUrl'=>Yii::app()->createUrl("admin/user/login"),
'stateKeyPrefix' => '_admin',
),
), false);
}
By the way, inside the module you can use component without calling
module:
Yii::app()->user->login($this->_identity,$duration);
Yii::app()->user->logout();

Categories