yii2 Dynamically call widget - yii2

I have an action in a controller and it must works like a "factory widget":
public function actionRenderWidget($widget)
{
$widgetClass = 'My'.$widget.'Widget';
return $widgetClass::widget();
}
What is the correct way to implement something similar?

You should simply use class name with namespace (no need to use call_user_func()), e.g. :
$widgetClass = 'app\widgets\My'.$widget.'Widget';
return $widgetClass::widget();
Read more about Dynamically accessing namespaced elements.

//example
$className = 'yii\jui\DatePicker';
return call_user_func($className .'::widget');
OR
$config = [];
$widget = Yii::createObject($className, $config);
return call_user_func([$widget, 'widget']);

Related

PHPStorm generate phpdoc

I have method like this:
public function connect (Application $app)
{
$controller = $app['controllers_factory'];
$controller->get('/login', function () {
return 'test';
});
return $controller;
}
Everything works fine, but I would like to save $app in private field, like this
private $app;
public function connect (Application $app)
{
$this->app = $app;
$controller = $this->app['controllers_factory'];
$controller->get('/login', function () {
return 'test';
});
return $controller;
}
In this case PHPStrom throws error "method get not found in class". I tried phpdoc but it didn't work (As you see i use SILEX micro-framework)
The autocomplete gets stuck at this step, because it's unclear whats the value coming from array (even if it's an object that acts like an array).
$controller = $this->app['controllers_factory'];
There are two possibilities. First, if you have an array that consist of elements that share a single type, you can specify the type like this:
/**
* #var Application[]
*/
private $app;
An alternate method is to specify the type of a variable inline:
/** #var Application $controller */
$controller = $this->app['controllers_factory'];
Both options are supported by PHPStorm.

How do you assign roles to users with the basic template in Yii2?

http://www.yiiframework.com/doc-2.0/guide-security-authorization.html#role-based-access-control-rbac
In the documentation, it says that you can assign the role to the user in the advanced template by using this code:
public function signup()
{
if ($this->validate()) {
$user = new User();
$user->username = $this->username;
$user->email = $this->email;
$user->setPassword($this->password);
$user->generateAuthKey();
$user->save(false);
// the following three lines were added:
$auth = Yii::$app->authManager;
$authorRole = $auth->getRole('author');
$auth->assign($authorRole, $user->getId());
return $user;
}
return null;
}
The problem is that I am using the basic template. Is there a way of doing this inside the basic template?
I thought about using the afterSave method; however, I am not sure how to do this.
public function afterSave($insert)
{
}
Any idea on how it can be done?
public function afterSave($insert)
{
$auth = Yii::$app->authManager;
$authorRole = $auth->getRole('author');
$auth->assign($authorRole, $this->Id());
}
I am thinking this could work, but I am not totally sure.
It does not depend on used template.
Your example is correct, except few things.
$this->Id() should be replaced with $this->id (assuming primary key of users table is named id).
Note that you need also call parent implementation of afterSave() method and you missed $changedAttributes parameter:
/**
* #inheritdoc
*/
public function afterSave($insert, $changedAttributes)
{
$auth = Yii::$app->authManager;
$authorRole = $auth->getRole('author');
$auth->assign($authorRole, $this->id);
parent::afterSave($insert, $changedAttributes);
}
For further improvements, you can wrap saving in transaction, so if something is failed in afterSave(), model is not saved (afterSave() event handler is executed after model is saved in database).
Also you can move assigning role logic to separate method.
Note that with this logic every registered user will have that role. You can wrap it with some condition, however it's better to assign role through admin interface.
You can see how it's implemented for example in this extension. For example you can create separate form, action and extend GridView ActionColumn with additional icon for assigning role.

Is it possible to register an open generic delegate in autofac?

I want to register a generic delegate that resolves itself at runtime, but I cannot find a way to do this on generics.
Given a delegate that looks like this:
public delegate TOutput Pipe<in TInput, out TOutput>(TInput input);
And given a discretely registered delegate that look like this:
public class AnonymousPipe<TInput, TOutput>
{
public Pipe<TInput, TOutput> GetPipe(IContext context)
{...}
I want to register a function along the lines of this:
builder.RegisterGeneric(typeof(Pipe<,>)).As(ctx =>
{
var typeArray = ctx.RequestedType.GetGenericArguments();
// this can be memoized
var pipeDefinition = ctx.Resolve(typeof(AnonymousPipe<,>).MakeGenericType(typeArray));
return pipeDefinition.GetPipe(ctx);
I cannot find a way to provide an implementation of the generic as a parameter in Autofac - I may just be missing something. I know I can do this through a generic object or interface, but I want to stick with the lightness of a delegate. It makes unit testing super simple on the injection of these.
Any thoughts? I am having to do discrete registrations at the moment(one per type combination and no generics).
I can only come up with the registration source solution (the universal hammer in Autofac.)
class PipeSource : IRegistrationSource
{
public bool IsAdapterForIndividualComponents { get { return true; } }
public IEnumerable<IComponentRegistration> RegistrationsFor(
Service service,
Func<Service, IEnumerable<IComponentRegistration>> registrationAccessor)
{
var swt = service as IServiceWithType;
if (swt == null || !swt.ServiceType.IsGenericType)
yield break;
var def = swt.ServiceType.GetGenericTypeDefinition();
if (def != typeof(Pipe<,>))
yield break;
var anonPipeService = swt.ChangeType(
typeof(AnonymousPipe<,>).MakeGenericType(
swt.ServiceType.GetGenericArguments()));
var getPipeMethod = anonPipeService.ServiceType.GetMethod("GetPipe");
foreach (var anonPipeReg in registrationAccessor(anonPipeService))
{
yield return RegistrationBuilder.ForDelegate((c, p) => {
var anon = c.ResolveComponent(anonPipeReg, p);
return getPipeMethod.Invoke(anon, null); })
.As(service)
.Targeting(anonPipeReg)
.CreateRegistration();
}
}
}
Then:
builder.RegisterSource(new PipeSource());
Now, I'm certain that I can't type that code into a web page and have it actually compile and run, but it might come close :)

Passing multiple Include statements into a repository?

I am trying to figure out a way to pass a collection of include statements into my repository so that I can have it include specific entities. Below is some sample code from my repository.
public TEntity GetById(Guid id)
{
return id != Guid.Empty ? GetSet().Find(id) : null;
}
private IDbSet<TEntity> GetSet()
{
return _unitOfWork.CreateSet<TEntity>();
}
The GetByID method calls the GetSet to return the entity set. I was thinking, if I could somehow pass in a collection of entities to include (via an expression) as part of my GetById, this way I wouldn't have to expose the GetSet to my services. So, something like this:
var entity = _repository.GetById(theId, e => {e.Prop1, e.Prop2, e.Prop3});
I could then pass that expression into my GetSet method and pass it into an include statement. Thoughts?
I have done something like this in my code recently. Would the following work for you?
public TEntity GetById(Guid id, params Expression<Func<TEntity, object>>[] includeProperties)
{
if (id == Guid.Empty) return null;
var set = _unitOfWork.CreateSet<TEntity>();
foreach(var includeProperty in includeProperties)
{
set.Include(includeProperty);
}
return set.First(i => i.Id == id);
}
Then you would call it like this...
var entity = _repository.GetById(theId, e => e.Prop1, e=> e.Prop2, e=> e.Prop3);
I know this doesn't exactly follow your pattern, but I think you could refactor it as required.
I don't think Paige Cook's code will work quite as shown.
I've included a modified version of the code that should work instead:
public TEntity GetById(Guid id, params Expression<Func<TEntity, object>>[] includeProperties)
{
if (id == Guid.Empty) return null;
IQueryable<TEntity> set = _unitOfWork.CreateSet<TEntity>();
foreach(var includeProperty in includeProperties)
{
set = set.Include(includeProperty);
}
return set.First(i => i.Id == id);
}
I only spotted this by tracing the SQL generated by Entity Framework, and realised the original code was only giving the illusion of working, by using lazy-loading to populate the entities specified for inclusion.
There's actually a more terse syntax for applying the Include statements using the LINQ Aggregate method, which is in the blog post linked to. My post also improves the method slightly by having a fall-back to the Find method, when no includes are needed and also shows an example of how to implement a "GetAll" method, using similar syntax.
It's bad idea to store context in non-local space, for many reasons.
I modify Steve's code and get this for my ASP.NET MVC projects:
public aspnet_User FirstElement(Func<aspnet_User, bool> predicate = null, params Expression<Func<aspnet_User, object>>[] includes)
{
aspnet_User result;
using (var context = new DataContext())
{
try
{
var set = context.Users.AsQueryable();
for (int i = 0; i < includes.Count(); i++ )
set = set.Include(includes[i]);
if (predicate != null)
result = set.ToList().FirstOrDefault(predicate);
else
result = set.ToList().FirstOrDefault();
}
catch
{
result = null;
}
}
return result;
}
The include method can be strung together in your linq query like so:
var result = (from i in dbContext.TableName.Include("RelationProperty")
.Include("RelationProperty")
.Include("RelationProperty")
select i);

Simple LINQ to SQL extension method

How would I write a simple LINQ to SQL extension method called "IsActive" which would contain a few basic criteria checks of a few different fields, so that I could reuse this "IsActive" logic all over the place without duplicating the logic.
For example, I would like to be able to do something like this:
return db.Listings.Where(x => x.IsActive())
And IsActive would be something like:
public bool IsActive(Listing SomeListing)
{
if(SomeListing.Approved==true && SomeListing.Deleted==false)
return true;
else
return false;
}
Otherwise, I am going to have to duplicate the same old where criteria in a million different queries right throughout my system.
Note: method must render in SQL..
Good question, there is a clear need to be able to define a re-useable filtering expression to avoid redundantly specifying logic in disparate queries.
This method will generate a filter you can pass to the Where method.
public Expression<Func<Listing, bool>> GetActiveFilter()
{
return someListing => someListing.Approved && !someListing.Deleted;
}
Then later, call it by:
Expression<Func<Filter, bool>> filter = GetActiveFilter()
return db.Listings.Where(filter);
Since an Expression<Func<T, bool>> is used, there will be no problem translating to sql.
Here's an extra way to do this:
public static IQueryable<Filter> FilterToActive(this IQueryable<Filter> source)
{
var filter = GetActiveFilter()
return source.Where(filter);
}
Then later,
return db.Listings.FilterToActive();
You can use a partial class to achieve this.
In a new file place the following:
namespace Namespace.Of.Your.Linq.Classes
{
public partial class Listing
{
public bool IsActive()
{
if(this.Approved==true && this.Deleted==false)
return true;
else
return false;
}
}
}
Since the Listing object (x in your lambda) is just an object, and Linq to SQL defines the generated classes as partial, you can add functionality (properties, methods, etc) to the generated classes using partial classes.
I don't believe the above will be rendered into the SQL query. If you want to do all the logic in the SQL Query, I would recommend making a method that calls the where method and just calling that when necessary.
EDIT
Example:
public static class DataManager
{
public static IEnumerable<Listing> GetActiveListings()
{
using (MyLinqToSqlDataContext ctx = new MyLinqToSqlDataContext())
{
return ctx.Listings.Where(x => x.Approved && !x.Deleted);
}
}
}
Now, whenever you want to get all the Active Listings, just call DataManager.GetActiveListings()
public static class ExtensionMethods
{
public static bool IsActive( this Listing SomeListing)
{
if(SomeListing.Approved==true && SomeListing.Deleted==false)
return true;
else
return false;
}
}
Late to the party here, but yet another way to do it that I use is:
public static IQueryable<Listing> GetActiveListings(IQueryable<Listing> listings)
{
return listings.Where(x => x.Approved && !x.Deleted);
}
and then
var activeListings = GetActiveListings(ctx.Listings);