How to use delegates inside a static PowerShell class? - powershell

I want to use a func delegate in my static PowerShell 5.0 class:
I had issues to find a way to assign other static class methods for my delegate.
This code is working but not very convinient.
Is there a better way to use a delegate here ?
And I need to instanciate my static! class, only to get the type.
I tried the outcommented line, how you would do it with .NET types, but it's not working for my own class.
How can I get the type of my static class here more elegant ?
And, BTW, GetMethod() did not accecpt the BindingFlags parameter, why ?
class Demo
{
hidden static [object] Method_1([string] $myString)
{
Write-Host "Method_1: $myString"
return "something"
}
hidden static [object] Method_2([string] $myString)
{
Write-Host "Method_2: $myString"
return $null
}
hidden static [object] TheWrapper([string]$wrappedMethod, [string] $parameter)
{
# do a lot of other stuff here...
#return [System.Type]::GetType("Demo").GetMethod($wrappedMethod).CreateDelegate([Func``2[string, object]]).Invoke($parameter)
return [Demo]::new().GetType().GetMethod($wrappedMethod).CreateDelegate([Func``2[string, object]]).Invoke($parameter)
}
static DoWork()
{
Write-Host ([Demo]::TheWrapper('Method_1', 'MyMessage'))
[Demo]::TheWrapper('Method_2', 'c:\my_file.txt')
}
}
[Demo]::DoWork()

You don't need to create an instance of [demo] since [demo] is the actual type of the class. Also, you can write the delegate type more simply as [Func[string,object]]. This simplifies the body of TheWrapper method to
return [Demo].GetMethod($wrappedMethod).CreateDelegate([Func[string, object]]).Invoke($parameter)
but a much simpler way to do this in PowerShell is to get the method by passing its name to the '.' operator then invoking the result:
return [demo]::$wrappedMethod.Invoke($parameter)
In PowerShell, the right-hand side of the '.' operator doesn't need to be a constant. You can use an expression that results in the name of the method (or property) to retrieve.

Related

Non-static method cannot be called statically [duplicate]

Im trying to load my model in my controller and tried this:
return Post::getAll();
got the error Non-static method Post::getAll() should not be called statically, assuming $this from incompatible context
The function in the model looks like this:
public function getAll()
{
return $posts = $this->all()->take(2)->get();
}
What's the correct way to load the model in a controller and then return it's contents?
You defined your method as non-static and you are trying to invoke it as static. That said...
1.if you want to invoke a static method, you should use the :: and define your method as static.
// Defining a static method in a Foo class.
public static function getAll() { /* code */ }
// Invoking that static method
Foo::getAll();
2.otherwise, if you want to invoke an instance method you should instance your class, use ->.
// Defining a non-static method in a Foo class.
public function getAll() { /* code */ }
// Invoking that non-static method.
$foo = new Foo();
$foo->getAll();
Note: In Laravel, almost all Eloquent methods return an instance of your model, allowing you to chain methods as shown below:
$foos = Foo::all()->take(10)->get();
In that code we are statically calling the all method via Facade. After that, all other methods are being called as instance methods.
Why not try adding Scope? Scope is a very good feature of Eloquent.
class User extends Eloquent {
public function scopePopular($query)
{
return $query->where('votes', '>', 100);
}
public function scopeWomen($query)
{
return $query->whereGender('W');
}
}
$users = User::popular()->women()->orderBy('created_at')->get();
Eloquent #scopes in Laravel Docs
TL;DR. You can get around this by expressing your queries as MyModel::query()->find(10); instead of MyModel::find(10);.
To the best of my knowledge, starting PhpStorm 2017.2 code inspection fails for methods such as MyModel::where(), MyModel::find(), etc (check this thread), and this could get quite annoying.
One (elegant) way to get around this is to explicitly call ::query() wherever it makes sense to. This will let you benefit from free auto-completion and a nice formatting/indentating for your queries.
Examples
BAD
Snippet where inspection complains about static method calls
// static call complaint
$myModel = MyModel::find(10);
// another poorly formatted query with code inspection complaints
$myFilteredModels = MyModel::where('is_foo', true)
->where('is_bar', false)
->get();
GOOD
Well formatted code with no complaints
// no complaint
$myModel = MyModel::query()->find(10);
// a nicely formatted and indented query with no complaints
$myFilteredModels = MyModel::query()
->where('is_foo', true)
->where('is_bar', false)
->get();
Just in case this helps someone, I was getting this error because I completely missed the stated fact that the scope prefix must not be used when calling a local scope. So if you defined a local scope in your model like this:
public function scopeRecentFirst($query)
{
return $query->orderBy('updated_at', 'desc');
}
You should call it like:
$CurrentUsers = \App\Models\Users::recentFirst()->get();
Note that the prefix scope is not present in the call.
Solution to the original question
You called a non-static method statically. To make a public function static in the model, would look like this:
public static function {
}
In General:
Post::get()
In this particular instance:
Post::take(2)->get()
One thing to be careful of, when defining relationships and scope, that I had an issue with that caused a 'non-static method should not be called statically' error is when they are named the same, for example:
public function category(){
return $this->belongsTo('App\Category');
}
public function scopeCategory(){
return $query->where('category', 1);
}
When I do the following, I get the non-static error:
Event::category()->get();
The issue, is that Laravel is using my relationship method called category, rather than my category scope (scopeCategory). This can be resolved by renaming the scope or the relationship. I chose to rename the relationship:
public function cat(){
return $this->belongsTo('App\Category', 'category_id');
}
Please observe that I defined the foreign key (category_id) because otherwise Laravel would have looked for cat_id instead, and it wouldn't have found it, as I had defined it as category_id in the database.
You can give like this
public static function getAll()
{
return $posts = $this->all()->take(2)->get();
}
And when you call statically inside your controller function also..
I've literally just arrived at the answer in my case.
I'm creating a system that has implemented a create method, so I was getting this actual error because I was accessing the overridden version not the one from Eloquent.
Hope that help?
Check if you do not have declared the method getAll() in the model. That causes the controller to think that you are calling a non-static method.
For use the syntax like return Post::getAll(); you should have a magic function __callStatic in your class where handle all static calls:
public static function __callStatic($method, $parameters)
{
return (new static)->$method(...$parameters);
}

Powershell subclass constructor - Cannot find an overload for "new"

Powershell: 5.1.1904.1
OS: Windows 10 Pro x64, version 2004
I'm probably missing something incredibly obvious here, but...
I have a base class and a subclass.
class Vehicle {
[string]$Name
Vehicle($Name) {
$this.Name = $Name
}
}
class Car : Vehicle {
[int]$NumWheels
Car($Name, $NumWheels) {
$this.NumWheels = $NumWheels
}
}
$testVar = [Car]::new("Ford Fiesta", 4)
When calling the subclass constructor I'm getting the message
Cannot find an overload for "new" and the argument count: "0".
When I remove the class inheritance I can call the constructor. Powershell seems to find it.
When I remove the inheritance and get info on the constructor
[Car]::new
I see
OverloadDefinitions
-------------------
Car new(System.Object Name, System.Object NumWheels)
When inheritance is specified (Car : Vehicle) I still get
OverloadDefinitions
-------------------
Car new(System.Object Name, System.Object NumWheels)
Totally unclear why this it seems to see the constructor, but I'm unable to call it when I inherit.
I know I have to be doing something wrong here. :P
Thank you in advance
To complement Doug Maurer's helpful answer:
The ultimate cause of the problem is that your Vehicle class has no default - always argument-less - constructor, which, as Doug points out, is the result of the class having at least one explicitly defined constructor (Vehicle($Name) { ... }). Once you have at least one explicitly defined constructor, you'll have to define an argument-less one explicitly (Vehicle() { ... }) - if needed.
The immediate cause is that the constructor in your derived class, Car, implicitly tries to call an argument-less constructor on the base class, Vehicle, because you haven't specified an explicit base-class constructor call via base(...) as part of the derived-class constructor.
Therefore, the right solution for you is to call the base-class constructor explicitly, namely the one with the $Name parameter:
# Note the call to the base-class constructor via `base`
Car($Name, $NumWheels) : base($Name) {
$this.NumWheels = $NumWheels
}
This obviates the need to add an argument-less constructor to the base class just for the sake of inheritance.
See also: about_Classes.
The error "no overload with argument count 0" is because there is no default constructor. When you create your own constructor the default constructor is no longer provided for you. The other issue is you are not fulfilling the contract by assigning the name.
Either create a default constructor for [Vehicle]
class Vehicle {
[string]$Name
Vehicle(){}
Vehicle($Name) {
$this.Name = $Name
}
}
class Car : Vehicle {
[int]$NumWheels
Car($Name, $NumWheels) {
$this.NumWheels = $NumWheels
$this.Name = $Name
}
}
[Car]::new("Ford Fiesta",4)
NumWheels Name
--------- ----
4 Ford Fiesta
or just remove the custom constructor from it
class Vehicle {
[string]$Name
}
class Car : Vehicle {
[int]$NumWheels
Car($Name, $NumWheels) {
$this.NumWheels = $NumWheels
$this.Name = $Name
}
}
[Car]::new("Ford Fiesta",4)
NumWheels Name
--------- ----
4 Ford Fiesta

How to initialize a static dictionary property in a Powershell class?

Is there any way for a static property in a Powershell class to contain a generic dictionary? Without the initialisation syntax that exists in C#, I haven't found a way to do it.
enum Environment
{
Production
QA
Dev
}
class Config
{
# How to get this populated?
static [System.Collections.Generic.Dictionary[Environment, hashtable]] $EnvData
}
Potential workarounds:
Add-Type and a C# class
A GetEnvData() method
Is there a better way I haven't thought of?
Just like in C#, you can use the static constructor (which is what initializers are syntactic sugar for, anyway):
class Config
{
static [System.Collections.Generic.Dictionary[Environment, hashtable]] $EnvData
static Config() {
$d = New-Object ([System.Collections.Generic.Dictionary[Environment, hashtable]])
$d["Production"] = #{Setting="Foo"}
[Config]::EnvData = $d
}
}
The use of a local is not strictly required, but I sleep better knowing the initialization is atomic.
You can also use an initializer ($EnvData = ...) but that's a little tricky in this case, since creating a generic Dictionary in one statement is, well, awkward, and the class syntax doesn't like it if you get complicated (pipes, nested function calls). You could still, if you so wanted, split off initialization into a separate hidden static function and call that for initialization (... $EnvData = [Config]::initialEnvData()), which may be more readable than one big constructor if you've got many conceptually independent properties.

How to get current class name/object inside PowerShell static class method?

I need $this to work inside static class! How to achieve that? Any workaround? I have analyzed return of Get-PSCallStack in class context and found nothing useful.
I need this for (a) logging and for (b) calling other static methods of same class without mentioning its name again and again.
Sample code (PowerShell v5):
class foo {
static [void]DoSomething() {
[foo]::DoAnything() #works
#$this.DoAnything #not working
$static_this = [foo]
$static_this::DoAnything() #works
}
static [void]DoAnything() {
echo "Done"
}
}
[foo]::DoSomething()
Static classes do not have this pointer. See MSDN
Static member functions, because they exist at the class level and not
as part of an object, do not have a this pointer. It is an error to
refer to this in a static method.
You must call method by class name.

Preventing PowerShell from wrapping value types in PSObjects

I have a .NET API that uses a lot of delegates. My API has a couple methods similar to the following:
public static class MyClass
{
public static void DoSomethingWithString(Func<object> myFunc)
{
string myStringValue = myFunc().ToString();
Console.WriteLine(myStringValue);
}
public static void DoSomethingWithDouble(Func<object> myFunc)
{
object unparsedValue = myFunc();
double parsedValue = Convert.ToDouble(unparsedValue);
Console.WriteLine(parsedValue);
}
}
Now in PowerShell I have the following:
[MyClass]::DoSomethingWithString({ "Hello" }); # No error here
[MyClass]::DoSomethingWithDouble({ 123.4 }); # InvalidCastException - can't convert a PSObject to double
The problem is that my PowerShell scriptblock is returning a PSObject instead of the actual double value. My .NET API doesn't know anything about PowerShell, and I don't want to add a reference to PowerShell DLLs just so I can add special handling for this particular scenario.
Is there a way to get my scriptblocks to return actual value types rather than PSObjects? Or is there a PowerShell-agnostic way for my .NET library to handle PSObjects?
PowerShell will wrap things in PSObject as it sees fit, there's no clean way to avoid that when accepting arbitrary script blocks. With some discipline, you can write script blocks that unwrap the PSObject, for example the following might work fine:
[MyClass]::DoSomethingWithDouble({ (123.4).PSObject.BaseObject })
If possible, a better option is to have your api take a delegate with a more specific return type.
public static void DoSomethingWithDouble(Func<double> myFunc)
In this case, PowerShell will convert the return value to the type expected by the delegate. When the return type is PSObject, PowerShell knows PSObject trivially converts to object so it doesn't unwrap, but if the return type is pretty much anything else, PowerShell is forced to do the conversion by unwrapping the PSObject.
For completeness, another option if you're using PowerShell V3 is to use the C# keyword dynamic, something like:
public static void DoSomethingWithDouble(Func<object> myFunc)
{
dynamic unparsedValue = myFunc();
double parsedValue = (double)(unparsedValue);
Console.WriteLine(parsedValue);
}
When unparsedValue is a PSObject, PowerShell will perform the conversion from to double on the second line even though you don't reference any PowerShell assemblies in your C# code.
Note these last two options may not fit well with your real API, but they are options that are worth understanding.