I have entity as following:
class A
{
/**
* #JMS\Groups({"writable", "other"})
*/
private $varA;
/**
* #JMS\Groups({"writable"})
*/
private $varB;
/**
* #JMS\Groups({"other"})
*/
private $varC;
}
I want to make serializer to generate output for properties that exists in BOTH groups so in simplier words I need an intersection of grouped properties.
$context = SerializationContext::create()->setGroups(['writable' ,'other']);
$serializer->serialize(new A(), 'json', $context);
The code above should output only variable $varA because it has both groups defined.
How to achieve it? The only thing that comes to my mind is to extend GroupExclusionStategy that comes from JMSSerializer but maybe there's better way?
Perhaps a simple solution could be add another group name ("exclusive") to $varA property:
/**
* #JMS\Groups({"writable", "other", "exclusive"})
*/
private $varA;
followed by:
$context = SerializationContext::create()->setGroups('exclusive');
but probably this is just a sample use-case. In the other way, should be created a CustomGroupsExclusionStrategy().
By default GroupsExclusionStrategy() checks whether any property group is included into requested groups:
private function shouldSkipUsingGroups(PropertyMetadata $property, $groups)
{
foreach ($property->groups as $group) {
if (in_array($group, $groups)) {
return false;
}
}
return true;
}
So, to do that you need change this one to:
private function shouldSkipUsingGroups(PropertyMetadata $property, $groups)
{
foreach ($groups as $group) {
if (!in_array($group, $property->groups)) {
return true;
}
}
return false;
}
Thus all requested group must be included into the property groups.
Solution:
https://gist.github.com/yceruto/90b1ac46c8e33d51ec21079725949f77
I leave here an implementation by using a flag "strict" strategy:
$context = SerializationContext::create();
$context->addExclusionStrategy(
new CustomGroupsExclusionStrategy(['writable', 'other'], true)
);
$json = $this->get('serializer')->serialize(new A(), 'json', $context);
Output:
{"varA":"foo"}
I've dag into the code of jms and I've found that setGroups uses GroupsExclusionStrategy but there are also different strategies and ExclusionStrategyInterface. So I've implemented this interface into my own
<?php
namespace AppBundle\Jms\Serializer;
use JMS\Serializer\Context;
use JMS\Serializer\Exclusion\ExclusionStrategyInterface;
use JMS\Serializer\Metadata\ClassMetadata;
use JMS\Serializer\Metadata\PropertyMetadata;
/**
* Class IntersectGroupsExclusionStrategy
* #package AppBundle\Jms
*/
class IntersectGroupsExclusionStrategy implements ExclusionStrategyInterface
{
/**
* #var array
*/
private $groups;
/**
* IntersectGroupsExclusionStrategy constructor.
* #param array $groups
*/
public function __construct(array $groups)
{
$this->setGroups($groups);
}
/**
* {#inheritDoc}
*/
public function shouldSkipProperty(PropertyMetadata $property, Context $navigatorContext)
{
if (is_array($this->groups) && is_array($property->groups)) {
return !(!empty($this->groups) && array_intersect($this->groups, $property->groups) === $this->groups);
}
return false;
}
/**
* Whether the class should be skipped.
*
* #param ClassMetadata $metadata
*
* #return boolean
*/
public function shouldSkipClass(ClassMetadata $metadata, Context $context)
{
return false;
}
/**
* #param array $groups
* #return $this
*/
public function setGroups(array $groups)
{
$this->groups = $groups;
return $this;
}
}
When serializing instead of using setGroups I've used
$intersectExclusionStrategy = new IntersectGroupsExclusionStrategy($groups);
$serializationContext = SerializationContext::create();
$serializationContext->addExclusionStrategy($intersectExclusionStrategy);
Where $groups held values ['writable' ,'other'].
It worked pretty well.
I've also created test for it if someone needed.
<?php
use AppBundle\Jms\Serializer\IntersectGroupsExclusionStrategy;
class IntersectGroupsExclusionStrategyTest extends PHPUnit_Framework_TestCase
{
public function testShouldSkipPropertyGroups()
{
$intersectExclusionStrategy = new IntersectGroupsExclusionStrategy(['group_a', 'group_b']);
$propertyMetaData = $this->getMock('JMS\Serializer\Metadata\PropertyMetadata', [], [], '', false);
$context = $this->getMock('JMS\Serializer\Context', [], [], '', false);
$propertyMetaData->groups = ['group_a', 'group_b', 'group_c'];
$this->assertNotTrue($intersectExclusionStrategy->shouldSkipProperty($propertyMetaData, $context));
$propertyMetaData->groups = ['group_a', 'group_b'];
$this->assertNotTrue($intersectExclusionStrategy->shouldSkipProperty($propertyMetaData, $context));
}
public function testShouldNotSkipPropertyGroups()
{
$intersectExclusionStrategy = new IntersectGroupsExclusionStrategy(['group_a', 'group_b']);
$propertyMetaData = $this->getMock('JMS\Serializer\Metadata\PropertyMetadata', [], [], '', false);
$context = $this->getMock('JMS\Serializer\Context', [], [], '', false);
$propertyMetaData->groups = ['group_a', 'group_c'];
$this->assertTrue($intersectExclusionStrategy->shouldSkipProperty($propertyMetaData, $context));
$propertyMetaData->groups = ['group_d', 'group_e'];
$this->assertTrue($intersectExclusionStrategy->shouldSkipProperty($propertyMetaData, $context));
$intersectExclusionStrategy = new IntersectGroupsExclusionStrategy([]);
$this->assertTrue($intersectExclusionStrategy->shouldSkipProperty($propertyMetaData, $context));
}
public function testShouldSkipClassReturnsFalse()
{
$intersectExclusionStrategy = new IntersectGroupsExclusionStrategy(['group_a', 'group_b']);
$classMetaData = $this->getMock('JMS\Serializer\Metadata\ClassMetadata', [], [], '', false);
$context = $this->getMock('JMS\Serializer\Context', [], [], '', false);
$this->assertFalse($intersectExclusionStrategy->shouldSkipClass($classMetaData, $context));
}
}
I am using Kohana View_Core derivative for my views. I would like to display buffered views or custom view on shutdown e.g.
If there were some errors/exceptions during the runtime => display error page
If executed with no errors => display the buffered output
The View
class View
{
private $file = '';
private $data = array();
private static $global_data = array();
public static $view = '';
private function __construct($file = FALSE, array $data = array())
{
if ($file !== FALSE)
{
$this->set_filename($file);
}
if (!empty($data))
{
$this->data = $data + $this->data;
}
}
/**
* Creates new view object and returns it.
*
* #param string filename
* #param array variables
* #return object View
*/
public static function factory($file = FALSE, array $data = array())
{
return new View($file, $data);
}
/**
* Captures the output that is generated when a view is included.
* The view data will be extracted to make local variables. This method
* is static to prevent object scope resolution.
*
* #param string filename
* #param array variables
* #return string
*/
public static function capture($view_filename, array $view_data)
{
extract($view_data, EXTR_SKIP);
ob_start();
try
{
require $view_filename;
}
catch (Exception $error)
{
$ob_handlers = ob_list_handlers();
if (!empty($ob_handlers))
{
ob_end_clean();
}
throw $error;
}
return ob_get_clean();
}
/**
* Load view file
*
* #param string filename
* #return boolean
* #return object View
*/
public function set_filename($file)
{
if (strpos($file, APP_DIR) === FALSE)
{
$extension = strrpos($file, '.') === FALSE ? '.php' : '';
$path = APP_DIR.DIR_SEP.'system'.DIR_SEP.'views'.DIR_SEP.$file.$extension;
}
else
{
$path = $file;
}
if (!file_exists($path))
{
Error::throw_throwable('Unable to find file '.$path);
}
$this->file = $path;
return $this;
}
/**
* Sets a global variable, similar to the set() method.
*
* #param string variable name
* #param mixed variable value
* #return object View
*/
public static function set_global($key, $value = FALSE)
{
self::$global_data[$key] = $value;
}
/**
* Assigns a variable by name.
*
* #param string variable name or an array of variables
* #param mixed variable value
* #return object View
*/
public function set($key, $value = FALSE)
{
if (is_array($key))
{
foreach ($key as $name => $value)
{
$this->data[$name] = $value;
}
}
else
{
$this->data[$key] = $value;
}
return $this;
}
/**
* Renders the view object to a string.
*
* #throws exception
* #param string filename
* #return string
*/
public function render($file = FALSE)
{
if ($file !== FALSE)
{
$this->set_filename($file);
}
if (empty($this->file))
{
Error::throw_throwable('Unable to find file '.$this->file);
}
$data = array_merge(View::$global_data, $this->data);
return View::capture($this->file, $data);
}
public function __toString()
{
try
{
$result = $this->render();
return $result;
}
catch (Exception $error)
{
Error::throw_throwable($error);
}
}
public function __set($key, $value)
{
$this->set($key, $value);
}
public function __get($key)
{
return isset($this->data[$key]) ? $this->data[$key] : FALSE;
}
}
Usage
$content = View::factory('main/Main')
->set('order', !empty($_SESSION['order']) ? $_SESSION['order'] : FALSE)
->set('order_success', $order_success)
->set('items', $items)
->set('weights', $weights)
->set('ammounts', $ammounts)
->set('prices', $prices)
->set('total_price', $total_price)
->set('validator', FALSE)
;
$template = $this->get_template();
echo $template->set('content', $content);
What the previous lines do is echo a content view inside of template view. And this should be also the result that my shutdown handler is echoing if necessary. Is there a nice/easy way to do this?
It sounds like what you want is to show a custom error page when there is an error. What I typically do here is to extend Kohana_Kohana_Exception class (as Kohana_Exception) and override the public static function handler method.
This lets you put in some code that can check what the error is (HTTP_404_Exception or other) what the environment is (dev / production) and do whatever behavior you want.
In my case, something like this:
// If not production and not an HTTP exception
if ( ! ($e instanceof HTTP_Exception) && Kohana::$environment !== Kohana::PRODUCTION)
{
// Use built in error handler to show stace trace for developers
Kohana_Kohana_Exception::handler($e);
// ... exit(1);
}
// Otherwise
$injected_routes = array(Route::get('error'));
echo Request::factory('error-uri', NULL, FALSE, $injected_routes)
->headers(Request::$initial->headers())
->execute()
->send_headers(TRUE)
->body();
Here you need another route called error that matches error-uri and goes to another error controller/action/view that you can use to render your custom error page.
For this to work you need to have errors enabled by passing 'errors' => TRUE to your Kohana::init call in bootstrap.php.
I have an issue in accessing the array in php.
$path = "['a']['b']['c']";
$value = $array.$path;
In the above piece of code I have an multidimensional array named $array.
$path is a dynamic value which I would get from database.
Now I want to retrieve the value from $array using $path but I am not able to.
$value = $array.$path
returns me
Array['a']['b']['c']
rather than the value.
I hope I have explained my question properly.
You have two options. First (evil) if to use eval() function - i.e. interpret your string as code.
Second is to parse your path. That will be:
//$path = "['a']['b']['c']";
preg_match_all("/\['(.*?)'\]/", $path, $rgMatches);
$rgResult = $array;
foreach($rgMatches[1] as $sPath)
{
$rgResult=$rgResult[$sPath];
}
The Kohana framework "Arr" class (API) has a method (Arr::path) that does something similar to what you are requesting. It simply takes an array and a path (with a . as delimiter) and returns the value if found. You could modify this method to suit your needs.
public static function path($array, $path, $default = NULL, $delimiter = NULL)
{
if ( ! Arr::is_array($array))
{
// This is not an array!
return $default;
}
if (is_array($path))
{
// The path has already been separated into keys
$keys = $path;
}
else
{
if (array_key_exists($path, $array))
{
// No need to do extra processing
return $array[$path];
}
if ($delimiter === NULL)
{
// Use the default delimiter
$delimiter = Arr::$delimiter;
}
// Remove starting delimiters and spaces
$path = ltrim($path, "{$delimiter} ");
// Remove ending delimiters, spaces, and wildcards
$path = rtrim($path, "{$delimiter} *");
// Split the keys by delimiter
$keys = explode($delimiter, $path);
}
do
{
$key = array_shift($keys);
if (ctype_digit($key))
{
// Make the key an integer
$key = (int) $key;
}
if (isset($array[$key]))
{
if ($keys)
{
if (Arr::is_array($array[$key]))
{
// Dig down into the next part of the path
$array = $array[$key];
}
else
{
// Unable to dig deeper
break;
}
}
else
{
// Found the path requested
return $array[$key];
}
}
elseif ($key === '*')
{
// Handle wildcards
$values = array();
foreach ($array as $arr)
{
if ($value = Arr::path($arr, implode('.', $keys)))
{
$values[] = $value;
}
}
if ($values)
{
// Found the values requested
return $values;
}
else
{
// Unable to dig deeper
break;
}
}
else
{
// Unable to dig deeper
break;
}
}
while ($keys);
// Unable to find the value requested
return $default;
}
I was hoping to find an elegant solution to nested array access without throwing undefined index errors, and this post hits high on google. I'm late to the party, but I wanted to weigh in for future visitors.
A simple isset($array['a']['b']['c'] can safely check nested values, but you need to know the elements to access ahead of time. I like the dot notation for accessing multidimensional arrays, so I wrote a class of my own. It does require PHP 5.6.
This class parses a string path written in dot-notation and safely accesses the nested values of the array or array-like object (implements ArrayAccess). It will return the value or NULL if not set.
use ArrayAccess;
class SafeArrayGetter implements \JsonSerializable {
/**
* #var array
*/
private $data;
/**
* SafeArrayGetter constructor.
*
* #param array $data
*/
public function __construct( array $data )
{
$this->data = $data;
}
/**
* #param array $target
* #param array ...$indices
*
* #return array|mixed|null
*/
protected function safeGet( array $target, ...$indices )
{
$movingTarget = $target;
foreach ( $indices as $index )
{
$isArray = is_array( $movingTarget ) || $movingTarget instanceof ArrayAccess;
if ( ! $isArray || ! isset( $movingTarget[ $index ] ) ) return NULL;
$movingTarget = $movingTarget[ $index ];
}
return $movingTarget;
}
/**
* #param array ...$keys
*
* #return array|mixed|null
*/
public function getKeys( ...$keys )
{
return static::safeGet( $this->data, ...$keys );
}
/**
* <p>Access nested array index values by providing a dot notation access string.</p>
* <p>Example: $safeArrayGetter->get('customer.paymentInfo.ccToken') ==
* $array['customer']['paymentInfo']['ccToken']</p>
*
* #param $accessString
*
* #return array|mixed|null
*/
public function get( $accessString )
{
$keys = $this->parseDotNotation( $accessString );
return $this->getKeys( ...$keys );
}
/**
* #param $string
*
* #return array
*/
protected function parseDotNotation( $string )
{
return explode( '.', strval( $string ) );
}
/**
* #return array
*/
public function toArray()
{
return $this->data;
}
/**
* #param int $options
* #param int $depth
*
* #return string
*/
public function toJson( $options = 0, $depth = 512 )
{
return json_encode( $this, $options, $depth );
}
/**
* #param array $data
*
* #return static
*/
public static function newFromArray( array $data )
{
return new static( $data );
}
/**
* #param \stdClass $data
*
* #return static
*/
public static function newFromObject( \stdClass $data )
{
return new static( json_decode( json_encode( $data ), TRUE ) );
}
/**
* Specify data which should be serialized to JSON
* #link http://php.net/manual/en/jsonserializable.jsonserialize.php
* #return array data which can be serialized by <b>json_encode</b>,
* which is a value of any type other than a resource.
* #since 5.4.0
*/
function jsonSerialize()
{
return $this->toArray();
}
}
I get the following error when I try to load views from controllers that use the 'UploadPack' - (source:- https://github.com/szajbus/uploadpack):-
Strict (2048): Declaration of UploadBehavior::setup() should be compatible with ModelBehavior::setup(Model $model, $config = Array) [APP\Plugin\upload_pack\Model\Behavior\UploadBehavior.php, line 15]
My 'UploadBehavior.php' and 'ModelBehavior.php' files are below, has anyone got any ideas of how to resolve this error?
<?php
App::uses('HttpSocket', 'Network/Http');
/**
* This file is a part of UploadPack - a plugin that makes file uploads in CakePHP as easy as possible.
*
* UploadBehavior
*
* UploadBehavior does all the job of saving files to disk while saving records to database. For more info read UploadPack documentation.
*
* joe bartlett's lovingly handcrafted tweaks add several resize modes. see "more on styles" in the documentation.
*
* #author MichaĆ Szajbe (michal.szajbe#gmail.com) and joe bartlett (contact#jdbartlett.com)
* #link http://github.com/szajbus/uploadpack
*/
class UploadBehavior extends ModelBehavior {
private static $__settings = array();
private $toWrite = array();
private $toDelete = array();
private $maxWidthSize = false;
public function setup(&$model, $settings = array()) {
$defaults = array(
'path' => ':webroot/upload/:model/:id/:basename_:style.:extension',
'styles' => array(),
'resizeToMaxWidth' => false,
'quality' => 75
);
foreach ($settings as $field => $array) {
self::$__settings[$model->name][$field] = array_merge($defaults, $array);
}
}
public function beforeSave(&$model) {
$this->_reset();
foreach (self::$__settings[$model->name] as $field => $settings) {
if (!empty($model->data[$model->name][$field]) && is_array($model->data[$model->name][$field]) && file_exists($model->data[$model->name][$field]['tmp_name'])) {
if (!empty($model->id)) {
$this->_prepareToDeleteFiles($model, $field, true);
}
$this->_prepareToWriteFiles($model, $field);
unset($model->data[$model->name][$field]);
$model->data[$model->name][$field.'_file_name'] = $this->toWrite[$field]['name'];
$model->data[$model->name][$field.'_file_size'] = $this->toWrite[$field]['size'];
$model->data[$model->name][$field.'_content_type'] = $this->toWrite[$field]['type'];
} elseif (array_key_exists($field, $model->data[$model->name]) && $model->data[$model->name][$field] === null) {
if (!empty($model->id)) {
$this->_prepareToDeleteFiles($model, $field, true);
}
unset($model->data[$model->name][$field]);
$model->data[$model->name][$field.'_file_name'] = null;
$model->data[$model->name][$field.'_file_size'] = null;
$model->data[$model->name][$field.'_content_type'] = null;
}
}
return true;
}
public function afterSave(&$model, $create) {
if (!$create) {
$this->_deleteFiles($model);
}
$this->_writeFiles($model);
}
public function beforeDelete(&$model) {
$this->_reset();
$this->_prepareToDeleteFiles($model);
return true;
}
public function afterDelete(&$model) {
$this->_deleteFiles($model);
}
public function beforeValidate(&$model) {
foreach (self::$__settings[$model->name] as $field => $settings) {
if (isset($model->data[$model->name][$field])) {
$data = $model->data[$model->name][$field];
if ((empty($data) || is_array($data) && empty($data['tmp_name'])) && !empty($settings['urlField']) && !empty($model->data[$model->name][$settings['urlField']])) {
$data = $model->data[$model->name][$settings['urlField']];
}
if (!is_array($data)) {
$model->data[$model->name][$field] = $this->_fetchFromUrl($data);
}
}
}
return true;
}
private function _reset() {
$this->toWrite = null;
$this->toDelete = null;
}
private function _fetchFromUrl($url) {
$data = array('remote' => true);
$data['name'] = end(explode('/', $url));
$data['tmp_name'] = tempnam(sys_get_temp_dir(), $data['name']) . '.' . end(explode('.', $url));
$httpSocket = new HttpSocket();
$raw = $httpSocket->get($url);
$response = $httpSocket->response;
$data['size'] = strlen($raw);
$data['type'] = reset(explode(';', $response['header']['Content-Type']));
file_put_contents($data['tmp_name'], $raw);
return $data;
}
private function _prepareToWriteFiles(&$model, $field) {
$this->toWrite[$field] = $model->data[$model->name][$field];
// make filename URL friendly by using Cake's Inflector
$this->toWrite[$field]['name'] =
Inflector::slug(substr($this->toWrite[$field]['name'], 0, strrpos($this->toWrite[$field]['name'], '.'))). // filename
substr($this->toWrite[$field]['name'], strrpos($this->toWrite[$field]['name'], '.')); // extension
}
private function _writeFiles(&$model) {
if (!empty($this->toWrite)) {
foreach ($this->toWrite as $field => $toWrite) {
$settings = $this->_interpolate($model, $field, $toWrite['name'], 'original');
$destDir = dirname($settings['path']);
if (!file_exists($destDir)) {
#mkdir($destDir, 0777, true);
#chmod($destDir, 0777);
}
if (is_dir($destDir) && is_writable($destDir)) {
$move = !empty($toWrite['remote']) ? 'rename' : 'move_uploaded_file';
if (#$move($toWrite['tmp_name'], $settings['path'])) {
if($this->maxWidthSize) {
$this->_resize($settings['path'], $settings['path'], $this->maxWidthSize.'w', $settings['quality']);
}
foreach ($settings['styles'] as $style => $geometry) {
$newSettings = $this->_interpolate($model, $field, $toWrite['name'], $style);
$this->_resize($settings['path'], $newSettings['path'], $geometry, $settings['quality']);
}
}
}
}
}
}
private function _prepareToDeleteFiles(&$model, $field = null, $forceRead = false) {
$needToRead = true;
if ($field === null) {
$fields = array_keys(self::$__settings[$model->name]);
foreach ($fields as &$field) {
$field .= '_file_name';
}
} else {
$field .= '_file_name';
$fields = array($field);
}
if (!$forceRead && !empty($model->data[$model->alias])) {
$needToRead = false;
foreach ($fields as $field) {
if (!array_key_exists($field, $model->data[$model->alias])) {
$needToRead = true;
break;
}
}
}
if ($needToRead) {
$data = $model->find('first', array('conditions' => array($model->alias.'.'.$model->primaryKey => $model->id), 'fields' => $fields, 'callbacks' => false));
} else {
$data = $model->data;
}
if (is_array($this->toDelete)) {
$this->toDelete = array_merge($this->toDelete, $data[$model->alias]);
} else {
$this->toDelete = $data[$model->alias];
}
$this->toDelete['id'] = $model->id;
}
private function _deleteFiles(&$model) {
foreach (self::$__settings[$model->name] as $field => $settings) {
if (!empty($this->toDelete[$field.'_file_name'])) {
$styles = array_keys($settings['styles']);
$styles[] = 'original';
foreach ($styles as $style) {
$settings = $this->_interpolate($model, $field, $this->toDelete[$field.'_file_name'], $style);
if (file_exists($settings['path'])) {
#unlink($settings['path']);
}
}
}
}
}
private function _interpolate(&$model, $field, $filename, $style) {
return self::interpolate($model->name, $model->id, $field, $filename, $style);
}
static public function interpolate($modelName, $modelId, $field, $filename, $style = 'original', $defaults = array()) {
$pathinfo = UploadBehavior::_pathinfo($filename);
$interpolations = array_merge(array(
'app' => preg_replace('/\/$/', '', APP),
'webroot' => preg_replace('/\/$/', '', WWW_ROOT),
'model' => Inflector::tableize($modelName),
'basename' => !empty($filename) ? $pathinfo['filename'] : null,
'extension' => !empty($filename) ? $pathinfo['extension'] : null,
'id' => $modelId,
'style' => $style,
'attachment' => Inflector::pluralize($field),
'hash' => md5((!empty($filename) ? $pathinfo['filename'] : "") . Configure::read('Security.salt'))
), $defaults);
$settings = self::$__settings[$modelName][$field];
$keys = array('path', 'url', 'default_url');
foreach ($interpolations as $k => $v) {
foreach ($keys as $key) {
if (isset($settings[$key])) {
$settings[$key] = preg_replace('/\/{2,}/', '/', str_replace(":$k", $v, $settings[$key]));
}
}
}
return $settings;
}
static private function _pathinfo($filename) {
$pathinfo = pathinfo($filename);
// PHP < 5.2.0 doesn't include 'filename' key in pathinfo. Let's try to fix this.
if (empty($pathinfo['filename'])) {
$suffix = !empty($pathinfo['extension']) ? '.'.$pathinfo['extension'] : '';
$pathinfo['filename'] = basename($pathinfo['basename'], $suffix);
}
return $pathinfo;
}
private function _resize($srcFile, $destFile, $geometry, $quality = 75) {
copy($srcFile, $destFile);
#chmod($destFile, 0777);
$pathinfo = UploadBehavior::_pathinfo($srcFile);
$src = null;
$createHandler = null;
$outputHandler = null;
switch (strtolower($pathinfo['extension'])) {
case 'gif':
$createHandler = 'imagecreatefromgif';
$outputHandler = 'imagegif';
break;
case 'jpg':
case 'jpeg':
$createHandler = 'imagecreatefromjpeg';
$outputHandler = 'imagejpeg';
break;
case 'png':
$createHandler = 'imagecreatefrompng';
$outputHandler = 'imagepng';
$quality = null;
break;
default:
return false;
}
if ($src = $createHandler($destFile)) {
$srcW = imagesx($src);
$srcH = imagesy($src);
// determine destination dimensions and resize mode from provided geometry
if (preg_match('/^\\[[\\d]+x[\\d]+\\]$/', $geometry)) {
// resize with banding
list($destW, $destH) = explode('x', substr($geometry, 1, strlen($geometry)-2));
$resizeMode = 'band';
} elseif (preg_match('/^[\\d]+x[\\d]+$/', $geometry)) {
// cropped resize (best fit)
list($destW, $destH) = explode('x', $geometry);
$resizeMode = 'best';
} elseif (preg_match('/^[\\d]+w$/', $geometry)) {
// calculate heigh according to aspect ratio
$destW = (int)$geometry-1;
$resizeMode = false;
} elseif (preg_match('/^[\\d]+h$/', $geometry)) {
// calculate width according to aspect ratio
$destH = (int)$geometry-1;
$resizeMode = false;
} elseif (preg_match('/^[\\d]+l$/', $geometry)) {
// calculate shortest side according to aspect ratio
if ($srcW > $srcH) $destW = (int)$geometry-1;
else $destH = (int)$geometry-1;
$resizeMode = false;
}
if (!isset($destW)) $destW = ($destH/$srcH) * $srcW;
if (!isset($destH)) $destH = ($destW/$srcW) * $srcH;
// determine resize dimensions from appropriate resize mode and ratio
if ($resizeMode == 'best') {
// "best fit" mode
if ($srcW > $srcH) {
if ($srcH/$destH > $srcW/$destW) $ratio = $destW/$srcW;
else $ratio = $destH/$srcH;
} else {
if ($srcH/$destH < $srcW/$destW) $ratio = $destH/$srcH;
else $ratio = $destW/$srcW;
}
$resizeW = $srcW*$ratio;
$resizeH = $srcH*$ratio;
}
elseif ($resizeMode == 'band') {
// "banding" mode
if ($srcW > $srcH) $ratio = $destW/$srcW;
else $ratio = $destH/$srcH;
$resizeW = $srcW*$ratio;
$resizeH = $srcH*$ratio;
}
else {
// no resize ratio
$resizeW = $destW;
$resizeH = $destH;
}
$img = imagecreatetruecolor($destW, $destH);
imagefill($img, 0, 0, imagecolorallocate($img, 255, 255, 255));
imagecopyresampled($img, $src, ($destW-$resizeW)/2, ($destH-$resizeH)/2, 0, 0, $resizeW, $resizeH, $srcW, $srcH);
$outputHandler($img, $destFile, $quality);
return true;
}
return false;
}
public function attachmentMinSize(&$model, $value, $min) {
$value = array_shift($value);
if (!empty($value['tmp_name'])) {
return (int)$min <= (int)$value['size'];
}
return true;
}
public function attachmentMaxSize(&$model, $value, $max) {
$value = array_shift($value);
if (!empty($value['tmp_name'])) {
return (int)$value['size'] <= (int)$max;
}
return true;
}
public function attachmentContentType(&$model, $value, $contentTypes) {
$value = array_shift($value);
if (!is_array($contentTypes)) {
$contentTypes = array($contentTypes);
}
if (!empty($value['tmp_name'])) {
foreach ($contentTypes as $contentType) {
if (substr($contentType, 0, 1) == '/') {
if (preg_match($contentType, $value['type'])) {
return true;
}
} elseif ($contentType == $value['type']) {
return true;
}
}
return false;
}
return true;
}
public function attachmentPresence(&$model, $value) {
$keys = array_keys($value);
$field = $keys[0];
$value = array_shift($value);
if (!empty($value['tmp_name'])) {
return true;
}
if (!empty($model->id)) {
if (!empty($model->data[$model->alias][$field.'_file_name'])) {
return true;
} elseif (!isset($model->data[$model->alias][$field.'_file_name'])) {
$existingFile = $model->field($field.'_file_name', array($model->primaryKey => $model->id));
if (!empty($existingFile)) {
return true;
}
}
}
return false;
}
public function minWidth(&$model, $value, $minWidth) {
return $this->_validateDimension($value, 'min', 'x', $minWidth);
}
public function minHeight(&$model, $value, $minHeight) {
return $this->_validateDimension($value, 'min', 'y', $minHeight);
}
public function maxWidth(&$model, $value, $maxWidth) {
$keys = array_keys($value);
$field = $keys[0];
$settings = self::$__settings[$model->name][$field];
if($settings['resizeToMaxWidth'] && !$this->_validateDimension($value, 'max', 'x', $maxWidth)) {
$this->maxWidthSize = $maxWidth;
return true;
} else {
return $this->_validateDimension($value, 'max', 'x', $maxWidth);
}
}
public function maxHeight(&$model, $value, $maxHeight) {
return $this->_validateDimension($value, 'max', 'y', $maxHeight);
}
private function _validateDimension($upload, $mode, $axis, $value) {
$upload = array_shift($upload);
$func = 'images'.$axis;
if(!empty($upload['tmp_name'])) {
$createHandler = null;
if($upload['type'] == 'image/jpeg') {
$createHandler = 'imagecreatefromjpeg';
} else if($upload['type'] == 'image/gif') {
$createHandler = 'imagecreatefromgif';
} else if($upload['type'] == 'image/png') {
$createHandler = 'imagecreatefrompng';
} else {
return false;
}
if($img = $createHandler($upload['tmp_name'])) {
switch ($mode) {
case 'min':
return $func($img) >= $value;
break;
case 'max':
return $func($img) <= $value;
break;
}
}
}
return false;
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//My 'ModelBehavior' file:-
<?php
/**
* Model behaviors base class.
*
* Adds methods and automagic functionality to Cake Models.
*
* PHP 5
*
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* #copyright Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
* #link http://cakephp.org CakePHP(tm) Project
* #package Cake.Model
* #since CakePHP(tm) v 1.2.0.0
* #license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
/**
* Model behavior base class.
*
* Defines the Behavior interface, and contains common model interaction functionality. Behaviors
* allow you to simulate mixins, and create reusable blocks of application logic, that can be reused across
* several models. Behaviors also provide a way to hook into model callbacks and augment their behavior.
*
* ### Mixin methods
*
* Behaviors can provide mixin like features by declaring public methods. These methods should expect
* the model instance to be shifted onto the parameter list.
*
* {{{
* function doSomething(Model $model, $arg1, $arg2) {
* //do something
* }
* }}}
*
* Would be called like `$this->Model->doSomething($arg1, $arg2);`.
*
* ### Mapped methods
*
* Behaviors can also define mapped methods. Mapped methods use pattern matching for method invocation. This
* allows you to create methods similar to Model::findAllByXXX methods on your behaviors. Mapped methods need to
* be declared in your behaviors `$mapMethods` array. The method signature for a mapped method is slightly different
* than a normal behavior mixin method.
*
* {{{
* public $mapMethods = array('/do(\w+)/' => 'doSomething');
*
* function doSomething(Model $model, $method, $arg1, $arg2) {
* //do something
* }
* }}}
*
* The above will map every doXXX() method call to the behavior. As you can see, the model is
* still the first parameter, but the called method name will be the 2nd parameter. This allows
* you to munge the method name for additional information, much like Model::findAllByXX.
*
* #package Cake.Model
* #see Model::$actsAs
* #see BehaviorCollection::load()
*/
class ModelBehavior extends Object {
/**
* Contains configuration settings for use with individual model objects. This
* is used because if multiple models use this Behavior, each will use the same
* object instance. Individual model settings should be stored as an
* associative array, keyed off of the model name.
*
* #var array
* #see Model::$alias
*/
public $settings = array();
/**
* Allows the mapping of preg-compatible regular expressions to public or
* private methods in this class, where the array key is a /-delimited regular
* expression, and the value is a class method. Similar to the functionality of
* the findBy* / findAllBy* magic methods.
*
* #var array
*/
public $mapMethods = array();
/**
* Setup this behavior with the specified configuration settings.
*
* #param Model $model Model using this behavior
* #param array $config Configuration settings for $model
* #return void
*/
public function setup(Model $model, $config = array()) {
}
/**
* Clean up any initialization this behavior has done on a model. Called when a behavior is dynamically
* detached from a model using Model::detach().
*
* #param Model $model Model using this behavior
* #return void
* #see BehaviorCollection::detach()
*/
public function cleanup(Model $model) {
if (isset($this->settings[$model->alias])) {
unset($this->settings[$model->alias]);
}
}
/**
* beforeFind can be used to cancel find operations, or modify the query that will be executed.
* By returning null/false you can abort a find. By returning an array you can modify/replace the query
* that is going to be run.
*
* #param Model $model Model using this behavior
* #param array $query Data used to execute this query, i.e. conditions, order, etc.
* #return boolean|array False or null will abort the operation. You can return an array to replace the
* $query that will be eventually run.
*/
public function beforeFind(Model $model, $query) {
return true;
}
/**
* After find callback. Can be used to modify any results returned by find.
*
* #param Model $model Model using this behavior
* #param mixed $results The results of the find operation
* #param boolean $primary Whether this model is being queried directly (vs. being queried as an association)
* #return mixed An array value will replace the value of $results - any other value will be ignored.
*/
public function afterFind(Model $model, $results, $primary) {
}
/**
* beforeValidate is called before a model is validated, you can use this callback to
* add behavior validation rules into a models validate array. Returning false
* will allow you to make the validation fail.
*
* #param Model $model Model using this behavior
* #return mixed False or null will abort the operation. Any other result will continue.
*/
public function beforeValidate(Model $model) {
return true;
}
/**
* afterValidate is called just after model data was validated, you can use this callback
* to perform any data cleanup or preparation if needed
*
* #param Model $model Model using this behavior
* #return mixed False will stop this event from being passed to other behaviors
*/
public function afterValidate(Model $model) {
return true;
}
/**
* beforeSave is called before a model is saved. Returning false from a beforeSave callback
* will abort the save operation.
*
* #param Model $model Model using this behavior
* #return mixed False if the operation should abort. Any other result will continue.
*/
public function beforeSave(Model $model) {
return true;
}
/**
* afterSave is called after a model is saved.
*
* #param Model $model Model using this behavior
* #param boolean $created True if this save created a new record
* #return boolean
*/
public function afterSave(Model $model, $created) {
return true;
}
/**
* Before delete is called before any delete occurs on the attached model, but after the model's
* beforeDelete is called. Returning false from a beforeDelete will abort the delete.
*
* #param Model $model Model using this behavior
* #param boolean $cascade If true records that depend on this record will also be deleted
* #return mixed False if the operation should abort. Any other result will continue.
*/
public function beforeDelete(Model $model, $cascade = true) {
return true;
}
/**
* After delete is called after any delete occurs on the attached model.
*
* #param Model $model Model using this behavior
* #return void
*/
public function afterDelete(Model $model) {
}
/**
* DataSource error callback
*
* #param Model $model Model using this behavior
* #param string $error Error generated in DataSource
* #return void
*/
public function onError(Model $model, $error) {
}
/**
* If $model's whitelist property is non-empty, $field will be added to it.
* Note: this method should *only* be used in beforeValidate or beforeSave to ensure
* that it only modifies the whitelist for the current save operation. Also make sure
* you explicitly set the value of the field which you are allowing.
*
* #param Model $model Model using this behavior
* #param string $field Field to be added to $model's whitelist
* #return void
*/
protected function _addToWhitelist(Model $model, $field) {
if (is_array($field)) {
foreach ($field as $f) {
$this->_addToWhitelist($model, $f);
}
return;
}
if (!empty($model->whitelist) && !in_array($field, $model->whitelist)) {
$model->whitelist[] = $field;
}
}
}
The signature of the setup() method of the UploadBehavior should be the same as the signature of the setup() method in the parent class. This means you have to change: public function setup(&$model, $settings = array()) in your UploadBehavior to public function setup(Model $model, $settings = array()) and the error should go away.
I have a value that's defined in application.ini
conditions.time= 50
How can I read it in an zend action the zend way?
You can use Zend_Config_Ini
$config = new Zend_Config_Ini('my/ini/file.ini');
echo $config->conditions->time; // 50
Here is my approach, which you can use anywhere in the application:
class My_Controller_Action_Helper_Options extends Zend_Controller_Action_Helper_Abstract
{
/**
* Options separator delimiterm e.g.
* option.subkey or
* option/subkey
*/
const DELIMITER = '.';
/**
* Retrieve application options from bootstrap
*
* #return array
*/
public function getOptions()
{
$front = $this->getFrontController();
$bootstrap = $front->getParam('bootstrap');
if (null === $bootstrap) {
throw new Exception('Unable to find bootstrap');
}
return $bootstrap->getOptions();
}
/**
* Get array key if exists, otherwise returns null
*
* #param array $values
* #param string $key
* #return mixed
*/
private static function _getValue($values, $key)
{
if (is_array($values) && isset($values[$key])) {
return $values[$key];
}
return null;
}
/**
* Get application option form bootstrap
*
* #example
* $options = Zend_Controller_Action_HelperBroker::getStaticHelper('options')
* ->get('conditions.time', 'defaultvalue');
*
* #param string $section
* #param mixed $default
* #return Zend_Config
*/
public function get($section = null, $default = null)
{
$value = $this->getOptions();
if (null !== $section && is_string($section)) {
if (false === strpos($section, self::DELIMITER)) {
$value = $this->_getValue($value, $section);
} else {
$sections = explode(self::DELIMITER, $section);
foreach ($sections as $section) {
$value = $this->_getValue($value, $section);
if (null === $value) {
break;
}
}
}
}
if (null === $value) {
return $default;
}
return $value;
}
/**
* #param string $section
* #param mixed $default
* #return Zend_Config
*/
public function direct($section = null, $default = null)
{
return $this->get($section, $default);
}
}
The Application's Bootstrap.php has access to the application.ini using $this->getOptions(), you could store the value you want in your registry something like this:
public function _initConditions()
{
$config = $this->getOptions();
if (isset($config['conditions']))
{
$registry = Zend_Registry::getInstance();
$registry->conditions = $config['conditions'];
}
}
You could then access your conditions using the registry, in much the same way that you set them here.
Here is an action helper for that :
class My_Controller_Action_Helper_Config
extends Zend_Controller_Action_Helper_Abstract
{
/**
* #return array
*/
public function direct()
{
$bootstrap = $this->getActionController()->getInvokeArg('bootstrap');
$ns = strtolower(trim($bootstrap->getAppNamespace(), '_'));
return $bootstrap->getOption($ns);
}
}
You have to put your application namespace as a prefix :
; application.ini
My.conditions.time= 50
You can use it in a controller like this :
$config = $this->_helper->config();
$this->view->time = $config['conditions']['time'];
You might be able to use getenv('conditions.time')
http://www.php.net/manual/en/function.getenv.php
PHP has a parse_ini_file() function.