ZF3 Shared Eventmanager ignores priority - zend-framework2

I have the following classes and configuration to listen for the creation of a profile. Everything works fine, but now I need to listen a second time for it and this should be executed after the first time. I thought I could use the priority here, but it seems that this priority has no effect.
Any ideas?
Config
return [
'listeners' => [
CreateListener::class
]
]
ProfileService
class ProfileService implements EventManagerAwareInterface {
use EventManagerAwareTrait;
public function createProfile(Profile $profile) {
$this->getEventManager()->trigger(__FUNCTION__, $this, ['profile' => $profile]);
$this->profileRepository->save($profile);
$this->getEventManager()->trigger(__FUNCTION__ . '.post', $this, ['profile' => $profile]);
}
}
CreateListener
class CreateListener extends AbstractListenerAggregate {
public function attach(EventManagerInterface $eventManager, $priority = 100) {
// Priority of 100 seems to be ignored...
$this->listeners[] = $eventManager->getSharedManager()->attach(
ProfileService::class,
'createProfile.post',
[$this, 'onPostCreateProfile'],
$priority
);
}
public function onPostCreateProfile(EventInterface $event) {
// Do something
}
}

Based upon the answer I linked in the comments, could you try the following?
(Replace naming with your own and such)
namespace Demo\Listener;
use Demo\Event\DemoEvent;
use Zend\EventManager\Event;
use Zend\EventManager\EventManagerInterface;
use Zend\EventManager\ListenerAggregateInterface;
class DemoListener implements ListenerAggregateInterface
{
use EventManagerAwareTrait;
// You had "$priority = 100", this will cause an error in PHP >7.1 as it does not match interface
public function attach(EventManagerInterface $events, $priority = 1)
{
$sharedManager = $events->getSharedManager();
$sharedManager->attach(
ProfileService::class,
'createProfile', // <<-- first function to execute
[$this, 'createProfile'],
100
);
$sharedManager->attach(
ProfileService::class,
'createProfilePost', // <<-- second function to execute via daisy-chain
[$this, 'onPostCreateProfile'],
100
);
}
public function createProfile(Event $event)
{
// Gets passed along parameters from the ->trigger() function elsewhere
$params = $event->getParams();
$specificClass = $params[SpecificClass::class]; // get your event managed object
//
// Do your magic
//
// Don't forget to detach this function (Handler) so it doesn't accidentally run again
$event->getEventManager()->getSharedManager()->clearListeners(get_class($specificClass), $event->getName());
// Trigger events specific for the Entity/class (this "daisy-chains" events, allowing for follow-up functionality)
$specificClass->getEventManager()->trigger(
'createProfile.post',
$specificClass ,
[get_class($specificClass) => $specificClass ] // Params getting passed along
);
}
public function createProfilePost(Event $event)
{
// This function is daisy-chained by the above. Copy contents, do magic
}
}

Related

Fatfree php (v3.6.5) dependency injection CONTAINER

Does anybody have a simple example of usage?
https://fatfreeframework.com/3.6/quick-reference#CONTAINER page
seem to me not really explanatory, but generally i need to figure a nice way to auto-inject $db_connection object just when/where needed, e.g.
In class's beforeRoute() method for smooth route resolving
When porting selfoss to use DI, I chose Dice as suggested in the docs you linked. I set up the container to use only a single shared instance of the DB class and pass it the connection string:
$f3 = Base::instance();
$dice = new Dice\Dice;
$host = $f3->get('db_host');
$database = $f3->get('db_database');
$dsn = "pgsql:host=$host; dbname=$database";
$dbParams = [
$dsn,
$f3->get('db_username'),
$f3->get('db_password')
];
$dice->addRule(DB\SQL::class, [
'constructParams' => $dbParams,
'shared' => true,
]);
$f3->set('CONTAINER', function($class) use ($dice) {
return $dice->create($class);
});
Then F3 will use the Dice container to create classes so controllers and any of their dependencies will be passed the instantiated dependencies in the constructor:
namespace daos;
class Items {
private DB\SQL $db;
public function __construct(DB\SQL $db) {
$this->db = $db;
}
public function fetchAll() {
$entries = $this->db->exec(…);
…
}
}
See the selfoss source code for a full example of how to configure the dependency container.
So, i've opted for singleton:
// Database class
class DB
{
private static $_instance = null;
private $dbconn = null;
// config files should better be located in more secure dir
const DB_HOST = '127.0.0.1';
const DB_NAME = 'my_db_name';
const DB_USER = 'my_db__user';
const DB_PASS = 'my_db_passw';
const CHARSET = 'utf8';
const DB_PREFIX = '';
///////////////////////////////////
private function __construct () {
$this->dbconn=new DB\SQL(
'mysql:host='.self::DB_HOST.';port='.self::DB_PORT.';dbname='.self::DB_NAME,
self::DB_USER,
self::DB_PASSW,
$options = array(
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
\PDO::ATTR_PERSISTENT => FALSE)
);
}
////////////////////////////////////
public static function getInstance() {
if (!self::$_instance) {
self::$_instance = new DB();
}
return self::$_instance;
}
//////////////////////////////////
public function connect() {
if ($this->dbconn) {
echo 'Hooray - 1st stage connected!';
return $this->dbconn;
}
else echo '<br>Sad enough, no connection :(((';
}
///////////// ///////////////// ////////////////////
private function __clone() { }
private function __wakeup() { }
}
Sorry for dummyness, i've just discovered for myself built in \Prefab class (to be extended for singletons), so the above DB connection i'd rather do LIKE%:
class DB extends \Prefab {
private $dbconn;
// ..then just do connection thing
public function connect() {
//////
}
}

How to integrate dependency injection with custom decorators

I'm trying to create a decorator that requires dependency injection.
For example:
#Injectable()
class UserService{
#TimeoutAndCache(1000)
async getUser(id:string):Promise<User>{
// Make a call to db to get all Users
}
}
The #TimeoutAndCache returns a new promise which does the following:
if call takes longer than 1000ms, returns a rejection and when the call completes, it stores to redis (so that it can be fetched next time).
If call takes less than 1000ms, simply returns the result
export const TimeoutAndCache = function timeoutCache(ts: number, namespace) {
return function log(
target: object,
propertyKey: string,
descriptor: TypedPropertyDescriptor<any>,
) {
const originalMethod = descriptor.value; // save a reference to the original method
descriptor.value = function(...args: any[]) {
// pre
let timedOut = false;
// run and store result
const result: Promise<object> = originalMethod.apply(this, args);
const task = new Promise((resolve, reject) => {
const timer = setTimeout(() => {
if (!timedOut) {
timedOut = true;
console.log('timed out before finishing');
reject('timedout');
}
}, ts);
result.then(res => {
if (timedOut) {
// store in cache
console.log('store in cache');
} else {
clearTimeout(timer);
// return the result
resolve(res);
}
});
});
return task;
};
return descriptor;
};
};
I need to inject a RedisService to save the evaluated result.
One way I could inject Redis Service in to the UserService, but seems kind ugly.
You should consider using an Interceptor instead of a custom decorator as they run earlier in the Nest pipeline and support dependency injection by default.
However, because you want to both pass values (for cache timeout) as well as resolve dependencies you'll have to use the mixin pattern.
import {
ExecutionContext,
Injectable,
mixin,
NestInterceptor,
} from '#nestjs/common';
import { Observable } from 'rxjs';
import { TestService } from './test/test.service';
#Injectable()
export abstract class CacheInterceptor implements NestInterceptor {
protected abstract readonly cacheDuration: number;
constructor(private readonly testService: TestService) {}
intercept(
context: ExecutionContext,
call$: Observable<any>,
): Observable<any> {
// Whatever your logic needs to be
return call$;
}
}
export const makeCacheInterceptor = (cacheDuration: number) =>
mixin(
// tslint:disable-next-line:max-classes-per-file
class extends CacheInterceptor {
protected readonly cacheDuration = cacheDuration;
},
);
You would then be able to apply the Interceptor to your handler in a similar fashion:
#Injectable()
class UserService{
#UseInterceptors(makeCacheInterceptor(1000))
async getUser(id:string):Promise<User>{
// Make a call to db to get all Users
}
}

Groovy/Grails promises/futures. There is no .resolve(1,2,3) method. Strange?

I am developing in a Grails application. What I want to do is to lock the request/response, create a promise, and let someone else resolve it, that is somewhere else in the code, and then flush the response.
What I find really strange is that the Promise promise = task {} interface has no method that resembles resolve or similar.
I need to lock the response until someone resolves the promise, which is a global/static property set in development mode.
Promise interface:
http://grails.org/doc/latest/api/grails/async/Promise.html
I have looked at the GPars doc and can't find anything there that resembles a resolve method.
How can I create a promise, that locks the response or request, and then flushes the response when someone resolves it?
You can call get() on the promise which will block until whatever the task is doing completes, but I imagine what that is not what you want. What you want seems to be equivalent to a GPars DataflowVariable:
http://gpars.org/1.0.0/javadoc/groovyx/gpars/dataflow/DataflowVariable.html
Which allows using the left shift operator to resolve the value from another thread. Currently there is no way to use the left shift operator via Grails directly, but since Grails' promise API is just a layer over GPars this can probably be accomplished by using the GPars API directly with something like:
import org.grails.async.factory.gpars.*
import groovyx.gpars.dataflow.*
import static grails.async.Promise.*
def myAction() {
def dataflowVar = new DataflowVariable()
task {
// do some calculation and resolve data flow variable
def expensiveData = ...
dataflowVar << expensiveData
}
return new GParsPromise(dataflowVar)
}
It took me quite some time to get around this and have a working answer.
I must say that it appears as if Grails is quite a long way of making this work properly.
task { }
will always execute immediatly, so the call is not put on hold until dispatch() or whatever is invoked which is a problem.
Try this to see:
public def test() {
def dataflowVar = new groovyx.gpars.dataflow.DataflowVariable()
task {
// do some calculation and resolve data flow variable
println '1111111111111111111111111111111111111111111111111111'
//dataflowVar << expensiveData
}
return new org.grails.async.factory.gpars.GparsPromise(dataflowVar);
}
If you are wondering what this is for, it is to make the lesscss refresh automatically in grails, which is a problem when you are using import statements in less. When the file is touched, the lesscss compiler will trigger a recompilation, and only when it is done should it respond to the client.
On the client side I have some javascript that keeps replacing the last using the refresh action here:
In my controller:
/**
* Refreshes link resources. refresh?uri=/resource/in/web-app/such/as/empty.less
*/
public def refresh() {
return LessRefresh.stackRequest(request, params.uri);
}
A class written for this:
import grails.util.Environment
import grails.util.Holders
import javax.servlet.AsyncContext
import javax.servlet.AsyncEvent
import javax.servlet.AsyncListener
import javax.servlet.http.HttpServletRequest
/**
* #Author SecretService
*/
class LessRefresh {
static final Map<String, LessRefresh> FILES = new LinkedHashMap<String, LessRefresh>();
String file;
Boolean touched
List<AsyncContext> asyncContexts = new ArrayList<AsyncContext>();
String text;
public LessRefresh(String file) {
this.file = file;
}
/** Each request will be put on hold in a stack until dispatchAll below is called when the recompilation of the less file finished **/
public static AsyncContext stackRequest(HttpServletRequest request, String file) {
if ( !LessRefresh.FILES[file] ) {
LessRefresh.FILES[file] = new LessRefresh(file);
}
return LessRefresh.FILES[file].handleRequest(request);
}
public AsyncContext handleRequest(HttpServletRequest request) {
if ( Environment.current == Environment.DEVELOPMENT ) {
// We only touch it once since we are still waiting for the less compiler to finish from previous edits and recompilation
if ( !touched ) {
touched = true
touchFile(file);
}
AsyncContext asyncContext = request.startAsync();
asyncContext.setTimeout(10000)
asyncContexts.add (asyncContext);
asyncContext.addListener(new AsyncListener() {
#Override
void onComplete(AsyncEvent event) throws IOException {
event.getSuppliedResponse().writer << text;
}
#Override
void onTimeout(AsyncEvent event) throws IOException {
}
#Override
void onError(AsyncEvent event) throws IOException {
}
#Override
void onStartAsync(AsyncEvent event) throws IOException {
}
});
return asyncContext;
}
return null;
}
/** When recompilation is done, dispatchAll is called from LesscssResourceMapper.groovy **/
public void dispatchAll(String text) {
this.text = text;
if ( asyncContexts ) {
// Process all
while ( asyncContexts.size() ) {
AsyncContext asyncContext = asyncContexts.remove(0);
asyncContext.dispatch();
}
}
touched = false;
}
/** A touch of the lessfile will trigger a recompilation **/
int count = 0;
void touchFile(String uri) {
if ( Environment.current == Environment.DEVELOPMENT ) {
File file = getWebappFile(uri);
if (file && file.exists() ) {
++count;
if ( count < 5000 ) {
file << ' ';
}
else {
count = 0
file.write( file.getText().trim() )
}
}
}
}
static File getWebappFile(String uri) {
new File( Holders.getServletContext().getRealPath( uri ) )
}
}
In LesscssResourceMapper.groovy of the lesscsss-recources plugin:
...
try {
lessCompiler.compile input, target
// Update mapping entry
// We need to reference the new css file from now on
resource.processedFile = target
// Not sure if i really need these
resource.sourceUrlExtension = 'css'
resource.contentType = 'text/css'
resource.tagAttributes?.rel = 'stylesheet'
resource.updateActualUrlFromProcessedFile()
// ==========================================
// Call made here!
// ==========================================
LessRefresh.FILES[resource.sourceUrl.toString()]?.dispatchAll( target.getText() );
} catch (LessException e) {
log.error("error compiling less file: ${originalFile}", e)
}
...
In the index.gsp file:
<g:set var="uri" value="${"${App.files.root}App/styles/empty.less"}"/>
<link media="screen, projection" rel="stylesheet" type="text/css" href="${r.resource(uri:uri)}" refresh="${g.createLink(controller:'home', action:'refresh', params:[uri:uri])}" resource="true">
JavaScript method refreshResources to replace the previous link href=...
/**
* Should only be used in development mode
*/
function refreshResources(o) {
o || (o = {});
var timeoutBegin = o.timeoutBegin || 1000;
var intervalRefresh = o.intervalRefresh || 1000;
var timeoutBlinkAvoid = o.timeoutBlinkAvoid || 400 ;
var maxErrors = o.maxErrors || 200 ;
var xpath = 'link[resource][type="text/css"]';
// Find all link[resource]
$(xpath).each(function(i, element) {
refresh( $(element) );
});
function refresh(element) {
var parent = element.parent();
var next = element.next();
var outer = element.clone().attr('href', '').wrap('<p>').parent().html();
var uri = element.attr('refresh');
var errorCount = 0;
function replaceLink() {
var link = $(outer);
link.load(function () {
// The link has been successfully added! Now remove the other ones, then do again
errorCount = 0;
// setTimeout needed to avoid blinking, we allow duplicates for a few milliseconds
setTimeout(function() {
var links = parent.find(xpath + '[refresh="'+uri+'"]');
var i = 0;
// Remove all but this one
while ( i < links.length - 1 ) {
links[i++].remove();
}
replaceLinkTimeout();
}, timeoutBlinkAvoid );
});
link.error(function(event, handler) {
console.log('Error refreshing: ' + outer );
++errorCount;
if ( errorCount < maxErrors ) {
// Load error, it happens. Remove this & redo!
link.remove();
replaceLink();
}
else {
console.log('Refresh: Aborting!')
}
});
link.attr('href', urlRandom(uri)).get(0);
link.insertBefore(next); // Insert just after
}
function urlRandom(uri) {
return uri + "&rand=" + Math.random();
}
function replaceLinkTimeout() {
setTimeout(function() {
replaceLink();
}, intervalRefresh ) ;
}
// Waith 1s before triggering the interval
setTimeout(function() {
replaceLinkTimeout();
}, timeoutBegin);
}
};
Comments
I am unsure why Javascript style promises have not been added to the Grails stack.
You can not render or stuff like that in the onComplete. render, redirect and what not are not available.
Something tells me that Grails and Promises/Futures are not there yet. The design of the GPars libraries seems not take into account of the core features which is to resolve later. At least it is not simple to do so.
It would be great if the dispatch() method actually could be invoked with some paramaters to pass from the resolving context. I am able to go around this using static properties.
I might continue to write my own solution and possibly contribute with a more fitting solutions around the AsyncContext class, but for now, this is enough for me.
I just wanted to refresh my less resources automatically.
Phew...
EDIT:
I made it to support several number of files. It is complete now!

ContentProvider: How to cancel a previous call to delete()?

I'm using a custom ContentProvider. For querying, there is a CancellationSignal (API 16+) which can be used to cancel a previous call to query().
My question: How can I archive that with delete()? For clarification, my custom provider manages files on SD card, and so I want to be able to cancel delete operation inside my provider.
I solved this with a simple solution.
For example, for every call to query(), we put a parameter pointing to the task ID, and use a SparseBooleanArray to hold that ID, like:
...
private static final SparseBooleanArray _MapInterruption = new SparseBooleanArray();
...
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
Cursor cursor = ...
int taskId = ... // obtain task ID from uri
boolean cancel = ... // obtain cancellation flag from uri
if (cancel) {
_MapInterruption.put(taskId, true);
return null; // don't return any cursor
} else {
doQuery(taskId);
if (_MapInterruption.get(taskId)) {
_MapInterruption.delete(taskId);
return null; // because the task was cancelled
}
}
...
return cursor;
}// query()
private void doQuery(int taskId) {
while (!_MapInterruption.get(taskId)) {
... // do the task here
}
}// doQuery()
Usage:
To query:
...
getContentResolver().query("content://your-uri?task_id=1", ...);
To cancel:
...
getContentResolver().query("content://your-uri?task_id=1&cancel=true", ...);
For a complete working solution, have a look at android-filechooser.
The advantage is you can use this technique in Android… 1+ and for other methods such as delete(), update()... While CancellationSignal is only available in API 16+ and is limited to only query().

Outputting required field indicator for symfony forms

I have a few forms configured in symfony. One things I need is to have an asterisk (*) or other indicator next to fields that are required. The fields are all set to required int he form framework, and return a "this field is required" error when the form is submitted, but I want an indicator before the form is submitted.
If there any way to do this without overriding the labels for each field manually?
Here's an automatic solution found in Kris Wallsmith's blog:
lib/formatter/RequiredLabelsFormatterTable.class.php, this will add a 'required' class to the labels of required fields
<?php
class RequiredLabelsFormatterTable extends sfWidgetFormSchemaFormatterTable
{
protected
$requiredLabelClass = 'required';
public function generateLabel($name, $attributes = array())
{
// loop up to find the "required_fields" option
$widget = $this->widgetSchema;
do {
$requiredFields = (array) $widget->getOption('required_fields');
} while ($widget = $widget->getParent());
// add a class (non-destructively) if the field is required
if (in_array($this->widgetSchema->generateName($name), $requiredFields)) {
$attributes['class'] = isset($attributes['class']) ?
$attributes['class'].' '.$this->requiredLabelClass :
$this->requiredLabelClass;
}
return parent::generateLabel($name, $attributes);
}
}
lib/form/BaseForm.class.php, this is the common base class for all the forms in your project:
protected function getRequiredFields(sfValidatorSchema $validatorSchema = null, $format = null)
{
if (is_null($validatorSchema)) {
$validatorSchema = $this->validatorSchema;
}
if (is_null($format)) {
$format = $this->widgetSchema->getNameFormat();
}
$fields = array();
foreach ($validatorSchema->getFields() as $name => $validator) {
$field = sprintf($format, $name);
if ($validator instanceof sfValidatorSchema) {
// recur
$fields = array_merge(
$fields,
$this->getRequiredFields($validator, $field.'[%s]')
);
} else if ($validator->getOption('required')) {
// this field is required
$fields[] = $field;
}
}
return $fields;
}
add the following few lines to BaseForm as well, in the __construct() method:
$this->widgetSchema->addOption("required_fields", $this->getRequiredFields());
$this->widgetSchema->addFormFormatter('table',
new RequiredLabelsFormatterTable($this->widgetSchema)
);
After all this, all your labels will have the required class, use whatever css you need to mark it to the user.
What about the simpler solution from the original cookbook - just a few lines in twig:
http://symfony.com/doc/2.1/cookbook/form/form_customization.html#adding-a-required-asterisk-to-field-labels
you can set the field's class as part of the constructor of the sfWidget
i.e.
$this->widgetSchema['form_field'] = new sfWidgetFormInput(array(), array('class' => 'required_field'));
Note: this is assuming you're not on the ancient sfForms (ala 1.0)
UPDATE
here is some CSS code from techchorus.net to show the required asterisk
.required
{
background-image:url(/path/to/your/images/dir/required-field.png);
background-position:top right;
background-repeat:no-repeat;
padding-right:10px;
}
I did it using Javascript:
$('form').find('select, input, textarea').each(function(){
if($(this).attr('required') == 'required'){
$label = $('label[for='+ $(this).attr('id') +']');
if($label.find('.required-field').length == 0){
$label.append('<span class="required-field">*</span>');
}
}
});

Resources