Symfony - Create Service object with parameters - api

I created a new service in my symfony application:
namespace AppBundle\Service;
class CustomService {
public function __construct($username, $password) {
// stuff
}
public function getItems() {
}
}
and configured in config.yml:
services:
custom_service:
class: AppBundle\Service\CustomService
My question is, how to create an object from this service with multiple arguments?
Like:
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
class CustomController extends Controller {
public function listAction() {
$custom_service = $this->get('custom_service'); //how to pass multiple arguments here?
// Next i would use my custom service, like:
$items = $custom_service->getItems();
}
}
Anybody knows how to solve this issue?
Thanks and Greetings!

Basically you don't. The container supports injecting dependencies. Doing what you propose kind of defeats the purpose of using a dependency injection container.
One work around is to add an init method to your object.
$custom_service = $this->get('custom_service')->init($additional_arguments);

Related

ASP NET Core Define API Controller within class

I'm currently switching from .net framework to .net core 3.1.
Defining Api Controllers inside the namespace is all fine and works.
Now I have the case, that I need to declare the Api Controllers within another class, like this:
namespace Api.Controllers
{
public class MainClass : BaseClass
{
public MainClass()
{
}
[ApiController]
[Route("Test")]
public class TestController : ControllerBase
{
[HttpGet]
public int GetResult()
{
return 0;
}
}
}
}
The result is, that the Api Controller can't be found after calling the "AddControllers" method inside the "ConfigureServices" method of the startup class.
The MainClass is instantiated before the Startup class will be called.
I've tried to change the global route and defining an area like "{area=Test}/{controller=Test}", or set the ApiController attribute above the MainClass, but none of them worked.
Is there a way to get this working?
Looks like the default ControllerFeatureProvider does not treat nested controller types as controller. You can add (don't need to replace) your custom provider to change that behavior, like this:
public class NestedControllerFeatureProvider : ControllerFeatureProvider
{
protected override bool IsController(TypeInfo typeInfo)
{
if(!typeInfo.IsClass) return false;
if(typeInfo.IsAbstract) return false;
var isNestedType = typeInfo.DeclaringType != null;
var isPublic = true;
var parentType = typeInfo.DeclaringType;
while(parentType != null){
isPublic = parentType.IsPublic;
parentType = parentType.DeclaringType; ​
​ }
​ return isNestedType && isPublic; ​
}
}
Then add that provider to the ApplicationPartManager in Startup.ConfigureServices like this:
​​services
.AddMvc()
​​ .ConfigureApplicationPartManager(appPart => {
​​appPart.FeatureProviders.Add(new NestedControllerFeatureProvider());
});
If you want to replace the default ControllerFeatureProvider, just find it in the FeatureProviders and remove it. Of course then you need to ensure that your custom one should handle everything just like what done by the default logic, something like this:
​​//for IsController
return base.IsController(typeInfo) || <...your-custom-logic...>;
NOTE: You can refer to the default implementation of ControllerFeatureProvider to learn some standard logic to implement your own logic correctly. The code above is just a basic example. To me, as long as the classes inherits from ControllerBase and not abstract, they can work fine as a controller to serve requests. There would be no serious troubles except some weird conventions (e.g: class name not ending with Controller is still a controller or some standard attributes applied on the controller class are not working ...).
We should not use nested controller classes. Each controller class should be put in a separate file (as a good practice). However the point of this answer (the most interesting part that I'm pretty sure not many know about, is the use of ControllerFeatureProvider which can help you customize the features set in other scenarios). And really if you really have to stick with your design somehow, you of course have to use this solution, no other way.

Property Injection in Microsoft.Extensions.DependencyInjection

I am relatively new to working with DI containers and have hit a bit of a roadblock.
SimpleInjector has a method with the following signature:
Container.RegisterInitializer<TService>(Action<TService>)
In our code base we do use it like this:
// this is a property injection of the abstract file system
container.RegisterInitializer<IFileSystemInjection>(
fs => fs.FileSystem = new FileSystem());
I am wondering how I would achieve the same using the IServiceCollection parameter in the ConfigureServices method in the Startup.cs class. So far I have been able to register all my types using the services.AddTransient(); but I am not sure how what the equivalent simpleinjector.RegisterInitializer is within the IServiceCollection.
You'd use the factory overload(s) of AddSingleton, AddScoped, and AddTransient. I'm not sure what scope IFileSystemInjection should be in, but it sounds like something that could be a singleton. If not, change the method you call appropriately:
service.AddSingleton<IFileSystemInjection>(p =>
{
var fs = new FileSystemInjection();
fs.FileSystem = new FileSystem();
});
In short, if you provide a factory, then you're responsible for the entire object initialization, hence the new FileSystemInjection(), which I'm subbing as the actual implementation of IFileSystemInjection your using.
If that implementation has dependencies that need to be injected in order to create it, you can pull those from p, which is an instance of IServiceProvider:
var myDep = p.GetRequiredService<MyDep>();
var fs = new FileSystemImplementation(myDep);
You can use this nuget package, that extends standard Microsoft Dependency Injection and adds property injection:
https://www.nuget.org/packages/DJMJ.Extensions.DependencyInjection.Property/1.1.0
Mark property for injection
using Microsoft.Extensions.DependencyInjection;
public class FooService
{
[Inject]
public IBooService BooService { get; set; }
public void Foo()
{
// just start using injected property
BooService...
}
}
Add services scan method in ConfigureServices
using Microsoft.Extensions.DependencyInjection;
...
host.ConfigureServices((services)=>
{
services.AddTransient<IBooService, BooService>();
services.AddTransient<IFooService, FooService>();
// scan method
services.AddPropertyInjectedServices();
});
If you using this extension in asp net and want add property injection support in controllers too, you should add in ConfigureServices this statement:
services.AddControllers().AddControllersAsServices()

How to invoke a controller method just when condition happens

I want to invoke controller method, in case a specific condition happens.
Can you give me an example how to implement it?
i guess it can intercepted with webapi filters..
Thanks.
I'm assuming that you are talking about invoking a method from a controller/api from another method/controller. It's actually pretty easy.
First, in your startup class you'll need to find the ConfigureServices method and add:
services.AddMvc().AddControllersAsServices();
Then, in whichever controller you need it, just create a new instance of that controller and you can call those methods. Assume you have api1 and you want to call a method from api2. I'd create the api via dependency injection:
public class api1
{
public _api2 {get;set;}
public api1()
{
_api2 = new api2();
}
}
And now you can call your methods however you'd like from api2:
public IActionResult SomeCondition()
{
if(!someCondition)
{
return _api2.YourMethod1();
}
else
{
return _api2.YourMethod2();
}
}

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.

Phalcon Initialize() is not working

I have 2 Controllers, TEST1Controller and TEST2Controller
In TEST2Controller I have a initialize() function setting value of a property.
If I try to access TEST2Controller directly from the browser, everything works perfectly.
But when I call a TEST2Controller method from TEST1Controller, it seems that initialize() function is not being called in TEST2Controller.
TEST1Controller:
namespace Modcont\Controller;
use Modcont\Controller\Test2Controller;
class Test1Controller extends BaseController
{
function gettestAction()
{
$t = new Test2Controller(); // calling TEST2 Controller Method Within TEST1 Controller
echo $t->dotestAction(" MYAPP ");
}
}
TEST2Controller:
namespace Modcont\Controller;
class Test2Controller extends BaseController
{
public $prefix;
function initialize()
{
$this->prefix = 'your name is';
}
function dotestAction($name)
{
return $this->prefix.' : '.$name;
}
}
Phalcon offers two ways for controller initialization, thy are the initialize and onContruct methods. The basic difference between these two methods is that initialize is called only when a controller is created by the framework to proceed with the execution of an action. Since you instantiating a controller object ad-hoc, initialize will not be called, only onConstruct will. So you'll need to put your initialization logic there:
function onConstruct()
{
$this->prefix = 'your name is';
}
Also, implementing native constructors in controller is discouraged, but if you do so, make sure to call the parent constructor in your own constructor: parent::__construct();.
All this information can be found in the Docs.