Why I can not call Implementation method from library class? - laravel

In laravel 9 app I created class app/Implementations/LocalStorageUploadedFileManagement.php :
<?php
namespace App\Implementations;
use App\Interfaces\UploadedFileManagement;
...
use Intervention\Image\Facades\Image as Image;
class LocalStorageUploadedFileManagement implements UploadedFileManagement
{
...
public function getImageFileDetails(string $itemId, string $image = null, string $itemUploadsDirectory = '',
bool $skipNonExistingFile = false): array {
\Log::info('++ getImageFileDetails$image ::');
\Log::info( $image );
}
...
and Interface class in app/Interfaces/UploadedFileManagement.php :
<?php
namespace App\Interfaces;
use Illuminate\Http\Request;
interface UploadedFileManagement
{
...
public function getImageFileDetails(string $itemId, string $image = null, string $itemUploadsDirectory = '',
bool $skipNonExistingFile = false): array;
}
In app/Providers/AppServiceProvider.php I have:
class AppServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->bind('App\Interfaces\UploadedFileManagement', 'App\Implementations\LocalStorageUploadedFileManagement');
}
so I can use it in my controller app/Http/Controllers/Admin/ProductController.php:
<?php
namespace App\Http\Controllers\Admin;
...
use App\Interfaces\UploadedFileManagement;
class ProductController extends Controller // http://local-mng-products.com/admin/products
{
public function __construct()
{
}
public function edit(string $productId, UploadedFileManagement $uploadedFileManagement)
{
...
$productImgProps = $uploadedFileManagement->getImageFileDetails(
itemId : $product->_id,
itemUploadsDirectory : Product::getUploadsProductDir(),
image : $product->image,
skipNonExistingFile : true);
...
abd it works ok
But I got error :
Cannot instantiate interface App\Interfaces\UploadedFileManagement
when I try to use UploadedFileManagement in library app/Library/ReportProduct.php class :
<?php
namespace App\Library;
use App\Interfaces\UploadedFileManagement;
...
class ReportProduct
{
public function getImageProps()
{
$uploadedFileManagement= new UploadedFileManagement(); // This line raised the error
$imgProps = $uploadedFileManagement->getImageFileDetails($this->product->_id, $this->product->image, Product::getUploadsProductDir(),true);
return $imgProps;
}
getImageProps method is called from ProductCardReport component, which I created with command :
php artisan make:component ProductCardReport
and it has in file app/View/Components/ProductCardReport.php :
<?php
namespace App\View\Components;
use App\Library\ReportProduct;
use Illuminate\View\Component;
class ProductCardReport extends Component
{
public string $productId;
public function __construct(string $productId)
{
$this->productId = $productId;
}
public function render()
{
$reportProduct = new ReportProduct($this->productId);
$reportProduct->loadProduct();
$productData = $reportProduct->getProductData(true);
$productImgProps = $reportProduct->getImageProps();
...
Why I got error in ReportProduct.php class on using of UploadedFileManagement service?
How that can be done ?
MODIFIED BLOCK :
I tried to inject it in the same way I did in ProductController edit function.
But I failed as If I try to edit app/Library/ReportProduct.php with similar injection :
<?php
namespace App\Library;
use App\Interfaces\UploadedFileManagement;
...
class ReportProduct
{
public function __construct(Product | string $product, UploadedFileManagement $uploadedFileManagement = null)
{
$this->uploadedFileManagement = $uploadedFileManagement;
...
But as I create ReportProduct and call getImageProps from app/View/Components/ProductCardReport.php component
I need to UploadedFileManagement class from somewhere for the 1st time . I can not inject
UploadedFileManagement in app/View/Components/ProductCardReport.php.
Not shure how injection works here and how can I use it in chain Component->customClass?
Thanks!

Your UploadedFileManagement is an object interface and interfaces are not instantiable (a fancy way of saying you can't create an instance of an interface - i.e. new UploadedFileManagement()).
Binding an interface and an implementation in the Laravel service container doesn't magically transform your code to use the implementation wherever you have made use of the interface. All it does is tell Laravel how to resolve a dependency when it encounters one.
To clarify this when you run your application
$service = new UploadedFileManagement();
Doesn't get converted to
$service = new LocalStorageUploadedFileManagement();
That is not how dependency injection or the service container work.
If you want to make use of your LocalStorageUploadedFileManagement() class in the ReportProduct class, you will need to inject it in the same way you have done with your ProductController edit function.

Valid decision is :
$uploadedFileManagement= app(UploadedFileManagement::class);
$productImgProps = $uploadedFileManagement->getImageFileDetails(
itemId : $product->_id,
itemUploadsDirectory : Product::getUploadsProductDir(),
image : $product->image,
skipNonExistingFile : true);

Related

How to add laravel-medialibrary into Laravel/mongodb app?

I need to add spatie/laravel-medialibrary 10 into Laravel 9 app with jenssegers/mongodb:^3.9
I modified model app/Models/CMSItem.php decalaration :
<?php
namespace App\Models;
use Carbon;
use Jenssegers\Mongodb\Eloquent\Model;
use Illuminate\Validation\Rule;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
class CMSItem extends Model implements HasMedia
{
use InteractsWithMedia;
use HasFactory;
protected $collection = 'cms_items';
public $timestamps = true;
But I got error :
Call to a member function prepare() on null
with code :
foreach ($cMSItems as $nextCMSItem) {
$this->media_image_url = '';
$this->file_name = '';
foreach ($nextCMSItem->getMedia(config('app.media_app_name')) as $mediaImage) { // this line raised the error
$nextCMSItem->media_image_url = $mediaImage->getUrl();
$nextCMSItem->file_name = $mediaImage->file_name;
}
}
Reading docs https://spatie.be/docs/laravel-medialibrary/v10/advanced-usage/using-your-own-model
I try to define custom model in config/media-library.php :
...
// 'media_model' => Spatie\MediaLibrary\MediaCollections\Models\Media::class,
'media_model' => App\Models\CustomMongoMedia::class,
...
and I created file app/Models/CustomMongoMedia.php with content:
<?php
namespace App\Models;
use Jenssegers\Mongodb\Eloquent\Model;
use Spatie\MediaLibrary\MediaCollections\Models\Media as BaseMedia;
class CustomMongoMedia extends BaseMedia
{
protected $collection = 'cms_items';
public $timestamps = true;
}
But I got the same error.
If I modify this file:
class CustomMongoMedia extends Model
{
protected $collection = 'cms_items';
public $timestamps = true;
}
But I got error:
Spatie\MediaLibrary\MediaCollections\MediaRepository::__construct():
Argument #1 ($model) must be of type Spatie\MediaLibrary\MediaCollections\Models\Media, App\Models\CustomMongoMedia given, called in
/mnt/_work_sdb8/wwwroot/lar/MngProducts/vendor/spatie/laravel-medialibrary/src/MediaLibraryServiceProvider.php on line 35
In which way can it be salved ?
Thanks!

Laravel : need a controller to be called on all views

What i want is to load a sidebar with a controller inside, on my layouts/app.blade.php.
i have read that the best way is to load it on AppServiceProvider, so i tried this :
View::composer('layouts.app', function ($view) {
$data = \App\Http\Controllers\DeliveryController::index();
$view::share('Delivery',$data);
});
That works, but the DeliveryController::index gave me this error :
Using $this when not in object context
The way that really works is to forget the AppServiceProviers and to do it on every views controller like this :
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Management\DeliveryManagement;
class WelcomeController extends Controller
{
protected $deliveryManagement;
protected $nbLastsDelivery = 3;
public function __construct(DeliveryManagement $deliveryManagement)
{
// $this->middleware('auth');
$this->deliveryManagement = $deliveryManagement;
}
public function index()
{
$deliveries = $this->deliveryManagement->getLasts($this->nbLastsDelivery);
return view ('welcome', compact('deliveries'));
}
}
Unfortunately i think AppServiceProviers is a better way, right ?
If someone can help me i would be very grateful !
EDIT :
I add code of DeliveryController and DeliveryManagement :
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests\DeliveryRequest;
use App\Management\DeliveryManagement;
class DeliveryController extends Controller
{
protected $deliveryManagement;
protected $nbLasts = 3;
public function __construct(DeliveryManagement $deliveryManagement)
{
$this->deliveryManagement=$deliveryManagement;
}
public function index()
{
$deliveries=$this->deliveryManagement->getLasts($this->nbLasts);
return $deliveries;
}
and :
<?php
namespace App\Management;
use App\Models\Delivery;
class DeliveryManagement extends ResourceManagement
{
protected $delivery;
public function __construct (Delivery $delivery)
{
$this->model=$delivery;
}
public function getLasts($limit)
{
$req = $this->model->orderBy('deliveries.id', 'desc')->skip(0)->take($limit)->get();
$i=0; $render = array();
foreach($req as $delivery)
{
if($i=0)
$render = [$delivery, 'latest'];
else
$render = [$delivery, 'older'];
$i++;
}
return $render;
}
}

Laravel : How to Pass an attribute to Eloquent model constructor

I want to use strtolower() before saving data in database for 5 attributes,
I'm using this code in Model
public function setFirstNameAttribute($value)
{
$this->attributes['firstName'] = strtolower($value);
}
public function setLastNameAttribute($value)
{
$this->attributes['lastName'] = strtolower($value);
}
public function setUserNameAttribute($value)
{
$this->attributes['userName'] = strtolower($value);
}
... etc
Can I use the __construct method instead of the above code?
There are two ways first one, to use boot method directly (preferred for small changes in model like in your question)
Method 1 :
we can directly use the boot method,
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Mymodel extends Model
{
public static function boot()
{
parent::boot();
static::saving(function ($model) {
// Remember that $model here is an instance of MyModel
$model->firstName = strtolower($model->firstName);
$model->lastName = strtolower($model->lastName);
$model->userName = strtolower($model->userName);
// ...... other attributes
});
}
}
Method 2 :
So we can use here a simple trait with a simple method for generating a strtolower() for a string.This is preferred when you have to do bigger changes in your model while performing operations in model like saving, creating etc. Or even if you want to use the same property in multiple models.
Create a trait MyStrtolower
<?php
namespace App\Traits;
trait MyStrtolower
{
public function mystrtolower($string)
{
return strtolower($string);
}
}
We can now attach this trait to any class that we want to have the mystrtolower method.
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use App\Traits\MyStrtolower;
class Mymodel extends Model
{
use MyStrtolower; // Attach the MyStrtolower trait to the model
public static function boot()
{
parent::boot();
static::saving(function ($model) {
// Remember that $model here is an instance of MyModel
$model->firstName = $model->mystrtolower($model->firstName);
$model->lastName = $model->mystrtolower($model->lastName);
$model->userName = $model->mystrtolower($model->userName);
// ...... other attributes
});
}
}
If you want to not repeat all these lines of code for every model you make, make the trait configurable using abstract methods so that you can dynamically pass the attribute names for which you want to lower case string, like employee_name is Employee Model and user_name in User Model.

Laravel 5.8 add data to a layout variable via controller constructor

I am trying to add data to a layout variable via a controller constructor. The reason I want to do this is because I always need to add categories to the topmenu when this controller is called.
No success so far. I add data to a layout via a view composer like this.
namespace App\Http\ViewComposers;
use Illuminate\View\View;
use App\Menu;
class MenuComposer
{
public function compose(View $view)
{
if (in_array($view->getName(), ['layouts.master', 'layouts.master-post', 'layouts.error']))
{
$menu = Menu::menu('topmenu');
view()->with('topmenu', $menu);
// view()->share('topmenu', $menu); not working either
}
}
}
I want to extend the data in a Controller constructor.
namespace App\Http\Controllers\Post;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\View\View;
class PostController extends Controller {
public function __construct(View $view)
{
$view->offsetGet('topmenu');
// $view->gatherData() not working either
}
Whatever I try, Laravel throws an exception:
Target [Illuminate\Contracts\View\Engine] is not instantiable while building [App\Http\Controllers\Post\PostController, Illuminate\View\View].
What I did in the serviceprovider boot function:
view()->share('topmenu', [
'items' => $newItemsToAdd
]);
In the viewComposer I did:
$extraItems = view()->shared('topmenu');
if (!empty($extraItems)) {
$items = aray_merge($items, $extraItems);
}
}

Laravel: Class not found if it is called from a Trait

After creating several Apps with Laravel and using softDelete properties I realized that methods like destroy(), restore() and kill() are exactly the same among several controllers. Therefore I am trying to put themn in a trait and use it from diferent Controllers.
My code is as follows:
ProfilesController.php
<?php
namespace App\Http\Controllers;
use App\Profile;
class ProfilesController extends Controller
{
public function destroy(Profile $profile)
{
Profile::del($profile, 'profiles');
return redirect()->route('profiles.index');
}
public function trashed()
{
Profile::trash('Profile');
}
}
Profile.php (model)
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Profile extends Model
{
protected $fillable = ['user_id', 'role_id', 'title', 'subtitle', 'slug', 'birthday', 'about'];
use SoftDeletes, Helpers, commonMethods;
public function getRouteKeyName()
{
return 'slug';
}
// ... more code here
}
trait file: commonMethods.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Auth;
use App\Profile;
use Session;
trait commonMethods
{
public static function del($element, $page_name)
{
$element->delete();
Session::flash('success', $element . ' successfully deleted!');
}
public static function trash($model)
{
$total = $model::onlyTrashed()->get();
$total_tr = count($total);
$all_tr = $model::all();
return view('partials.templates.trashed', compact('total', 'total_tr', 'all_tr'));
}
// ...more code here
}
The problem:
I try to visit the view "Trashed" that will list all elements "softdeleted" but not "killed", the method.
I pass the $model variable with the method trash($model)
I get the following error:
Class App/Profile does not found. Try to call App/Profile
I have debugged and the $model variable contains exactly what I need, the string 'Profile' which is what I need to build the Query:
$total = Profile::onlyTrashed()->get();
This query works while in the ProfilesController, but does not work while in a trait, since the model class is not found.
Any idea how could I make it work?
I am using Laravel 6.
If you need to use a class as a string you will want to use its full name. 'App\Profile' instead of 'Profile'.
$model = 'Profile';
new $model; // will use `\Profile`
$model = 'App\Profile';
new $model; // will use '\App\Profile';
In your controller( ProfilesController ) write :
use App\Profile;
In your model write :
use App\commonMethods;

Resources