How to implement custom model manager in sonata admin - api

I have a entity in my symfony/sonata application which is not persisted via doctrine orm. Instead i would like to fill the data for these entities on demand by calling an api ...
How can i connect this custom managed entity with my existing model which is managed by doctrine orm?
Is there a way to exchange the doctrine managing part with my custom api calls? I know there are some repository functions like findAll() findBy() and so on ... is the solution to overwrite this functions?
Another approaches?
Thanks

I had a similar use case. I ended up with a solution which is roughly the following.
1) Create a custom ModelManager class. In my case I extended the one provided for Doctrine (Sonata\DoctrineORMAdminBundle\Model\ModelManager), but you can also directly implement Sonata\AdminBundle\Model\ModelManagerInterface.
Override the methdods you need to change (e.g. create(), update(), delete()).
# MyCustomModelManager.php
namespace AppBundle\Admin;
use Sonata\DoctrineORMAdminBundle\Model\ModelManager;
class MyCustomModelManager extends ModelManager {
public function create($object){ // Your custom implementation }
public function create($object){ // Your custom implementation }
public function create($object){ // Your custom implementation }
}
2) Register your new model manager as a service (not needed if you use Symfony 3.3 with the default container configuration, which includes automatic configuration of classes as services)
# services.yml
...
AppBundle\Admin\MyCustomModelManager:
arguments: [ '#doctrine' ]
...
3) Configure your admin class to use your custom model manager, for example:
app.admin.myadmin:
class: AppBundle\Admin\MyAdmin
tags:
- { name: sonata.admin, manager_type: orm }
arguments: [~, AppBundle\Entity\MyEntity, ~]
calls:
- [ setModelManager, [ '#AppBundle\Admin\MyCustomModelManager' ]]
Hope it helps :)

Related

Adding DbContext for list of context types at runtime

To implement a plug-in system in a AspNet Core Mvc app, I would like a non-generic method to add a data context from a list of assemblies loaded dynamically at runtime, taking a Type parameter like this:
foreach(Type tp in pluginContexts)
{
services.AddDbContext(tp, options => ...);
}
instead of the usual
services.AddDbContext<PluginDataContext>(options => ...);
That's because for dynamically loaded assemblies, I can not provide the TContext type parameter to the AddDbContextPool method, since that's statically compiled and not available at compile time.
Background
This is for a larger Asp.Net Core MVC app. The plugins must be able to both access the main database of the overall app and a separate database of their own.
Plugin assemblies, containing domain code and their private database context are to be dropped in a specified directory.
The main app loads the plugin assembly dynamically upon startup.
The way I am solving this now is to have each controller get the IConfiguration instance injected, obtain the appropriate connection string from the config, and the database context is instantiated in the controller. Not so nice but does work.
One can easily inject a general class into the Services collection with AddScoped<>, and then use it as a sort of ServiceLocator - however, that is considered an antipattern.
I looked into the source code for AddDbContext but honestly I am lost.
Is there any simple way to achieve this?
Solved it by creating an extensibility point in the plugin assembly.
Define an interface in the main app, which all plugins must implement.
public interface IPluginContextRegistration
{
void RegisterContext(ref IServiceCollection services, Action<DbContextOptionsBuilder> optionsAction);
String GetDatabaseName();
}
Create a class implementing this interface (in the plugin). It has access to the type of its private database context, thus can use the generic AddDbContext method:
public class DatabaseRegistration : IPluginContextRegistration
{
public void RegisterContext(ref IServiceCollection services, Action<DbContextOptionsBuilder> optionsAction)
{
services.AddDbContext<Test1DbContext>(optionsAction);
}
public String GetDatabaseName()
{
return "test-plugin-db";
}
}
Then in the main app ASP.Net Startup.cs file, add following code, which calls the RegisterContext() method for each plugin. For example, if you want to use Sql Server:
void RegisterPluginDbContexts(ref IServiceCollection services, List<Assembly> assemblyList)
{
IEnumerable<IPluginContextRegistration> registrars = new List<IPluginContextRegistration>();
foreach (Assembly assembly in assemblyList)
{
registrars = registrars.Concat(GetClassInstances<IPluginContextRegistration>(assembly));
}
foreach (var reg in registrars)
{
String name = reg.GetDatabaseName();
String connStr = Configuration.GetConnectionString(name);
reg.RegisterContext(ref services, options => options.UseSqlServer(connStr));
}
}
For completeness - the method "GetClassInstances" is just a helper method using Reflection to obtain an instance of classes implementing the specified interface.
So it's simple after all - no need for re-writing framework code .

Laravel define relationship between two tables

I have the following tables
1. Environment
--------------
env_id
env_name
env_description
repo_id
2. Repository
--------------
repo_id
repo_name
repo_url
Now multiple environments can be associated with same repository. I am very new to Eloquent and so has a confusion that which kind of relation this is ? I have an api and I would like to return repo details when I access an environment endoint. So when I use Evironment::all(), I would like to get the associated Repository. How can I do this ?
I went through the examples given in the documentation, but it seems like I have a scenario which is not mentioned there.
That will be a One To Many(inverse) relationship.
//Environment Model
public function repository()
{
return $this->belongsTo('App\Repository');
}
//Repository Model
public function environments()
{
return $this->hasMany('App\Environment', 'repo_id');
}
Then you can return all the Environment with their relationship as follow:
Environment::with('repository')->get();
According to your description, it should be a one-to-many relationship between two tables. Therefore, in plain English sentences, it would be, the Repository has many Environments and the Environment model belongs to the Repository. Let's build up those two models.
Repository model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Repository extends Model
{
public function environments()
{
return $this->hasMany(Environment::class);
}
}
Environment model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Environment extends Model
{
public function repo()
{
return $this->belongsTo(Repository::class);
}
}
Notice: I name after Environment::repo() instead of Environment::repository().
If we called Environment::repository as a property on the Environment object inside a foreach loop, it would return null instead of Repository object. It may be eloquent's naming issue. To be honest, I don't know the reason so far. I'm using Laravel 7.
Once you're done with the creations of models, you can play with data using them. As you need to get the associated Repository object with Environment object, check out the following code:
<?php
$environments = Environment::all();
foreach ($environments as $environment) {
echo $environment->repo->repo_name . '<br>';
}

Get or inject Lagom application context LagomApplicationContext

Exist any way to get LagomApplicationContext? I would like to use play configuration object play.api.Configuration form playContext - composition:
sealed trait LagomApplicationContext {
/**
* The Play application loader context.
*/
val playContext: Context
}
Any ideas or suggestion how to do it? Is Exist some DI?
I need to use values from application.conf the same like in this example:
https://www.webkj.com/play-framework/play-scala-2.5-reading-config-using-di
Yes it is true - but only it is possible to inject in controller level:
class OnlineDiscountTagImpl #Inject() (cache : CacheApi) extends Controller

DunglasApiBundle - Trying to get the bundle to use Named Constructors rather than public constructor

I'm using the Dunglas api-platform bundle (https://github.com/api-platform/api-platform) for a new app.
Setup and installation went fine, GET requests are working.
While trying to create new objects using POST requests, I received errors about having a private constructor. My models are all made using a private constructor, and using named constructors instead.
Ideally i'm either looking for a way to have the bundle call my Named constructors, ... or someone to tell me my approach is completely wrong.
Services.yml
services:
resource.player:
parent: "api.resource"
arguments: [ "Name\\Space\\Player" ]
tags: [ { name: "api.resource" } ]
Player Object
class Player
{
private $name;
private function __construct()
{
}
public static function withName($playerName)
{
$player = new Player();
$player->name = $playerName;
return $player;
}
public function getName()
{
return $this->name;
}
}
Settings are pretty much all out of the box, following the introduction and setup in the documentation. I've skimmed through the Factory thing briefly - hoping that i'd be able to use a factory to create the objects, allowing me to call my own named constructors - but that doesn't seem to do what i think it does.
Any input regarding the use, boundaries or the setup is well appreciated.
API Platform (like most Symfony and Doctrine related libraries) is not designed to work with immutable objects like this one.
I suggest to create a typical mutable Entity as suggested in the doc:
class Player
{
private $name;
public static function setName($playerName)
{
$this->name = $playerName;
}
public function getName()
{
return $this->name;
}
}
If you really want to keep your immutable model, you'll need to implement yourself the Symfony\Component\PropertyAccess\PropertyAccessorInterface and use a CompilerPass to make API Platform using your own implementation. You will probably need to submit a patch to API Platform and to the Symfony Serializer Component to update the reference of the given object too because currently, both serializers actually update the current object and will not use the new instance returned by your with method.
I strongly encourage you to switch to typical mutable entities.

Controlling lifetime of objects created by factory generated by ToFactory()

I am using the following Ninject related nuget packages in an MVC 5 WebAPI application:
Ninject.MVC5
Ninject.Extensions.Factory
ninject.extensions.conventions
I have a simple repository and a corresponding factory class like so:
public interface ITaskRunner
{
void Run();
}
public interface IRepository<T> where T: class
{
T[] GetAll();
}
public interface IRepositoryFactory<T> where T: class
{
IRepository<T> CreateRepository();
}
I have setup the Ninject bindings using ToFactory() from Ninject.Extensions.Factory like so:
kernel.Bind<ITaskRunner>().To<TaskRunner>().InSingletonScope();
kernel.Bind(typeof(IRepository<>)).To(typeof(Repository<>)).InRequestScope();
kernel.Bind<IRepositoryFactory<Contact>>().ToFactory();
I am using the factory in the following class:
public class TaskRunner : ITaskRunner
{
//MyTask is a simple POCO class(not shown for brevity)
IRepositoryFactory<MyTask> repoFactory = null;
IRepository<MyTask> repo = null;
public TaskRunner(IRepositoryFactory<MyTask> repoFactory)
{
this.repoFactory = repoFactory;
repo = repoFactory.CreateRepository();
}
//implementation elided
}
I am noticing that the call to repoFactory.CreateRepository() always returns the same instance of the factory (dynamic proxy) that Ninject generates.
Question : Is there a way to change/control this behavior and set a "lifetime" such as Transient, PerThread etc. for the instance that "CreateRepository" returns?
In this particular case, tasks might be processed asynchronously on multiple threads and the repository is not thread safe and hence singleton behavior for the instance returned from "CreateRepository" is not desirable.
I'm not sure what you are trying to achieve, but results you are seeing are quite expected because your TaskRunner is bound as Singleton (so constructed once), and you retrieve your repository in the TaskRunner constructor, which again happens once, and so repo is always the same instance. Note this happens regardless of how you bind IRepository and IRepositoryFactory, see Captive Dependency post by Mark Seemann for details http://blog.ploeh.dk/2014/06/02/captive-dependency/.
In fact, if you need to create repo in the constructor, you could just inject IRepository itself. The power of the Factory extension lies in the fact that it allows to resolve instances at runtime, not construction time. For example, if your TaskRunner has Run() method, you can create repository in it, so each task to run can have its own instance.