Zend_Cache understanding issue - zend-framework

I try to use Zend_Cache (first try) to save information about user grants. The idea and most of the source code comes from Oleg Krivtsovs tutorial.
I get an error, if I try to retrieve my cache.
Call to a member function getItem() on array
Here the implementation of FilesystemCache, in my global.php
'caches' => [
'FilesystemCache' => [
'adapter' => [
'name' => Filesystem::class,
'options' => [
// Store cached data in this directory.
'cache_dir' => './data/cache',
// Store cached data for 1 hour.
'ttl' => 60*60*1
],
],
'plugins' => [
[
'name' => 'serializer',
'options' => [
],
],
],
],
],
Here my factory class:
<?php
namespace User\Service;
use User\Controller\Plugin\AuthPlugin;
use User\Model\GrantsTable;
use User\Model\UserTable;
use Zend\Authentication\AuthenticationService;
use Zend\ServiceManager\Factory\FactoryInterface;
use Interop\Container\ContainerInterface;
class AccessControlFactory implements FactoryInterface {
public function __invoke(ContainerInterface $container, $requestedName, array $options = null) {
$config = $container->get('config');
$userTable = $container->get(UserTable::class);
$grantsTable = $container->get(GrantsTable::class);
$cache = $config['caches']['FilesystemCache'];
$userplugin = $container->get(AuthPlugin::class);
// $authentication = $container->get( \Zend\Authentication\AuthenticationService::class);
return new AccessControl($userTable, $grantsTable, $cache, $userplugin);//, $authentication
}
}
Now in the init function within my AccessControl Service, I try to retrieve from the cache:
$this->cache->getItem('rbac_container', $result);
There I get the above error.
Any help with a bit of explanation would be appreciated.

What you're injecting to the AccessControl constructor is an array, not a cache implementation, because $config['caches']['FilesystemCache'] returns an array of FilesystemCache options (adapter, plugins, etc.). What you're supposed to do is fetch the cache implementation via the ContainerInterface, like this:
$cache = $container->get('FilesystemCache');
Then the ContainerInterface will depend on StorageCacheAbstractServiceFactory to find your requested cache configs and return the class for you.

Related

Scanning translatable strings in zend 3 forms with Poedit

Zend 3 translates form labels automatically.
If forms are created using array specification, how is it possible to scan translatable form element strings with Poedit?
How to add translator->translate() functionality to forms? I tried the following in module.php onBootstrap method but this does not work:
$sm = $e->getApplication()->getServiceManager();
$vhm = $sm->get('ViewHelperManager');
$translator = $sm->get('MvcTranslator');
$vhm->get('form')->setTranslator($translator);
I want to use it like $form->translator->translate(), in such a way it would be possible to scan code with Poedit to find translatable labeles, placeholders etc.
Here's a TranslatorFactory if you need
final class TranslatorFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
// get translator files' paths from config
$paths = $container->get('config')['settings']['localization-paths'] ?? [];
$translator = new Translator();
// add zend-i18n-resources files to translator
$translator->addTranslationFilePattern(
'phpArray',
Resources::getBasePath(),
Resources::getPatternForValidator()
);
// add your translation files to translator
foreach ($paths as $path) {
$translator->addTranslationFilePattern('phpArray', $path, '%s.php');
}
// todo: replace with user's preferred language
$translator->setLocale('tr');
return $translator;
}
}
And add your factory to service manager
'service_manager' => [
'factories' => [
\Zend\I18n\Translator\TranslatorInterface::class => \MyModule\Factory\TranslatorFactory::class,
],
],
Not sure if you're still looking for a solution, so I'll add mine.
I use the TranslatorAwareTrait in my AbstractForm class.
use Zend\I18n\Translator\TranslatorAwareTrait;
abstract class AbstractForm extends \Zend\Form\Form implements
{
use TranslatorAwareTrait;
// Form stuff
}
Then, in the *FormFactory do the following:
use Zend\I18n\Translator\Translator;
use Zend\ServiceManager\Factory\FactoryInterface;
class SomeFormFactory implements FactoryInterface
{
/**
* #param ContainerInterface $container
* #param string $requestedName
* #param array|null $options
* #return mixed|object|AbstractForm
* #throws \Psr\Container\ContainerExceptionInterface
* #throws \Psr\Container\NotFoundExceptionInterface
*/
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
// Obviously you'll have more/other requirements. Fulfil them here.
$form = new SomeForm();
$form->setTranslator(
$container->get('translator')
);
return $form;
}
}
Usage example:
use Zend\I18n\Translator\TranslatorAwareTrait;
abstract class AbstractForm extends \Zend\Form\Form implements
{
use TranslatorAwareTrait;
public function init()
{
if (!$this->has('submit')) {
$this->addSubmitButton();
}
}
public function addSubmitButton($value = 'Save', array $classes = null)
{
$this->add([
'name' => 'submit',
'type' => Submit::class,
'attributes' => [
'value' =>
// Translate $value before passing to this function
$value === 'Save' ? $this->getTranslator()->translate('Save') : $value,
'class' => (!is_null($classes) ? join (' ', $classes) : 'btn btn-primary'),
],
]);
}
}
On the other hand, you could...
Translate strings before passing them if you're translating with Poedit.
If your modules contain the following config (in each module!):
'translator' => [
'translation_file_patterns' => [
[
'type' => 'gettext',
'base_dir' => __DIR__ . '/../language',
'pattern' => '%s.mo',
],
],
],
You can see here that translation is done using gettext. This is a PHP module that searches for the following code strings and translates its contents: _('translatable string').
The translation files to look for end with the .mo extension and can be found in __DIR__ . '/../language'.
Thus, if you make sure to have the PHP gettext module enabled to use this.
To use this with just normal strings, even in config for a Fieldset or a form, you could have the following:
$this->add([
'name' => 'street',
'required' => true,
'type' => Text::class,
'options' => [
'label' => _('Street'), // <<-- translated using gettext
],
]);

File Upload in Rest API yii2

my controller file inside api/v1/controller/
class ProfileController extends ActiveController
{
public $modelClass = 'app\models\Profile';
public function behaviors()
{
return [
[
'class' => 'yii\filters\ContentNegotiator',
'only' =>
['index', 'view', 'createnew','update','search'],
'formats' =>
['application/json' => Response::FORMAT_JSON,],
],
'verbs' => [
'class' => VerbFilter::className(),
'actions' => [
'index' => ['get'],
'view' => ['get'],
'createnew' => ['post'],
'update' => ['put'],
'delete' => ['delete'],
'deleteall' => ['post'],
'search' => ['get']
],
]
];
}
public function actionCreatenew() {
$model = new Profile();
$model->load(Yii::$app->request->post());
$model->asset = UploadedFile::getInstance($model, 'asset');
$name = $model->user_id;
if($model->asset) {
$model->asset->saveAs('uploads/'.$name.'.
'.$model->asset->extension);
$model->asset = $model->asset->name.'.'.
$model->asset->extension;
}
if($model->save()) {
echo json_encode(array('status'=>"Success",
'data'=>$model->attributes),JSON_PRETTY_PRINT);
} else {
echo json_encode(array('status'=>"Failure",
'error_code'=>400,
'errors'=>$model->errors),JSON_PRETTY_PRINT);
}
}
}
When I try to use access this from Postman like:
POST http://localhost/myapp/api/v1/profiles
I get Invalid Parameter – yii\base\InvalidParamException
Response content must not be an array.
What is the issue?? help would be grateful!! Thanks
You can easily receive single / multi-uploaded files using HTTP POST with form-data encoding in Yii2, directly in your Yii2 Controller / action.
Use this code:
$uploads = UploadedFile::getInstancesByName("upfile");
if (empty($uploads)){
return "Must upload at least 1 file in upfile form-data POST";
}
// $uploads now contains 1 or more UploadedFile instances
$savedfiles = [];
foreach ($uploads as $file){
$path = //Generate your save file path here;
$file->saveAs($path); //Your uploaded file is saved, you can process it further from here
}
If you use Postman API client to test how your API is working, you can configure the upload endpoint to work like this for multi-file uploads:
Note: The upfile[] square brackets are important! Postman will happily let you select multiple files for upload in one slot, but this will not actually work. Doing it the way shown in the screenshot makes an array of files available to the Yii2 action, through the UploadedFile mechanism. This is roughly equivalent to the standard PHP $_FILES superglobal variable but with easier handling.
Single files can be uploaded with or without the [] square brackets after the key name. And of course you can name upfile whatever you like, for your convention.
You should use \yii\web\UploadedFile::getInstanceByName('asset'); instead of getInstance() checkout this Link

Multiple duplicate uri parameters in GuzzleHttp

I am accessing the Echo Nest API, which requires me to repeat the same uri parameter name bucket. However I can't make this work in Guzzle 6. I read a similar issue from 2012, however the approach does not work.
I have tried adding it manually into the query string without any success.
A sample API call could be:
http://developer.echonest.com/api/v4/song/search?format=json&results=10&api_key=someKey&artist=Silbermond&title=Ja&bucket=id:spotify&bucket=tracks&bucket=audio_summary
Here's my example Client:
/**
* #param array $urlParameters
* #return Client
*/
protected function getClient()
{
return new Client([
'base_uri' => 'http://developer.echonest.com/api/v4/',
'timeout' => 5.0,
'headers' => [
'Accept' => 'application/json',
],
'query' => [
'api_key' => 'someKey',
'format' => 'json',
'results' => '10',
'bucket' => 'id:spotify' // I need multiple bucket parameter values with the 'bucket'-name
]);
}
/**
* #param $artist
* #param $title
* #return stdClass|null
*/
public function searchForArtistAndTitle($artist, $title)
{
$response = $this->getClient()->get(
'song/search?' . $this->generateBucketUriString(),
[
'query' => array_merge($client->getConfig('query'), [
'artist' => $artist,
'title' => $title
])
]
);
// ...
}
Can you help me?
In the Guzzle 6 you are not allowed to pass any aggregate function anymore. Whenever you will pass an array to the query config it will be serialized with the http_build_query function:
if (isset($options['query'])) {
$value = $options['query'];
if (is_array($value)) {
$value = http_build_query($value, null, '&', PHP_QUERY_RFC3986);
}
To avoid it you should serialize a query string by your own and pass it as string.
new Client([
'query' => $this->serializeWithDuplicates([
'bucket' => ['id:spotify', 'id:spotify2']
]) // serialize the way to get bucket=id:spotify&bucket=id:spotify2
...
$response = $this->getClient()->get(
...
'query' => $client->getConfig('query').$this->serializeWithDuplicates([
'artist' => $artist,
'title' => $title
])
...
);
Otherwise you could pass into the handler option an adjusted HandlerStack that will have in its stack your Middleware Handler. The one will read some new config param, say, query_with_duplicates, build acceptable Query String and modify Request's Uri with it accordingly.
I had the same need today, but now we are on Guzzle 7, the easiest way of getting duplicates for params (bucket=value1&bucket=value2&bucket=value3...) is to use the Query Build method. For this to work do the following:
// Import the class
use GuzzleHttp\Psr7\Query;
Example params
$params = [
'bucket' => 'value1',
'bucket' => 'value2',
'bucket' => 'value3',
];
Then when passing the params array to the query key, first pass it through the Query::build method
$response = $client->get('/api', [
'query' => Query::build($params),
]);

Overriding ZF2 global/local config: unsetting

I've run into a problem where my local config overrides global but I need local to remove not just override.
E.g.
// global.php
'mail_transport' => [
'type' => 'Zend\Mail\Transport\Smtp',
'options' => [
'host' => 'smtp.gmail.com',
'port' => 587,
'connectionClass' => 'login',
'connectionConfig' => [
// ...
],
],
], // ...
// local.php
'mail_transport' => [
'type' => 'Zend\Mail\Transport\File',
'options' => [
'path' => 'data/mail/',
]
],
// ...
So, mail_transport is being overridden, yet its options host, port, connectionClass remain and muck up the mail transport factory. Is there any way to override as I'd like? Or is the only way to edit global.php directly?
You can add a listener on the event Zend\ModuleManager\ModuleEvent::EVENT_MERGE_CONFIG to remove the required options.
Zend\ModuleManager\Listener\ConfigListener triggers a special event, Zend\ModuleManager\ModuleEvent::EVENT_MERGE_CONFIG, after merging all configuration, but prior to it being passed to the ServiceManager. By listening to this event, you can inspect the merged configuration and manipulate it.
Such a listener could look like this.
use Zend\ModuleManager\ModuleEvent;
use Zend\ModuleManager\ModuleManager;
use Zend\ModuleManager\Feature\InitProviderInterface;
class Module implements InitProviderInterface
{
public function init(ModuleManager $moduleManager)
{
$events = $moduleManager->getEventManager();
$events->attach(ModuleEvent::EVENT_MERGE_CONFIG, [$this, 'removeMailOptions']);
}
public function removeMailOptions(ModuleEvent $event)
{
$listener = $event->getConfigListener();
$config = $listener->getMergedConfig(false);
if (isset($config['mail_transport']['type'])) {
switch($config['mail_transport']['type']) {
case \Zend\Mail\Transport\File::class :
$config['mail_transport']['options'] = [
'path' => $config['mail_transport']['options']['path']
];
break;
}
}
$listener->setMergedConfig($config);
}
}

How can I perform a raw query in doctrine mongodb

Is there a way to perform a raw query (just as you can do with MySQL) in Doctrine with MongoDB?
I'm trying to do this:
db.report.aggregate([{"$group" : {_id:"$content", count:{$sum:1}}}])
It doesn't seem to be a native aggregate function in Doctrine either, is it?
Following did the trick for me
$dbName = $this->container->getParameter('mongo_db_name');
$connection = $this->container->get('doctrine_mongodb')->getConnection();
$mongo = $connection->getMongo();
$db = $mongo->selectDB($dbName);
$results = $db ->command([
'aggregate' => 'report',
'pipeline' => [
['$group' => ['_id' => '$content', 'count' => ['$sum' => 1]]]
]
]);
return $results;
Not sure about native Doctrine function, but in case of aggregations I'd prefer to have RAW JSON output, because it's mostly used to render out some charts.
I needed to use an advanced version of a $lookup stage, but sadly, didn't because its lookup() method adds just a basic version of a stage like:
public function getExpression(): array
{
return [
'$lookup' => [
'from' => $this->from,
'localField' => $this->localField,
'foreignField' => $this->foreignField,
'as' => $this->as,
],
];
}
An obvious solution would be to provide a custom version of Stage/Lookup.php class, but I didn't want to create a separate file for such a small thing, so I decided to go with inline class:
$lookupExpr = [
'$lookup' => [...],
];
$aggregationBuilder->addStage(new class($lookupExpr, $aggregationBuilder) extends Aggregation\Stage {
public function __construct(private array $lookupExpr, Builder $builder) {parent::__construct($builder);}
public function getExpression(): array
{
return $this->lookupExpr;
}
});
In Doctrine ODM 2.0, the underlying connection is handled by the mongodb/mongodb package instead of doctrine/mongodb. As such, you can get the connection though doctrine ManagerRegistry::getConnection(), then use the command function using the mongodb library:
use Doctrine\Bundle\MongoDBBundle\ManagerRegistry;
class Test {
function execute(ManagerRegistry $mr) {
$database= $mr->getConnection()->db_name;
$cursor = $database->command([
'geoNear' => 'restos',
'near' => [
'type' => 'Point',
'coordinates' => [-74.0, 40.0],
],
'spherical' => 'true',
'num' => 3,
]);
$results = $cursor->toArray()[0];
var_dump($results);
}
}
In my case i use aggregation
$db = $mongo->selectDB('ostrov_sync');
$dbTable = $mongo->selectCollection($db, 'sync_task');
$results = $dbTable->aggregate([
[
'$match' => [
'payloadHash' => [
'$eq' => '0000cfdc-c8cf-11e9-9485-000c29d1ed7a',
],
],
]
]);
dump(results);