Laravel collection map use private function in same class instead closure - laravel

I use the map function to loop each object of collections.
The closure method is too long and I tried to make it a private function.
But I cannot successfully call the function.
Here is my sample code. (note: the function test may be very long in real case)
<?php
class cls
{
private function test($a) {
return ($a + 1);
}
public function run1() {
return ($this->test(5));
}
public function run2() {
$col = Collect([1,2,3]);
return ($col->map($this->test()));
}
public function run3() {
$col = Collect([1,2,3]);
$mycls = $this;
return ($col->map(function ($c) use ($mycls) {
return($mycls->test($c));
}));
}
}
$c = new cls;
$c->run1(); # 6
$c->run2(); # Error: Too Few Argements
$c->run3(); # [2,3,4]
I use the function: run1 to test if the private function is callable and I failed at function: run2.
Although function: run3 makes code shorten. It seems a little bit too superfluous.
How can I make run2 works?
Update
My version of Laravel is 6.2
Update
I tried the #xenooooo answer.
It works with the public function, but I get a different error code with the private method.

Simple answer: $this->test() required a parameter and you are simply not passing anything to it.
You can also modify your run2/run3 methods to do the following:
public function run2() {
$col = collect([1,2,3])->map(function ($value) {
return $this->test($value);
});
return $col; //returns a collection of items
//return $col->toArray(); //(extra option) returns an array of collected items
}
Result:
Illuminate\Support\Collection {#872 ▼
#items: array:3 [▼
0 => 2
1 => 3
2 => 4
]
#escapeWhenCastingToString: false
}
You may read more on collections here: https://laravel.com/docs/9.x/collections#introduction

The problem in run2 is that you are calling the method directly and you need to pass the an argument since test is expecting argument $a. To use the method as a callback function you need the pass it as an array which is the first value is $this and the second is the method name which is test :
public function test($a) {
return ($a + 1);
}
public function run2()
{
$collection = collect([1,2,3]);
$newCollection = $collection->map([$this,'test']);
return $newCollection;
}
UPDATE
It only work if the method that you will call is public and it will not work if you use a private or protected method. If you use a private or protected if will throw a BadMethodCallException

Related

Laravel Dependency Injection in Mockery

I'm trying to write test for this repo: laravel.com. below is the app's structure.
App\Documentation.php
public function __construct(Filesystem $files, Cache $cache)
{
$this->files = $files;
$this->cache = $cache;
}
public function get($version, $page)
{
return $this->cache->remember('docs.'.$version.'.'.$page, 5, function () use ($version, $page) {
if ($this->files->exists($path = $this->markdownPath($version, $page))) {
return $this->replaceLinks($version, markdown($this->files->get($path)));
}
return null;
});
}
public function markdownPath($version, $page)
{
return base_path('resources/docs/'.$version.'/'.$page.'.md');
}
DocsController.php
public function __construct(Documentation $docs)
{
$this->docs = $docs;
}
public function show($version, $page = null)
{
$content = $this->docs->get($version, $sectionPage);
}
here is my test logic
$mock = Mockery::mock('App\Documentation')->makePartial();
$mock->shouldReceive('markdownPath')->once()->andReturn(base_path('test/stubs/stub.md'));
$this->app->instance('App\Documentation', $mock);
$this->get('docs/'.DEFAULT_VERSION.'/stub') //It hits DocsController#show
//...
here is the error
Call to a member function remember() on null
Different ways I've tried so far and different errors
1)
$mock = \Mockery::mock('App\Documentation');
$mock->shouldReceive('markdownPath')->once()->andReturn(base_path('test/stubs/stub.md'));
app()->instance('App\Documentation', $mock);
Mockery\Exception\BadMethodCallException: Received Mockery_0_App_Documentation::get(), but no expectations were specified
Here I don't wanna define expectation for each methods.
2)
app()->instance('App\Documentation', \Mockery::mock('App\Documentation[markdownPath]', function ($mock) {
$mock->shouldReceive('markdownPath')->once()->andReturn(base_path('test/stubs/stub.md'));
}));
ArgumentCountError: Too few arguments to function App\Documentation::__construct(), 0 passed and exactly 2 expected
If we specify method name in mock (App\Documentation[markdownPath]); the constructor is not mocking, so we get error.
You can use the container to resolve a real instance (so that the filesystem and cache dependencies are resolved), and then create a partial mock from your real instance in order to define the expectation for the markdownPath method. Once your mock instance is setup, put it into the container for the controller to use.
// Resolve a real instance from the container.
$instance = $this->app->make(\App\Documentation::class);
// Create a partial mock from the real instance.
$mock = \Mockery::mock($instance);
// Define your mock expectations.
$mock->shouldReceive('markdownPath')->once()->andReturn(base_path('test/stubs/stub.md'));
// Put your mock instance in the container.
$this->app->instance(\App\Documentation::class, $mock);

how to return object instead of object in array

I am trying to return a response of an object which came from a collection array due to a relation of hasMany.
I have tried to do a return $block->where('date','=',$today)->first();
error said: Call to undefined method
App\BlockDate::addEagerConstraints()
public function block_dates()
{
return $this->hasMany(BlockDate::class);
}
public function schedule_block()
{
$today = Carbon::today()->toDateString();
$block = $this->block_dates();
return $block->where('date','=',$today)->first();
}
schedule_block() should return an object of BlockDate. If I remove first(), it returns an array with the desired object in. I would like to just retrieve the object based on the relation. Any help is appreciated.
try this one :
public function schedule_block() {
$today = Carbon::today()->toDateString();
return $this->hasOne(BlockDate::class)->where('date','=',$today);
}

How to return different relation as the same name in an Api Resource

In my project I have set up multiple relationships like :
Model
public function foo()
{
return $this->hasMany(Bar::class);
}
public function fooSold()
{
return $this->hasMany(Bar::class)->where('sold', 1);
}
Controller
public function show()
{
$bar = Bar::with('foo')->first();
return new BarResource($bar);
}
public function showSold()
{
$bar = Bar::with('fooSold')->first();
return new BarResource($bar);
}
Resource
public function toArray($request)
return [
...
'foo' => Foo::collection($this->whenLoaded('foo')),
]
Returning the first function in my controller isn't any problem. But how would I return the second one under the same name as 'foo' in my resource ?
'foo' => Foo::collection($this->whenLoaded'fooSold')),
'foo' => Foo::collection($this->whenLoaded'foo')),
This works but doesn't seem like the right way to do it, since you have the same array keys twice.
What's the best way of doing this ?
Use a local query scope for the second case:
public function scopeSold($query)
{
return $query->whereHas('foo', function ($q) {
$q->where('sold', 1);
});
}
// call the scope
$sold = Foo::sold();
The whole point of arrays is to have unique keys. If you want to store pairs of values, then create an array of arrays like:
$array[] = [$value1, $value2];
In your case, something like:
'foo' => [Foo::collection($this->whenLoaded'fooSold')), Foo::collection($this->whenLoaded'foo'))]
Try this:
'foo' => Foo::collection($this->whenLoaded('foo') instanceof MissingValue ? $this->whenLoaded('fooSold') : $this->whenLoaded('foo')),

Adding methods to Eloquent Model in Laravel

I'm a bit confused how I am to add methods to Eloquent models. Here is the code in my controller:
public function show($id)
{
$limit = Input::get('limit', false);
try {
if ($this->isExpand('posts')) {
$user = User::with(['posts' => function($query) {
$query->active()->ordered();
}])->findByIdOrUsernameOrFail($id);
} else {
$user = User::findByIdOrUsernameOrFail($id);
}
$userTransformed = $this->userTransformer->transform($user);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound('User does not exist');
}
return $this->respond([
'item' => $userTransformed
]);
}
And the code in the User model:
public static function findByIdOrUsernameOrFail($id, $columns = array('*')) {
if (is_int($id)) return static::findOrFail($id, $columns);
if ( ! is_null($user = static::whereUsername($id)->first($columns))) {
return $user;
}
throw new ModelNotFoundException;
}
So essentially I'm trying to allow the user to be retrieved by either user_id or username. I want to preserve the power of findOrFail() by creating my own method which checks the $id for an int or string.
When I am retrieving the User alone, it works with no problem. When I expand the posts then I get the error:
Call to undefined method
Illuminate\Database\Query\Builder::findByIdOrUsernameOrFail()
I'm not sure how I would go about approaching this problem.
You are trying to call your method in a static and a non-static context, which won't work. To accomplish what you want without duplicating code, you can make use of Query Scopes.
public function scopeFindByIdOrUsernameOrFail($query, $id, $columns = array('*')) {
if (is_int($id)) return $query->findOrFail($id, $columns);
if ( ! is_null($user = $query->whereUsername($id)->first($columns))) {
return $user;
}
throw new ModelNotFoundException;
}
You can use it exactly in the way you are trying to now.
Also, you can use firstOrFail:
public function scopeFindByIdOrUsernameOrFail($query, $id, $columns = array('*')) {
if (is_int($id)) return $query->findOrFail($id, $columns);
return $query->whereUsername($id)->firstOrFail($columns);
}
Your method is fine, but you're trying to use it in two conflicting ways. The one that works as you intended is the one in the else clause, like you realised.
The reason the first mention doesn't work is because of two things:
You wrote the method as a static method, meaning that you don't call it on an instantiated object. In other words: User::someStaticMethod() works, but $user->someStaticMethod() doesn't.
The code User::with(...) returns an Eloquent query Builder object. This object can't call your static method.
Unfortunately, you'll either have to duplicate the functionality or circumvent it someway. Personally, I'd probably create a user repository with a non-static method to chain from. Another option is to create a static method on the User model that starts the chaining and calls the static method from there.
Edit: Lukas's suggestion of using a scope is of course by far the best option. I did not consider that it would work in this situation.

Codeigniter write chainable functions

Can I write chainable functions in CodeIgniter?
So if I have functions like these :
function generate_error(){
return $data['result'] = array('code'=> '0',
'message'=> 'error brother');
}
function display_error(){
$a= '<pre>';
$a.= print_r($data);
$a.= '</pre>';
return $a;
}
I want to call those by chaining them :
echo $this->generate_error()->display_error();
The reason why I want to seperate these functions are because display_error() is only useful for development, so when it comes to production, I can just remove the display_error() or something like that.
Thanks!
To write chainable functions they musy be part of a class, from the function you then return a reference to the current class (usually $this).
If you return anything other than a reference to a class it will fail.
It is also possible to return a reference to another class (e.g. when you use the code igniter active records class get() function it returns a reference to the DBresult class)
class example {
private $first = 0;
private $second = 0;
public function first($first = null){
$this->first = $first;
return $this;
}
public function second($second = null){
$this->second = $second;
return $this;
}
public function add(){
return $this->first + $this->second;
}
}
$example = new example();
//echo's 15
echo $example->first(5)->second(10)->add();
//will FAIL
echo $example->first(5)->add()->second(10);
you should return $this in your function to make chain-able functions in php oop
public function example()
{
// your function content
return $this;
}

Resources