Laravel - Get value instances via facades - laravel

In Illuminate\Support\Facades\Facade abstract In method
protected static function resolveFacadeInstance($name)
{
if (is_object($name)) {
return $name;
}
if (isset(static::$resolvedInstance[$name])) {
return static::$resolvedInstance[$name];
}
return static::$resolvedInstance[$name] = static::$app[$name];
}
static::$app is an instance of Application. And static::$app[$name] that like access value of array, and i don't understand that, What technique here?
ex: static::$app['router'] it return instance of Router. Seem that get values of protected $instances in Illuminate\Container\Container
I think it like example ? but got FATAL ERROR Uncaught Error: Cannot use object of type Foo as array
class Foo
{
public $bar = 'barValue';
}
$foo = new Foo();
echo $foo['bar'];

If you check the API of Illuminate\Container\Container, you will notice that it implements ArrayAccess and consequently the following methods.
offsetExists()
offsetGet()
offsetSet()
offsetUnset()
ArrayAccess lets you access objects as arrays. Here's a very simplistic example of a Container.
<?php
class Container implements ArrayAccess {
private $items = array();
public function __construct() {
$this->items = [
'one' => 1,
'two' => 2,
'three' => 3,
];
}
public function offsetSet($offset, $value) {
if (is_null($offset)) {
$this->items[] = $value;
} else {
$this->items[$offset] = $value;
}
}
public function offsetExists($offset) {
return isset($this->items[$offset]);
}
public function offsetUnset($offset) {
unset($this->items[$offset]);
}
public function offsetGet($offset) {
return isset($this->items[$offset]) ? $this->items[$offset] : null;
}
}
$container = new Container();
echo $container['one']; // outputs 1
$container['four'] = 4; // adds 4 to $items.
echo $container['four']; // outputs 4
As you can see, you can access the Container object as an array since it implements ArrayAccess.
It also doesn't matter if the items property is not publicly accessible. In any case, the implementation of ArrayAccess means that it will allow us to retrieve those values as if they were in an array.

Related

working with the collection of objects in laravel

Trying to work with the collection of objects.
the collection is NOT an Eloquent one but hand made Illuminate\Support\Collection
if i understood it right in the case of collection of objects i'm not able to use most of methods but only those which can use callback.
so, i have collection of objects:
and here is the code ($country = 'Russia'):
dump($this->countries);
$filtered = $this->countries->filter(function ($countryObj) use($country) {
dump($countryObj->name == $country);
return $countryObj->name == $country;
});
dd($filtered);
i expect the $filtered contains only one element, the one which return true (in our case Russia)
but instead of it i have the same collection of 3 elements.
here is the rest of classes to be sure they are collection related
use App\Services\Taxes\DataSourceInterface;
use Illuminate\Contracts\Filesystem\FileNotFoundException;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
abstract class JsonModel extends Collection implements DataSourceInterface
{
public function __construct()
{
parent::__construct($this->readDataFile(env('JSON_DATA_PATH')));
}
protected function readDataFile(string $path): array
{
$disk = Storage::disk('local');
try {
$dataObj = json_decode($disk->get($path), false, 10, JSON_THROW_ON_ERROR);
return $this->loadData($dataObj);
} catch (FileNotFoundException $e) {
Log::error('Storage ' . $e->getMessage() . ' was not found');
} catch (\JsonException $e) {
Log::error('Json DataFile: ' . $e->getMessage());
}
return [];
}
abstract protected function loadData(object $dataObject): array;
}
class JsonCountries extends JsonModel
{
public function loadData(object $dataObject): array
{
$data = array_filter($dataObject->countries, function ($item){
unset($item->states);
return true;
});
return $data;
}
}
problem was in new static which used inside laravel methods which return instance of collection and in fact that i dont expect array entry in my constructor.
choice between empty array and file reading fixes the problem
abstract class JsonModel extends Collection implements DataSourceInterface
{
public function __construct($dataArr = [])
{
if(!is_array($dataArr))
$dataArr = $this->readDataFile(env('JSON_DATA_PATH'));
parent::__construct($dataArr);
}

Using a dummy class for testing when required in a model

I am looking for the best way to test a call in an API I have created.
My test is:
/**
* #test
*/
public function it_can_return_a_block()
{
$block = new Block();
$block->type = "MyComp\MyOtherPackage\Widget";
$block->save();
$response = $this->call('GET', 'http://api.mysite.com/blocks?id=1');
$response->assertExactJson([
"id" => 1,
"type" => "MyComp\MyOtherPackage\Widget",
"component" => "widget",
"name" => "My Widget"
]);
}
The Block model for this uses the type attribute to instantiate a MyComp\MyOtherPackage\Widget instance which is extended from another class: MyComp\MyPackage\BlockDefintion which is abstract:
public class Block
{
...
private $definition;
public function getDefinitionAttribute()
{
if(!$this->definition)
{
$this->definition = new $this->type;
}
return $this->definition;
}
public function getNameAttribute()
{
return $this->definition->name;
}
public function getComponentAttribute()
{
return $this->definition->component;
}
...
}
This test works fine if MyComp\MyOtherPackage\Widget is real and can be instantiated but these clases will be in a different package so I want to decouple the relationship in my test.
Is there a way to create some kind of dummy class to use here? I was thinking Mockery but not sure how I can use this in this instance

Pass data between different "Via" Laravel Notification

Do you know how to pass data between 2 different "Via" notification?
This is the situation:
I have created 2 custom channels for notification
PushNotificationChannel
CustomDatabase
In my Notification Class I have this:
class GeneralNotification extends Notification
{
use Queueable;
private $notificationType;
private $sender;
public function __construct($notificationType, $sender)
{
$this->notificationType = $notificationType;
$this->sender = $sender;
}
public function via($notifiable)
{
return [CustomDatabaseChannel::class, PushNotificationChannel::class];
}
public function toPushNotification($notifiable)
{
return [
//
];
}
public function toCustomDatabase($notifiable)
{
return [
//
];
}
}
So this will first execute the toCustomDatabase method, and then the toPushNotification method.
What I want is after I save the data into de database, pass it (the inserted record) to the toPushNotification method.
I was testing with assigning for example:
public function toCustomDatabase($notifiable)
{
$this->notificationType = 'test';
return [
//
];
}
but when I do:
public function toPushNotification($notifiable)
{
dd($this->notificationType);
return [
//
];
}
It shows me the original notificationType, and not the value I changed in the toCustomDatabase method.
What I did was to add the inserted record into de session() (Although in API Middleware the session is not persistent, it works within the request cycle). Then in the toPushNoification method I retreive that session.

Custom Eloquent relation which sometimes returns $this Model

I have a Model Text which has a 1-to-many-relation called pretext(), returning a 1-to-many-Relationshop to Text, like so:
class Text extends Model
{
public function pretext(){
return $this->belongsTo('App\Models\Text', 'pretext_id');
}
public function derivates(){
return $this->hasMany('App\Models\Text', 'pretext_id');
}
}
If a $text does not have any pretext (which, in my scenario, means $text['pretext_id'] == 0) $text->pretext() shall return the $text itself. When I try
public function pretext(){
if ( $this->belongsTo('App\Models\Text', 'pretext_id') ) {
return $this->belongsTo('App\Models\Text', 'pretext_id');
}
else {
return $this;
}
}
I get the error
local.ERROR: LogicException: Relationship method must return an object of type Illuminate\Database\Eloquent\Relations\Relation
when the else-part is executed. So my questions are:
How do I turn $this into an object of type Relation? or alternatively:
How can I achieve my goal on a different way?
Try dynamic props
class Text extends Model
{
protected $appends = ['content'];
public function pretext(){
return $this->belongsTo('App\Models\Text', 'pretext_id');
}
public function getContentAttribute(){
$pretext = $this->pretext()->get();
if ($pretext->count()) {
return $pretext;
}
return $this;
}
}
Then in controller or view if you have the instance
(consider optimizing it if you have N+1 issues)
$obj = Text::find(1);
dd($obj->content);
I think you can create another method that calling pretext() and check the returned value.
public function getPretext() {
$value = pretext();
return ($value)? $value : $this;
}

Laravel automatic resolution with parameters

I have a class like this:
class PostValidator
{
public function __construct(Validator $validator, $data)
{
$this->validator = $validator;
$this->data = $data;
}
}
I read Laravel doc about IoC automatic resolution, it gives an example:
class FooBar {
public function __construct(Baz $baz)
{
$this->baz = $baz;
}
}
$fooBar = App::make('FooBar');
Is it possible to use App::make only without App::bind (with closure) to instantiate my class above which also passing parameter $data?
No, you can't do that.
The idea is that you pass only the dependencies to the constructor, and obviously data is not one. Validator works with the data, but does not depend on the data.
Instead use setter for the data.
class PostValidator
{
public function __construct(Validator $validator)
{
$this->validator = $validator;
}
public function setData($data)
{
$this->data = $data;
}
}
and simply call it explicitly:
$validator = App::make('PostValidator');
$validator->setData($data);
// or in the controller, which is what you're doing most likely
public function __construct(PostValidator $validator)
{
$this->validaotr = $validator;
}
public function update($id)
{
$data = Input::only([ input that you need ]);
$this->validator->setData($data);
// run the validation
...
}
edit: as per comment, this is what 2nd argument $parameters does:
// Foo class with Eloquent Models as dependencies
public function __construct(User $user, Category $category, Post $post)
{
$this->user = $user;
$this->category = $category;
$this->post = $post;
}
then IoC container will resolve the dependencies as newly instantiated models:
$foo = App::make('Foo');
$foo->user; // exists = false
$foo->category; // exists = false
$foo->post; // exists = false
but you can do this if you want:
$user = User::first();
$cat = Category::find($someId);
$foo = App::make('Foo', ['category' => $cat, 'user' => $user]);
$foo->user; // exists = true, instance you provided
$foo->category; // exists = true, instance you provided
$foo->post; // exists = false, newly instantiated like before

Resources