I need to add an empty collection that will always be. I do it.
protected $appends = ['additional'];
public function getAdditionalAttribute()
{
return collect();
}
Then I do additional accessor, for example
public function getSomeAttributeAttribute()
{
//some code
return $something; //bool
}
Then in the right place I call this accessor ->append('some_attribute');
But I need that the result was inside in the collection additional.
I try do it like this:
public function getSomeAttributeAttribute()
{
//some code
return $this->additional['some_attribute'] = $something; //bool
}
But it does not work, and the result is on the same level with all the elements, not inside of the collection additional.
I can do something like this:
public function getAdditionalAttribute()
{
return collect([
'some_attribute' => $this->some_attribute
]);
}
It works, but this value will be constantly, but I want to call it only when it is necessary through ->append('some_attribute');
But the collection to be constantly although empty.
Or maybe there is a different way to make it. How can I do it?
Maybe it's easier for you to overwrite the serialization itself:
class YourModel extends Model
{
/**
* Convert the object into something JSON serializable.
*
* #return array
*/
#[\ReturnTypeWillChange]
public function jsonSerialize()
{
return [
'additional' => [
'some_attribute' => $something
]
];
}
}
If you need the collection, you may want to use a custom one (php artisan make:collection \\App\\Collections\\AdditionalCollection):
class YourModel extends Model
{
public function getAdditionalAttribute()
{
return new App\Collections\AdditionalCollection($something);
}
}
class AdditionalCollection extends Collection
{
private $something;
public function __construct($something)
{
$this->something = $something;
parent::__construct([]);
}
public function __get(string $property)
{
if ($property === 'some_attribute') {
return $this->something;
}
}
}
Related
I have the following routes in routes/api.php:
Route::get('items/{item}', function(Guid $item) {...});
Route::get('users/{user}', function(Guid $user) {...});
Since Guid is a custom type, how can I resolve that via dependency injection? As shown, the route parameter {item} differs from the callback parameter type-hint:Guid so it can not be automatically resolved.
That's what I've tried in app/Providers/AppServiceProvider.php:
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* #return void
*/
public function register()
{
$this->app->bind(Guid::class, function(Application $app, array $params) {
return Guid::fromString($params[0]);
});
}
}
I'd expect $params to be something like this: [ 'item' => 'guid' ] -- but it is: [].
You can make use of explicit binding Laravel Routing:
in RouteServiceProvider::boot():
public function boot()
{
Route::model('item', Guid $item);
Route::model('user', Guid $user);
}
If Guid is not a model use a Closure to map onto the string:
Route::bind('user', function ($value) {
return Guid::fromString($value);
});
UPDATED
And I found another way, much better - implement UrlRoutable contract Lavaravel API:
<?php
namespace App\Models;
use Illuminate\Contracts\Routing\UrlRoutable;
class Guid implements UrlRoutable
{
private string $guid;
public function setGuid(string $guid)
{
$this->guid = $guid;
return $this;
}
public function getGuid(): string
{
return $this->guid;
}
public static function fromString(string $guid): self
{
//you cannot set props from constructor in this case
//because binder make new object of this class
//or you can resolve constructor depts with "give" construction in ServiceProvider
return (new self)->setGuid($guid);
}
public function getRouteKey()
{
return $this->guid;
}
public function getRouteKeyName()
{
return 'guid';
}
public function resolveRouteBinding($value, $field = null)
{
//for using another "fields" check documentation
//and maybe another resolving logic
return self::fromString($value);
}
public function resolveChildRouteBinding($childType, $value, $field)
{
//or maybe you have relations
return null;
}
}
And, with this, you can use routes like you want as Guid now implements UrlRoutable and can turn {item} (or whatever) URL-path sub-string markers into Guids per dependency injection (by the type-hint as you asked for it):
Route::get('items/{item}', function(Guid $item) {
return $item->getGuid();
});
BTW: NEVER EVER use closures in routes as you cannot cache closure routes - and routes are good to be optimized, and caching helps with that in Laravel routing.
simple helper to utilize route binding callback.
if (!function_exists('resolve_bind')) {
function resolve_bind(string $key, mixed $value) {
return call_user_func(Route::getBindingCallback($key), $value);
}
}
usage
resolve_bind('key', 'value');
I am currently working on a HMS project (Hotel Management System). I am currently stucked in implementing a DTO based functionality in Symfony 5. The following from below is my HotelMapper.php file where I would like to build a method which transforms the Array of Hotels into an Array of DTOs so I can pass them in the Hotel Controller later on and for this I would like to use the objects from the dtoToHotel() function. I already created the DTO (setters & getters).
namespace App\Transformer;
use App\DTO\HotelDTO;
use App\Entity\HotelEntity;
class HotelMapper
{
public $hotel;
public function dtoToHotel(HotelDTO $hotelDTO, Hotel $hotel) : HotelEntity
{
$hotel->setId($hotelDTO->getId());
$hotel->setName($hotelDTO->getName());
$hotel->setLocation($hotelDTO->getLocation());
$hotel->setEmployees($hotelDTO->getEmployees());
$hotel->setAvailability($hotelDTO->getAvailability());
$hotel->setFacility($hotelDTO->getFacility());
$hotel->setPhoto($hotelDTO->getPhoto());
$hotel->setDescription($hotelDTO->getDescription());
$hotel->setEarnings($hotelDTO->getEarnings());
}
public function hotelToDto(HotelEntity $hotel)
{
return HotelDTO(
$hotel->getId(),
$hotel->getName(),
$hotel->getLocation(),
$hotel->getEmployees(),
$hotel->getAvailability(),
$hotel->getFacility(),
$hotel->getPhoto(),
$hotel->getDescription(),
$hotel->getEarnings()
);
}
public function transformHotelsArrayToDTO()
{
/* Code here */
}
}
The code from below is my HotelController where I would like to update the following line $hotels = $this->hotelRepository->findAll() inside the showAllHotels() function by passing the DTO in here. Any help is much appreciated!
class HotelController extends AbstractController
{
/**
* #var HotelRepository
*/
public $hotelRepository;
public function __construct(HotelRepository $hotelRepository)
{
$this->hotelRepository = $hotelRepository;
}
/**
* #Route (path="/", methods={"GET"})
*/
public function index(): Response
{
return $this->render('index/index.html.twig');
}
/**
* #Route (path="/hotel-management", methods={"GET"})
*/
// It does populate the table with the hotels from the DB
public function showAllHotels(): Response
{
$hotels = $this->hotelRepository->findAll();
return $this->render('hotel-management/hotel-management.html.twig', array('hotels' => $hotels));
}
}
Inject HotelMapper transformer to your controller and then pass findAll's result to this function:
public function transformHotelsArrayToDTO(array $items): array
{
if (empty($items) or is_null($items)) {
return [];
}
return array_map([$this, 'hotelToDto'], $items);
}
Let's say I have an URL like this:
/city/nyc (display info about new york city)
and another like this:
/city/nyc/streets (display a list of Street of nyc)
I can bind them to a method like this:
Route::get('city/{city}', 'CityController#showCity');
Route::get('city/{city}/streets', 'CityController#showCityStreet');
The problem is that I need to execute some checks on the city (for example if {city} is present in the database) on both methods.
I could create a method and call them in both like this:
class CityController {
private function cityCommonCheck($city) {
// check
}
public function showCity($city) {
$this->cityCommonCheck($city);
// other logic
}
public function showCityStreet($city) {
$this->cityCommonCheck($city);
// other logic
}
}
Is there any better way?
Even though you think differently, I believe a middleware is the best solution for this.
First, use php artisan make:middleware CityCheckMiddleware to create a class in App/Http/Middleware. Then edit the method to do what your check is supposed to do and add a constructor to inject the Router
public function __construct(\Illuminate\Http\Routing\Router $router){
$this->route = $router;
}
public function handle($request, Closure $next)
{
$city = $this->route->input('city');
// do checking
return $next($request);
}
Define a shorthand key in App/Http/Kernel.php:
protected $routeMiddleware = [
'auth' => 'App\Http\Middleware\Authenticate',
// ...
'city_checker' => 'App\Http\Middleware\CityCheckerMiddleware',
];
Then, in your controller:
public function __construct()
{
$this->middleware('city_checker', ['only' => ['showCity', 'showCityStreet']]);
}
I think best way to do this, you can move common logic into a Model.So your code would like below.
class CityController {
public function showCity($city) {
City::cityCommonCheck($city);
}
public function showCityStreet($city) {
City::cityCommonCheck($city);
}
}
model class
class City{
public static function cityCommonCheck($city) {
//put here your logic
}
}
In this way you could invoke cityCommonCheck function from any controller.
I've got my Laravel application using the repository pattern. I also have an abstract class called EloquentRepository which contains basic methods. All of my repositories have an update() method, where I simply update a model using an ID and array:
abstract class EloquentRepository {
public function update($id, array $array) {
$this->model->whereId($id)->update($array);
}
}
Now, I also have a Server repository:
interface ServerRepository {
public function update($id, array $options);
}
class EloquentServerRepository extends EloquentRepository implements ServerRepository {
protected $model;
public function __construct(Server $model)
{
$this->model = $model;
}
}
So now, I don't have to add the update() method to my EloquentServerRepository, nor any other Repositories which need to do this (quite a few).
However, there is one repository which does have an update feature, but I'd like it to do something "custom". Lets say it's the User repository:
interface UserRepository {
public function update($id, array $options, $status);
}
class EloquentUserRepository extends EloquentRepository implements UserRepository {
protected $model;
public function __construct(User $model)
{
$this->model = $model;
}
public function update($id, array $options, $status)
{
$this->model->setStatus($status);
$this->model->whereId($id)->update($options);
}
}
So now, I have my User repository requiring a status with every update.
However, I get the error:
Declaration of EloquentUserRepository::update() should be compatible with EloquentRepository::update($id, array $array).
Why is this, surely my interface specifies what the declaration should be?
You can get passed that error by making $status optional by giving it default value, for example:
public function update($id, array $options, $status = null)
Without it being optional (with default value) you're saying this method needs to have a third parameter, which violates the contract set by ServerRepository
It's because you are extending EloquentUserRepository where you have the update method like this:
public function update($id, array $array) {
$this->model->whereId($id)->update($array);
}
In this case you are also implementing the UserRepository interface but according to the base class' update method your update method has a different signature, which is as given below:
public function update($id, array $options, $status);
So, the error is rising because you've different method signatures. While you may can make both method's signature same probably using an optional parameter like this:
// EloquentUserRepository
public function update($id, array $array, $status = null) {
$this->model->whereId($id)->update($array);
}
// interface UserRepository
interface UserRepository {
public function update($id, array $options, $status = null);
}
But I would suggest to use only one interface or abstract class and override the method in your EloquentUserRepository for the different use case. Which would look like this:
abstract class EloquentRepository {
public function update($id, array $array, $status = null) {
$this->model->whereId($id)->update($array);
}
}
// Only extend the EloquentRepository and override the update method
class EloquentUserRepository extends EloquentRepository {
protected $model;
public function __construct(User $model)
{
$this->model = $model;
}
// re-declare the method to override
public function update($id, array $options, $status = null)
{
$this->model->setStatus($status);
$this->model->whereId($id)->update($options);
}
}
Or change EloquentRepository a little, for example:
abstract class EloquentRepository {
public function update($id, array $array, $status = null) {
if(!is_null($status)) {
$this->model->setStatus($status);
}
$this->model->whereId($id)->update($array);
}
}
Is it possible to pass, somehow, a parameter to a relationship function?
I have currently the following:
public function achievements()
{
return $this->belongsToMany('Achievable', 'user_achievements')->withPivot('value', 'unlocked_at')->orderBy('pivot_unlocked_at', 'desc');
}
The problem is that, in some cases, it does not fetch the unlocked_at column and it returns an error.
I have tried to do something like:
public function achievements($orderBy = true)
{
$result = $this->belongsToMany (...)
if($orderBy) return $result->orderBy(...)
return $result;
}
And call it as:
$member->achievements(false)->(...)
But this does not work. Is there a way to pass parameters into that function or any way to check if the pivot_unlocked_at is being used?
Well what I've did was just adding new attribute to my model and then add the my condition to that attirbute,simply did this.
Class Foo extends Eloquent {
protected $strSlug;
public function Relations(){
return $this->belongsTo('Relation','relation_id')->whereSlug($this->strSlug);
}
}
Class FooController extends BaseController {
private $objFoo;
public function __construct(Foo $foo){
$this->objFoo = $foo
}
public function getPage($strSlug){
$this->objFoo->strSlug = $strSlug;
$arrData = Foo::with('Relations')->get();
//some other stuff,page render,etc....
}
}
You can simply create a scope and then when necessary add it to a builder instance.
Example:
User.php
public function achievements()
{
return $this->hasMany(Achievement::class);
}
Achievement.php
public function scopeOrdered(Builder $builder)
{
return $builder->orderBy(conditions);
}
then when using:
//returns unordered collection
$user->achievements()->get();
//returns ordered collection
$user->achievements()->ordered()->get();
You can read more about scopes at Eloquent documentation.
You can do more simple, and secure:
When you call the relation function with the parentesis Laravel will return just the query, you will need to add the get() or first() to retrieve the results
public function achievements($orderBy = true)
{
if($orderBy)
$this->belongsToMany(...)->orderBy(...)->get();
else
return $this->belongsToMany(...)->get();
}
And then you can call it like:
$member->achievements(false);
Works for the latest version of Laravel.
Had to solve this another was as on Laravel 5.3 none of the other solutions worked for me. Here goes:
Instantiate a model:
$foo = new Foo();
Set the new attribute
$foo->setAttribute('orderBy',true);
Then use the setModel method when querying the data
Foo::setModel($foo)->where(...)
This will all you to access the attribute from the relations method
public function achievements()
{
if($this->orderBy)
$this->belongsToMany(...)->orderBy(...)->get();
else
return $this->belongsToMany(...)->get();
}