ZF2 Dafault Database Session Storage - database

I am busy writing a DB Session module so I can quickly install it between multiple applications. The module will be loaded from the autoloader as the first module to be started. What I'm trying to accomplish is to change the default session container / session handler to be the default session handler for all modules and it should also be database hosted sessions. I've been strugling with zf2 session handler for quite a while now and the errors in the logs make 0 sense. So here what I have so far. A basic module with Module.php containing...
namespace DBSession;
use Zend\Mvc\ModuleRouteListener;
class Module {
public function onBootstrap($e) {
$e->getApplication()->getServiceManager()->get('translator');
$eventManager = $e->getApplication()->getEventManager();
$moduleRouteListener = new ModuleRouteListener();
$moduleRouteListener->attach($eventManager);
$config = $e->getApplication()->getServiceManager()->get('Config');
$controller = $e->getTarget();
$controller->config = $config;
new \DBSession\Storage\DBStorage();
}
public function getConfig() {
return include __DIR__ . '/config/module.config.php';
}
public function getAutoloaderConfig() {
return array(
'Zend\Loader\ClassMapAutoloader' => array(
__DIR__ . '/autoload_classmap.php',
),
'Zend\Loader\StandardAutoloader' => array(
'namespaces' => array(
__NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
),
),
);
}
}
And the actual class that initiates the DB session handler.
namespace DBSession\Storage;
use Zend\Session\SaveHandler\DbTableGateway;
use Zend\Session\SaveHandler\DbTableGatewayOptions;
use Zend\Db\Adapter\Adapter;
use Zend\Session\SessionManager;
use Zend\Session\Container;
class DBStorage {
public function __construct() {
$dbAdapter = new Adapter(array(
'driver' => 'Mysqli',
'host' => 'localhost',
'dbname' => 'zf2_session',
'username' => 'zf2',
'password' => 'testme',
'options' => array(
'buffer_results' => true,
),
));
$tableGateway = new \Zend\Db\TableGateway\TableGateway('session', $dbAdapter);
$gwOpts = new DbTableGatewayOptions();
$gwOpts->setDataColumn('data');
$gwOpts->setIdColumn('id');
$gwOpts->setLifetimeColumn('lifetime');
$gwOpts->setModifiedColumn('modified');
$gwOpts->setNameColumn('name');
$saveHandler = new DbTableGateway($tableGateway, $gwOpts);
$sessionManager = new SessionManager();
$sessionManager->setSaveHandler($saveHandler);
return Container::setDefaultManager($sessionManager);
}
}
When trying to create a session I see the following in the logs which I have 0 clue how to fix. This is starting to make me hate magic...
[29-Nov-2012 20:47:28 UTC] PHP Fatal error: Uncaught exception 'Zend\Db\Sql\Exception\InvalidArgumentException' with message 'Not a valid magic property for this object' in /document_root/vendor/zendframework/zendframework/library/Zend/Db/Sql/Select.php:764
Stack trace:
#0 /document_root/vendor/zendframework/zendframework/library/Zend/Db/Sql/Select.php(163): Zend\Db\Sql\Select->__get('tableReadOnly')
#1 /document_root/vendor/zendframework/zendframework/library/Zend/Db/Sql/Select.php(146): Zend\Db\Sql\Select->from('session')
#2 /document_root/vendor/zendframework/zendframework/library/Zend/Db/Sql/Sql.php(65): Zend\Db\Sql\Select->__construct('session')
#3 /document_root/vendor/zendframework/zendframework/library/Zend/Db/TableGateway/AbstractTableGateway.php(191): Zend\Db\Sql\Sql->select()
#4 /document_root/vendor/zendframework/zendframework/library/Zend/Session/SaveHandler/DbTableGateway.php(134): Zend\Db\TableGateway\AbstractTableGateway->select(Array)
#5 [internal function]: Zend\Session\SaveHandler\DbTableGateway->write(' in /document_root/vendor/zendframework/zendframework/library/Zend/Db/Sql/Select.php on line 764

update :
zend framework >= 2.2, this issue is no more .
Old answer :
I have faced same issue on storing session into database , I have written initDbSession function inside basic module class \module\Application\Module.php
class Module
{
public function onBootstrap(MvcEvent $e)
{
$e->getApplication()->getServiceManager()->get('translator');
$eventManager = $e->getApplication()->getEventManager();
$moduleRouteListener = new ModuleRouteListener();
$moduleRouteListener->attach($eventManager);
// session start from here
$this->initDbSession( $e );
}
/**
* Store session into database
*
* #param type $e
*/
private function initDbSession( MvcEvent $e )
{
// grab the config array
$serviceManager = $e->getApplication()->getServiceManager();
$config = $serviceManager->get('config');
$dbAdapter = $serviceManager->get('Zend\Db\Adapter\Adapter');
/* some how this not works for me
$sessionOptions = new \Zend\Session\SaveHandler\DbTableGatewayOptions( null );
$sessionTableGateway = new \Zend\Db\TableGateway\TableGateway('session', $dbAdapter);
$saveHandler = new \Zend\Session\SaveHandler\DbTableGateway($sessionTableGateway, $sessionOptions);
*/
/* I written my own save handler , I am using mysql as database */
$saveHandler = new \My\Session\SaveHandler\Mysql( $config['db'] );
$sessionConfig = new \Zend\Session\Config\SessionConfig();
$sessionConfig->setOptions($config['session']);
// pass the saveHandler to the sessionManager and start the session
$sessionManager = new \Zend\Session\SessionManager( $sessionConfig , NULL, $saveHandler );
$sessionManager->start();
\Zend\Session\Container::setDefaultManager($sessionManager);
}
// other function goes here ...
Here my config file , which is located in \config\autoload\global.php
return array(
'db' => array(
'driver' => 'Pdo',
'dsn' => 'mysql:dbname=zf2;host=localhost',
'driver_options' => array(
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\'',
'buffer_results' => true
),
'username' => 'root',
'password' => '',
'host' => 'localhost',
'dbname' => 'zf2',
),
'session' => array(
'remember_me_seconds' => 2419200,
'use_cookies' => true,
'cookie_httponly' => true,
'cookie_lifetime' => 2419200,
'gc_maxlifetime' => 2419200,
),
'service_manager' => array(
'factories' => array(
'Zend\Db\Adapter\Adapter' => 'Zend\Db\Adapter\AdapterServiceFactory'
)
)
);
Mysql Table structure is
CREATE TABLE `session` (
`id` CHAR(32) NOT NULL DEFAULT '',
`name` VARCHAR(255) NOT NULL,
`modified` INT(11) NULL DEFAULT NULL,
`lifetime` INT(11) NULL DEFAULT NULL,
`data` TEXT NULL,
PRIMARY KEY (`id`)
)COLLATE='utf8_general_ci' ENGINE=InnoDB;
Custom Mysql save handler class is given below . I have written this class, because new Zend\Session\SaveHandler\DbTableGateway
not working on my server . My custom Mysql session save handler class written on \library\My\Session\SaveHandler\Mysql.php
<?php
namespace My\Session\SaveHandler;
use Zend\Session\SaveHandler\SaveHandlerInterface;
/**
* Description of Mysql
*
* #author rab
*/
class Mysql implements SaveHandlerInterface
{
/**
* Session Save Path
*
* #var string
*/
protected $sessionSavePath;
/**
* Session Name
*
* #var string
*/
protected $sessionName;
/**
* Lifetime
* #var int
*/
protected $lifetime;
/**
* Constructor
*
*/
public function __construct( $dbConfig )
{
$this->dbconn = mysql_connect(
$dbConfig['host'],
$dbConfig['username'],
$dbConfig['password']
);
if ( $this->dbconn ) {
return mysql_select_db($dbConfig['dbname'], $this->dbconn);
}
}
/**
* Open the session
*
* #return bool
*/
public function open( $savePath, $name )
{
$this->sessionSavePath = $savePath;
$this->sessionName = $name;
$this->lifetime = ini_get('session.gc_maxlifetime');
return true;
}
/**
* Close the session
*
* #return bool
*/
public function close()
{
return mysql_close($this->dbconn);
}
/**
* Read the session
*
* #param int session id
* #return string string of the sessoin
*/
public function read($id)
{
$id = mysql_real_escape_string($id);
$sql = "SELECT `data` FROM `session` " .
"WHERE id = '$id'";
if ( $result = mysql_query($sql, $this->dbconn)) {
if ( mysql_num_rows($result) ) {
$record = mysql_fetch_assoc($result);
return $record['data'];
}
}
return '';
}
/**
* Write the session
*
* #param int session id
* #param string data of the session
*/
public function write($id, $data )
{
$data = (string) $data ;
$dbdata = array(
'modified' => time(),
'data' => mysql_real_escape_string( $data ) ,
);
$selectSql = "SELECT * FROM session
WHERE id = '$id' AND name = '{$this->sessionName}' ";
$rs = mysql_query( $selectSql, $this->dbconn );
if ( $rs = mysql_query( $selectSql , $this->dbconn)) {
if ( mysql_num_rows($rs) ) {
$updateSql = "UPDATE `session` SET
`modified`= '".$dbdata['modified'] . "' ,
`data`= '".$dbdata['data']. "'
WHERE id= '$id' AND name = '{$this->sessionName}' ";
mysql_query( $updateSql , $this->dbconn );
return true;
}
}
$dbdata['lifetime'] = $this->lifetime;
$dbdata['id'] = $id;
$dbdata['name'] = $this->sessionName;
$insertSql = "INSERT INTO session (". implode(',' , array_keys($dbdata)) .")"
."VALUES ('" . implode("','" , array_values( $dbdata )). "')";
return mysql_query( $insertSql, $this->dbconn);
}
/**
* Destoroy the session
*
* #param int session id
* #return bool
*/
public function destroy($id)
{
$sql = sprintf("DELETE FROM `session` WHERE `id` = '%s'", $id);
return mysql_query($sql, $this->dbconn);
}
/**
* Garbage Collector
*
* #param int life time (sec.)
* #return bool
*/
public function gc( $maxlifetime )
{
$sql = sprintf("DELETE FROM `session` WHERE `modified` < '%s'",
mysql_real_escape_string(time() - $maxlifetime)
);
return mysql_query($sql, $this->dbconn);
}
}
Which saves my session values into db table . For more information you can check http://php.net/manual/en/function.session-set-save-handler.php

After all of the fixes that have recently gone into ZF2 my original solution is now working without modification however, I have added a new composer module for anyone that wants to use it, it reduces the need to do it all manually.
Installation details are in the readme file on: https://github.com/Nitecon/DBSessionStorage
Enjoy and let me know if you have any issues.

Here's another similar solution:
How to use cookie in Zend Framework 2?
make sure you change this line:
Session::setDefaultManager($sessionManager);
With this one:
Container::setDefaultManager($sessionManager);
Using:
use Zend\Session\Container;

Related

DebugKit Error: CakePHP get() mystery empty elements in associations

EDIT: This is after upgrading from CakePHP 4.0 to 4.3
With DebugKit turned on, CakePHP is throwing the following error message:
Argument 1 passed to Cake\ORM\Entity::get() must be of the type string, bool given, called in /Users/thomasbelknap/one-vision/vendor/cakephp/cakephp/src/Datasource/EntityTrait.php on line 607
This appears to be as a result of an empty, numerically-indexed element in the return array of a database query. Let me explain:
In my system, a User has many Estimates (and also Carts and Orders, but more on that). These two clauses in the User and Estimate table files bare this out:
$this->belongsTo('Users', [
'foreignKey' => 'user_id',
]);
::snip::
$this->hasMany('Estimates', [
'foreignKey' => 'user_id',
'dependent' => true,
]);
I am querying this information on a "Dashboard" page using the following query structure:
$user = $this->Users->get($this->getRequest()->getSession()->read('Auth.id'), [
'contain' => [
'UserGroups',
'Carts' => function ($q) {
return $q->limit(5);
},
'Orders' => function ($q) {
return $q->limit(5);
},
'Orders.OrderStatuses',
'Estimates' => function ($q) {
return $q->limit(5);
},
'Estimates.EstimateStatuses',
],
]);
I am not doing any further transformation of the data. There are no Collection functions being employed here. Nevertheless, in the estimates returned, there is always a mystery, numerically-indexed and empty array element:
[1] => Visualize\Model\Entity\Estimate Object
(
[id] => 30
[user_id] => 1
[estimate_status_id] => 5
[estimate_po] => lksjdf
[estimate_total] =>
[name] => Full throttle
[instore_date] => Cake\I18n\FrozenDate Object
(
[date] => 2022-12-31 00:00:00.000000
[timezone_type] => 3
[timezone] => EST
)
[notes] => Let's do this.
[created] => Cake\I18n\FrozenTime Object
(
[date] => 2022-04-19 13:20:39.000000
[timezone_type] => 3
[timezone] => EST
)
[modified] => Cake\I18n\FrozenTime Object
(
[date] => 2022-04-19 13:21:26.000000
[timezone_type] => 3
[timezone] => EST
)
[1] =>
[location_info] => Array
(
)
[[new]] =>
[[accessible]] => Array
(
[user_id] => 1
[estimate_status_id] => 1
[estimate_po] => 1
[estimate_total] => 1
[name] => 1
[instore_date] => 1
[notes] => 1
[created] => 1
[modified] => 1
[user] => 1
[line_items] => 1
[orders] => 1
[estimate_options] => 1
)
[[dirty]] => Array
(
)
[[original]] => Array
(
)
[[virtual]] => Array
(
[item_count] => 1
[box_count] => 1
[deliverable_count] => 1
[0] => location_info
[estimate_total] => 1
)
[[hasErrors]] =>
[[errors]] => Array
(
)
[[invalid]] => Array
(
)
[[repository]] => Estimates
)
This only affects the Estimates part of the query! The other two elements do not have this mystery index. Here's what CakePHP ultimately uses as it's query, which again, seems fine:
SELECT
Estimates.id AS Estimates__id,
Estimates.user_id AS Estimates__user_id,
Estimates.estimate_status_id AS Estimates__estimate_status_id,
Estimates.estimate_po AS Estimates__estimate_po,
Estimates.estimate_total AS Estimates__estimate_total,
Estimates.name AS Estimates__name,
Estimates.instore_date AS Estimates__instore_date,
Estimates.notes AS Estimates__notes,
Estimates.created AS Estimates__created,
Estimates.modified AS Estimates__modified,
EstimateStatuses.id AS EstimateStatuses__id,
EstimateStatuses.name AS EstimateStatuses__name,
EstimateStatuses.description AS EstimateStatuses__description,
EstimateStatuses.created AS EstimateStatuses__created,
EstimateStatuses.modified AS EstimateStatuses__modified
FROM
estimates Estimates
LEFT JOIN estimate_statuses EstimateStatuses ON EstimateStatuses.id = Estimates.estimate_status_id
WHERE
Estimates.user_id in (1)
ORDER BY
Estimates.modified DESC
LIMIT
5
For obvious reasons, I really need the DebugKit to work, but I have no idea where this mystery element is coming from. Everything seems right, to me?
Edit: Adding the definition of my estimate entity:
<?php
declare(strict_types=1);
namespace Visualize\Model\Entity;
use Cake\ORM\Entity;
/**
* Estimate Entity
*
* #property int $id
* #property int $user_id
* #property int|null $estimate_status_id
* #property string|null $estimate_po
* #property float|null $estimate_total
* #property string $name
* #property \Cake\I18n\FrozenDate $instore_date
* #property string $notes
* #property \Cake\I18n\FrozenTime $created
* #property \Cake\I18n\FrozenTime $modified
*
* #property \Visualize\Model\Entity\User $user
* #property \Visualize\Model\Entity\LineItem[] $line_items
* #property \Visualize\Model\Entity\Order[] $orders
* #property \Visualize\Model\Entity\EstimateOption[] $estimate_options
* #property float $item_count
* #property int $box_count
* #property array|\Cake\Collection\CollectionInterface|null $by_location
* #property int $deliverable_count
* #property array $location_info
* #property \Visualize\Model\Entity\EstimateStatus|null $estimate_status
* #property array|\Cake\Collection\CollectionInterface|null $by_deliverable
*/
class Estimate extends Entity
{
/**
* Fields that can be mass assigned using newEntity() or patchEntity().
*
* Note that when '*' is set to true, this allows all unspecified fields to
* be mass assigned. For security purposes, it is advised to set '*' to false
* (or remove it), and explicitly make individual fields accessible as needed.
*
* #var array
*/
protected $_accessible = [
'user_id' => true,
'estimate_status_id' => true,
'estimate_po' => true,
'estimate_total' => true,
'name' => true,
'instore_date' => true,
'notes' => true,
'created' => true,
'modified' => true,
'user' => true,
'line_items' => true,
'orders' => true,
'estimate_options' => true,
];
/**
* #var array
*/
protected $_virtual = [
'item_count' => true,
'box_count' => true,
'deliverable_count' => true,
'location_info' => true,
'estimate_total' => true,
];
/**
* Returns a zero-filled version of the cannonical ID
*
* #param int $id The autoincremental ID
* #return string
*/
protected function _getId($id): string
{
return sprintf('%05d', $id);
}
/**
* Returns the sum of quantities of all line items.
*
* #return float
*/
protected function _getItemCount(): float
{
$count = 0;
if (!empty($this->line_items)) {
$collection = new \Cake\Collection\Collection($this->line_items);
$count = $collection->sumOf('quantity');
}
return (float)$count;
}
/**
* Returns the count of locations in the estimate
*
* #return int
*/
protected function _getBoxCount(): int
{
$count = 0;
if (!empty($this->line_items)) {
$collection = new \Cake\Collection\Collection($this->line_items);
$collection = $collection->combine('location_id', 'id');
$count = $collection->count();
}
return $count;
}
/**
* Returns the total number of deliverable types in the order.
*
* #return int
*/
protected function _getDeliverableCount(): int
{
$count = 0;
if (!empty($this->line_items)) {
$collection = new \Cake\Collection\Collection($this->line_items);
$collection = $collection->combine('deliverable_id', 'id');
$count = $collection->count();
}
return $count;
}
/**
* Returns the list of locations for this estimate
*
* #return array
*/
protected function _getLocationInfo(): array
{
$info = [];
if (!empty($this->line_items)) {
$collection = new \Cake\Collection\Collection($this->line_items);
$info = $collection->combine(
'location_id',
function ($entity) {
return $entity->location;
}
);
return $info->toArray();
}
return $info;
}
/**
* Returns either the name or the id of the given estimate
*
* #return string
*/
protected function _getName(): string
{
return $this->name ?? (string)$this->id;
}
public const FIELD_ID = 'id';
public const FIELD_USER_ID = 'user_id';
public const FIELD_ESTIMATE_STATUS_ID = 'estimate_status_id';
public const FIELD_ESTIMATE_PO = 'estimate_po';
public const FIELD_ESTIMATE_TOTAL = 'estimate_total';
public const FIELD_NAME = 'name';
public const FIELD_INSTORE_DATE = 'instore_date';
public const FIELD_NOTES = 'notes';
public const FIELD_CREATED = 'created';
public const FIELD_MODIFIED = 'modified';
public const FIELD_USER = 'user';
public const FIELD_LINE_ITEMS = 'line_items';
public const FIELD_ORDERS = 'orders';
public const FIELD_ESTIMATE_OPTIONS = 'estimate_options';
public const FIELD_ITEM_COUNT = 'item_count';
public const FIELD_BOX_COUNT = 'box_count';
public const FIELD_BY_LOCATION = 'by_location';
public const FIELD_DELIVERABLE_COUNT = 'deliverable_count';
public const FIELD_LOCATION_INFO = 'location_info';
public const FIELD_ESTIMATE_STATUS = 'estimate_status';
public const FIELD_BY_DELIVERABLE = 'by_deliverable';
}
Take a close look at the dump of the entity, that field is apparently configured as an exposed virtual field, and looking at the other exposed fields, they seem to be configured incorrectly, as that property should hold a flat array of strings, not string indexed entries like 'item_count' => 1, as the value declares the field name, hence why you see a field named 1.
You are also misinterpreting the output, location_info does not belong to the 1 field, that's actually two different fields, 1 which holds something empty-ish that PHP doesn't print, like null or false, and location_info which holds an empty array.
Use debug() instead of pr()/print_r() to get a better structured dump which also properly shows empty data. And then inspect your Estimate entity class (or wherever things may be configured on the fly) and fix up the virtual fields.

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
));

Magento 2 adminhtml multiselect and showing selected options after save

How do I get the Magento2 Adminhtml Form with a Multiselect to select the saved options?
Here is my Adminhtml Form Class for reference.
namespace RussellAlbin\Blog\Block\Adminhtml\Post\Edit;
/**
* Adminhtml blog post edit form
*/
class Form extends \Magento\Backend\Block\Widget\Form\Generic
{
/**
* #var \RussellAlbin\Blog\Model\Category\Source\ListCategories
*/
protected $_categories;
/**
* #var \RussellAlbin\Blog\Model\Postcategory
*/
protected $_postcategory;
/**
* #var \Magento\Store\Model\System\Store
*/
protected $_systemStore;
/**
* #param \Magento\Backend\Block\Template\Context $context
* #param \Magento\Framework\Registry $registry
* #param \Magento\Framework\Data\FormFactory $formFactory
* #param \Magento\Store\Model\System\Store $systemStore
* #param \RussellAlbin\Blog\Model\Category\Source\ListCategories $categories
* #param \RussellAlbin\Blog\Model\Postcategory $postcategory
* #param array $data
*/
public function __construct(
\Magento\Backend\Block\Template\Context $context,
\Magento\Framework\Registry $registry,
\Magento\Framework\Data\FormFactory $formFactory,
\Magento\Store\Model\System\Store $systemStore,
\RussellAlbin\Blog\Model\Category\Source\ListCategories $categories,
\RussellAlbin\Blog\Model\Postcategory $postcategory,
array $data = []
) {
$this->_categories = $categories;
$this->_systemStore = $systemStore;
$this->_postcategory = $postcategory;
parent::__construct($context, $registry, $formFactory, $data);
}
/**
* Init form
*
* #return void
*/
protected function _construct()
{
parent::_construct();
$this->setId('blog_post_form');
$this->setTitle(__('Blog Post Information'));
}
/**
* Prepare form
*
* #return $this
*/
protected function _prepareForm()
{
/** #var \RussellAlbin\Blog\Model\Post $model */
$model = $this->_coreRegistry->registry('blog_post');
/** #var \Magento\Framework\Data\Form $form */
$form = $this->_formFactory->create(
['data' => ['id' => 'edit_form', 'action' => $this->getData('action'), 'method' => 'post']]
);
$form->setHtmlIdPrefix('post_');
$fieldset = $form->addFieldset(
'base_fieldset',
['legend' => __('General Information'), 'class' => 'fieldset-wide']
);
if ($model->getPostId()) {
$fieldset->addField('post_id', 'hidden', ['name' => 'post_id']);
}
// Gather our existing categories
$currentCategories = $this->_getExistingCategories( $model );
// Get all the categories that in the database
$allCategories = $this->_categories->toOptionArray();
$field = $fieldset->addField(
'blog_categories',
'multiselect',
[
'label' => __('Categories'),
'required' => true,
'name' => 'blog_categories',
'values' => $allCategories,
'value' => $currentCategories
]
);
$form->setValues($model->getData());
$form->setUseContainer(true);
$this->setForm($form);
return parent::_prepareForm();
}
/**
* #param $model
* #return array
*/
private function _getExistingCategories( $model )
{
// Get our collection
$existingCategories = $this->_postcategory->getCollection()
->addFieldToSelect('category_id')
->addFieldToFilter('post_id', $model->getId());
// Setup our placeholder for the array of categories needed to set back on the value of the multiselect
$itemList = array();
foreach($existingCategories as $_item)
{
$itemList[] = $_item['category_id'];
}
return $itemList;
}
}
I looked back at some work I have done in Magento 1.x and I the only way I got this to work before was using javascript and the setAfterElementHtml to set the options that matched. I remember hating this method of getting it accomplished because it seemed like a work around.
I found the issue.
I did not have the values saved on the model.
// This is what shows it as selected on reload
$model->setData('blog_categories', $categories);
$fieldset->addField(
'blog_categories',
'multiselect',
[
'name' => 'blog_categories[]',
'label' => __('Categories'),
'title' => __('Categories'),
'required' => true,
'values' => $optionArray,
'disabled' => false
]
);

Laravel inserting data

I have FotoController.php
and this is part of function:
$img = array(
'name' => $filename,
'gid' => $id,
);
$s = new Img($img);
$create = $s->save();
My img model:
class Img extends Eloquent {
public $timestamps = false;
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'images';
/**
* The attributes excluded from the model's JSON form.
*
* #var array
*/
//protected $hidden = array('remember_token');
protected $fillable = array('name', 'gid', 'views', 'likes');
}
But it doesn't add new record into the database.
You can't create a new object and fill it at the same time this way, you need to use the fill method:
$img = array(
'name' => $filename,
'gid' => $id,
);
$s = new Img();
$s->fill($img);
$s->save();
Or you can use the static create method on your model:
$img = array(
'name' => $filename,
'gid' => $id,
);
$s = Img::create($img);

How to generate an image that is a link?

Right now my image links look like this:
I need them to look like this:
My Images are stored in APP/uploads/userid/images/filename.jpg
This is my view at the moment:
<?php foreach($file as $files){?>
<?php echo $this->Html->link($files['Image']['filename'], array('controller' => 'images', 'action' => 'downloadImages', $files['Image']['filename']), array('class' => 'frame'));}?>
It works and clicking the link shows the relevant image correctly.
Controller snippet for reference:
public function downloadImages($filename) {
$download = !empty($_GET['download']);
$idUser = $this->Auth->user('idUser');
$folder_url = APP.'uploads/'.$idUser.'/'.'images'.'/'.$filename;
$this->response->file($folder_url, array('download' => $download, 'name' => $filename));
return $this->response;
}
What do I need to do to make images display as links instead of the name of the file?
How to generate an image link
In the question there is this line (paraphrased for clarity):
$downloadUrl = array('controller' => 'images', 'action' => 'downloadImages', $files['Image']['filename'], '?' => array('download' => true));
$imageUrl = array('controller' => 'images', 'action' => 'downloadImages', $files['Image']['filename']);
echo $this->Html->link(
$files['Image']['filename'],
$downloadUrl,
array('class' => 'frame')
);
Instead of linking to the filename - link to the image:
echo $this->Html->link(
$this->Html->image($imageUrl),
$downloadUrl,
array('class' => 'frame', 'escape' => false)
);
OR use the image function directly, since the image function supports that:
echo $this->Html->image(
$imageUrl,
array(
'url' => $downloadUrl
)
);
Here is a copy of y GemsImageComponent that I use to push images to the browser from disk. It handles caching, and uses the file timestamp to see if the image should sent again, or if the browser's current cached version is up to date.
Please up-vote if you find this useful.
<?php
/**
* $settings['cache'] string (optional) How long the browser should cache
* images.
* $settings['expires'] string (optional) If all images should expire after a
* given period from the browsers cache.
*/
class GemsImageComponent extends Component
{
/**
*
* #var array The default settings for the component.
*/
protected $defaults = array(
'cache' => '+7 days',
'expires' => false
);
/**
*
* #var Controller The controller using this component.
*/
protected $controller;
/**
*
* #see Component::__construct()
*/
public function __construct(ComponentCollection $collection,
$settings = array())
{
parent::__construct($collection,
Hash::merge($this->defaults, $settings));
}
/**
*
* #see Component::startup()
*/
public function startup(Controller $controller)
{
$this->controller = $controller;
}
/**
* Sends an image from disk to the browser.
*
* #param string $file The full path to the image file.
* #return mixed The value that the View should return.
*/
public function send($file)
{
$response = $this->controller->response;
$settings = $this->settings;
// the file has to exist
if(! file_exists($file))
{
throw new NotFoundException();
}
// set browser cache options
if($settings['cache'] !== false)
{
$response->cache('-1 minute', $settings['cache']);
}
if($settings['expires'] !== false)
{
$response->expires($settings['expires']);
}
// mark this image with a timestamp of when it last changed.
$timestamp = filemtime($file);
$response->modified($timestamp);
// generate a unique ID that browser cache engines can use to track this
// resource.
// TODO: Add GEMS version number to the hash tag.
$unique = sha1(sprintf('%s-%d', $file, $timestamp));
$response->etag($unique);
// check if we can abort sending this resource
if(! $response->checkNotModified($this->controller->request))
{
$response->file($file, array(
'download' => false
));
}
return $response;
}
}
?>

Resources