VichUploader do not upload file (NULL filePath in database) - api-platform.com

I'm trying to upload file with API Plateform and VichUploader,
The entity is created in database, but the file is not uploaded and the filePath in database is NULL
# api/config/packages/vich_uploader.yaml
vich_uploader:
db_driver: orm
metadata:
type: attribute
mappings:
media_object:
uri_prefix: /media
upload_destination: '%kernel.project_dir%/public/media'
namer: Vich\UploaderBundle\Naming\OrignameNamer
<?php
namespace App\Entity;
use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Post;
use App\Controller\CreateMediaObjectAction;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
#[Vich\Uploadable]
#[ORM\Entity]
#[ApiResource(
normalizationContext: ['groups' => ['media_object:read']],
types: ['https://schema.org/MediaObject'],
operations: [
new Get(),
new GetCollection(),
new Post(
controller: CreateMediaObjectAction::class,
deserialize: false,
validationContext: ['groups' => ['Default', 'media_object_create']],
openapiContext: [
'requestBody' => [
'content' => [
'multipart/form-data' => [
'schema' => [
'type' => 'object',
'properties' => [
'file' => [
'type' => 'string',
'format' => 'binary'
]
]
]
]
]
]
]
)
]
)]
class MediaObject
{
#[ORM\Id, ORM\Column, ORM\GeneratedValue]
private ?int $id = null;
#[ApiProperty(types: ['https://schema.org/contentUrl'])]
#[Groups(['media_object:read'])]
public ?string $contentUrl = null;
#[Vich\UploadableField(mapping: "media_object", fileNameProperty: "filePath")]
#[Assert\NotNull(groups: ['media_object_create'])]
public ?File $file = null;
#[ORM\Column(nullable: true)]
public ?string $filePath = null;
public function getId(): ?int
{
return $this->id;
}
}
<?php
namespace App\Controller;
use App\Entity\MediaObject;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Attribute\AsController;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
#[AsController]
final class CreateMediaObjectAction extends AbstractController
{
public function __invoke(Request $request): MediaObject
{
$uploadedFile = $request->files->get('file');
if (!$uploadedFile) {
throw new BadRequestHttpException('"file" is required');
}
$mediaObject = new MediaObject();
$mediaObject->file = $uploadedFile;
return $mediaObject;
}
}
<?php
namespace App\Serializer;
use App\Entity\MediaObject;
use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
use Vich\UploaderBundle\Storage\StorageInterface;
final class MediaObjectNormalizer implements ContextAwareNormalizerInterface, NormalizerAwareInterface
{
use NormalizerAwareTrait;
private const ALREADY_CALLED = 'MEDIA_OBJECT_NORMALIZER_ALREADY_CALLED';
public function __construct(private StorageInterface $storage)
{
}
public function normalize($object, ?string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null
{
$context[self::ALREADY_CALLED] = true;
//$object->contentUrl = $this->storage->resolveUri($object, 'file');
return $this->normalizer->normalize($object, $format, $context);
}
public function supportsNormalization($data, ?string $format = null, array $context = []): bool
{
if (isset($context[self::ALREADY_CALLED])) {
return false;
}
return $data instanceof MediaObject;
}
}
I'm using :
"php": ">=8.2",
"api-platform/core": "^3.0",
"vich/uploader-bundle": "^2.0"

Related

How to modify fortify CreatesNewUsers.php interface?

I need to modify /vendor/laravel/fortify/src/Contracts/CreatesNewUsers.php interface
and to add 1 more bool parameter, as using CreateNewUser in different places of the app
validations rules are different, say in some places password is not filled on user creation, but must be separate function.
So I copied file /project/resources/fortify/CreatesNewUsers.php with content :
<?php
namespace Laravel\Fortify\Contracts;
interface CreatesNewUsers
{
public function create(array $input, bool $makeValidation);
}
and in app/Actions/Fortify/CreateNewUser.php I modified :
<?php
namespace App\Actions\Fortify;
use App\Models\User;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
//use Laravel\Fortify\Contracts\CreatesNewUsers;
use Resources\Fortify\CreatesNewUsers; // Reference to my interface
use Laravel\Jetstream\Jetstream;
class CreateNewUser implements CreatesNewUsers
{
use PasswordValidationRules;
public function create(array $input, bool $makeValidation)
{
...
But trying to use this class I got error
Interface "Resources\Fortify\CreatesNewUsers" not found
Which is the valid way ?
Thanks!
I moved interface at file app/Actions/Fortify/CreatesNewUsers.php :
<?php
namespace App\Actions\Fortify;
interface CreatesNewUsers
{
public function create(array $input, bool $make_validation, array $hasPermissions);
}
and modified app/Actions/Fortify/CreateNewUser.php :
<?php
namespace App\Actions\Fortify;
use App\Models\User;
use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use DB;
use App\Actions\Fortify\CreatesNewUsers;
use Laravel\Jetstream\Jetstream;
use Spatie\Permission\Models\Permission;
class CreateNewUser implements CreatesNewUsers
{
use PasswordValidationRules;
/**
* Validate and create a newly registered user.
*
* #param array $input
*
* #return \App\Models\User
*/
public function create(array $input, bool $make_validation, array $hasPermissions)
{
if ($make_validation) {
$userValidationRulesArray = User::getUserValidationRulesArray(null, '', []);
if (\App::runningInConsole()) {
unset($userValidationRulesArray['password_2']);
}
$validator = Validator::make($input, $userValidationRulesArray);//->validate();
if ($validator->fails()) {
$errorMsg = $validator->getMessageBag();
if (\App::runningInConsole()) {
echo '::$errorMsg::' . print_r($errorMsg, true) . '</pre>';
}
return $errorMsg;
}
} // if($make_validation) {
$newUserData = [
'name' => $input['name'],
'email' => $input['email'],
'account_type' => $input['account_type'],
'phone' => $input['phone'],
'website' => $input['website'],
'notes' => $input['notes'],
'first_name' => $input['first_name'],
'last_name' => $input['last_name'],
];
if (isset($input['password'])) {
$newUserData['password'] = Hash::make($input['password']);
}
if (isset($input['status'])) {
$newUserData['status'] = $input['status'];
}
if (isset($input['activated_at'])) {
$newUserData['activated_at'] = $input['activated_at'];
}
if (isset($input['avatar'])) {
$newUserData['avatar'] = $input['avatar'];
}
try {
DB::beginTransaction();
$newUser = User::create($newUserData);
foreach ($hasPermissions as $nextHasPermission) {
$appAdminPermission = Permission::findByName($nextHasPermission);
if ($appAdminPermission) {
$newUser->givePermissionTo($appAdminPermission);
}
}
DB::commit();
return $newUser;
} catch (QueryException $e) {
DB::rollBack();
if (\App::runningInConsole()) {
echo '::$e->getMessage()::' . print_r($e->getMessage(), true) . '</pre>';
}
}
return false;
}
}
It allows me to use CreateNewUser from different parts of app, like seeders, adminarea, user registration
with different behaviour. For me it seems good way of using fortify and CreateNewUser...

Laravel - How to import excel file file and save into a table with foreign keys

I am using Laravel-5.8 for a web application. Also, I am using maatwebsite-3.1 to import excel file.
Model: HrEmployee
protected $table = 'hr_employees';
protected $fillable = [
'employee_code',
'user_id',
'address',
'company_id',
'email',
'line_manager_id',
'employee_designation_id',
'employee_job_title_id',
'employee_status_id',
'employee_type_id',
'employement_type_id',
'employment_date',
'first_name',
'last_name',
'is_hod'
];
public function user()
{
return $this->belongsTo('App\User');
}
public function parent()
{
return $this->belongsTo('HrEmployee', 'line_manager_id');
}
public function children()
{
return $this->hasMany('HrEmployee', 'ine_manager_id');
}
public function company()
{
return $this->belongsTo('App\Models\Organization\OrgCompany','company_id');
}
public function designation()
{
return $this->belongsTo('App\Models\Hr\HrDesignation','employee_designation_id');
}
public function jobtitle()
{
return $this->belongsTo('App\Models\Hr\HrJobTitle','employee_job_title_id');
}
public function employeestatus()
{
return $this->belongsTo('App\Models\Hr\HrEmployeeStatus','employee_status_id');
}
public function employeetype()
{
return $this->belongsTo('App\Models\Hr\HrEmployeeType','employee_type_id');
}
public function employementtype()
{
return $this->belongsTo('App\Models\Hr\HrEmployementType','employement_type_id');
}
public function department()
{
return $this->belongsTo('App\Models\Organization\OrgDepartment','department_id');
}
I have already configured the Maatwebsite Excel Package
app/Imports/EmployeesImport.php
namespace App\Imports;
use App\User;
use App\HrEmployee;
use Maatwebsite\Excel\Concerns\ToModel;
use Maatwebsite\Excel\Concerns\WithHeadingRow;
class EmployeesImport implements ToModel, WithHeadingRow
{
public function model(array $row)
{
return new HrEmployee([
'employee_code' => $row['employee_code'],
'email' => $row['email'],
'first_name' => $row['first_name'],
'last_name' => $row['last_name'],
'line_manager_id' => $row['line_manager_id'],
'employee_designation_id' => $row['employee_designation_id'],
'employee_job_title_id' => $row['employee_job_title_id'],
]);
}
}
Controller
class HrEmployeesController extends Controller
{
public function importExportView()
{
return view('import');
}
public function import()
{
Excel::import(new EmployeesImport,request()->file('file'));
return back();
}
}
How do I re-write my code to accommodate the foreign keys (line_manager_id,employee_designation_id,employee_job_title_id), so that it will map the keys to the name.
For example, if designation_name is entered to the excel sheet it should map it to employee_designation_id.
Thank you
Just find the foreign key ID in the EmployeesImport
$row['employee_designation_id'] = HrDesignation::where("name", "like", "%".$row['designation']."%");
$row['line_manager_id'] = HrEmployee::where("first_name", "like", "%".$row['line_manager']."%");
$row['employee_job_title_id'] = HrJobTitle::where("name", "like", "%".$row['job_title']."%");
or the full code
namespace App\Imports;
use App\User;
use App\HrEmployee;
use App\Hr\HrDesignation;
use App\Hr\HrJobTitle;
use Maatwebsite\Excel\Concerns\ToModel;
use Maatwebsite\Excel\Concerns\WithHeadingRow;
class EmployeesImport implements ToModel, WithHeadingRow
{
public function model(array $row)
{
$row['employee_designation_id'] = HrDesignation::where("name", "like", "%".$row['designation']."%");
$row['line_manager_id'] = HrEmployee::where("first_name", "like", "%".$row['line_manager']."%");
$row['employee_job_title_id'] = HrJobTitle::where("name", "like", "%".$row['job_title']."%");
return new HrEmployee([
'employee_code' => $row['employee_code'],
'email' => $row['email'],
'first_name' => $row['first_name'],
'last_name' => $row['last_name'],
'line_manager_id' => $row['line_manager_id'],
'employee_designation_id' => $row['employee_designation_id'],
'employee_job_title_id' => $row['employee_job_title_id'],
]);
}
}
namespace App\Imports;
use App\User;
use App\HrEmployee;
use App\Hr\HrDesignation;
use App\Hr\HrJobTitle;
use Maatwebsite\Excel\Concerns\ToModel;
use Maatwebsite\Excel\Concerns\WithHeadingRow;
class EmployeesImport implements ToModel, WithHeadingRow
{
public function model(array $row)
{
$employee_designation = HrDesignation::where("name", "like", "%".$row['designation']."%")->first();
$line_manager = HrEmployee::where("first_name", "like", "%".$row['line_manager']."%")->first();
$employee_job_title = HrJobTitle::where("name", "like", "%".$row['job_title']."%")->first();
$row['employee_designation_id'] = $employee_designation->id;
$row['line_manager_id'] = $line_manager->id;
$row['employee_job_title_id'] = $employee_job_title->id;
return new HrEmployee([
'employee_code' => $row['employee_code'],
'email' => $row['email'],
'first_name' => $row['first_name'],
'last_name' => $row['last_name'],
'line_manager_id' => $row['line_manager_id'],
'employee_designation_id' => $row['employee_designation_id'],
'employee_job_title_id' => $row['employee_job_title_id'],
]);
}
}

laravel validate conditional image field

i have a field that required, and can be 2 value type : String (path) and Image file
how can write validation rule for this ?
if value is string check file_exist and if is file must be image
thanks
Maybe there's an easier way to do this. However, I think a custom rule as such should work.
$validator = Validator::make($request->all(), [
'image' => [
'required',
function ($attribute, $value, $fail) {
if(is_file($value)) {
if (true !== mb_strpos($value->getMimeType(), "image")) {
return $fail($attribute.' is invalid.');
}
}
if (is_string($value)) {
if(! file_exists($value)) {
return $fail($attribute.' is invalid.');
}
}
},
],
]);
i found my answer with FormRequest
MyTestFormRequest
<?php
use Illuminate\Foundation\Http\FormRequest;
use Symfony\Component\HttpFoundation\File\UploadedFile;
class MyTestFormRequest extends FormRequest
{
public function rules()
{
$rules = [
"image" => ['required']
];
if(is_string($this->image)) {
$rules['image'][] = new FileExistsRule;
} else if($this->image instanceof UploadedFile) {
$rules['image'][] = 'image';
$rules['image'][] = 'dimensions:ratio=1/1';
}
return $rules;
}
}
FileExistsRule
<?php
use Illuminate\Contracts\Validation\Rule;
class FileExistsRule implements Rule
{
public function passes($attribute, $value)
{
return file_exists(public_path($value));
}
public function message()
{
return 'The file not exists';
}
}

Couldn't get relation model value in maatwebsite laravel

The work of this function is to generate report of specific condition. Thus i am generating report with two tables ( User and Booking ) With primary key is userid and bookingid. Both the table is be clubbed into relations. Now i want to generate excel with using maatwebsite package at this condition. From (booking table) and to (booking table) with ticketstatus (booking table) and also with usertype ( from usertable ). For example From 01.09.2018 to 23.09.2018 with ticket status as "booked " and usertype as "Normal or agent". But i am getting an error, i am using FromQuery method in maatwebsite to perform this function.
I am adding all the codes here, User Model :
<?php
namespace App;
use App\Booking;
use App\Wallet;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
protected $primaryKey = 'userid';
protected $fillable = ['name', 'phone', 'email','password','usertype'];
protected $dates = [
'createdAt'
];
const CREATED_AT = 'createdAt';
const UPDATED_AT = 'updatedAt';
public function bookings()
{
return $this->hasMany('App\Booking', 'userid');
}
public function walletUsers()
{
return $this->hasOne('App\Wallet', 'userid');
}
public function supports()
{
return $this->hasMany('App\Help', 'userid');
}
public function getNameAttribute($value)
{
return ucfirst($value);
}
}
Booking Model :
<?php
namespace App;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
class Booking extends Model
{
protected $primaryKey = 'bookingid';
protected $dates = [
'createdAt','updatedAt'
];
const CREATED_AT = 'createdAt';
const UPDATED_AT = 'updatedAt';
public function users()
{
return $this->belongsTo('App\User', 'userid');
}
public function getDateOfIssueAttribute($value) {
return Carbon::parse($value)->format('d-M-Y , h:m a');
}
public function getDateOfCancellationAttribute($value) {
return Carbon::parse($value)->format('d-M-Y , h:m a');
}
public function getDojAttribute($value) {
return Carbon::parse($value)->format('d-M-Y , h:m a');
}
}
Now Controller :
public function report(Request $request){
$from = $request->from;
$to = $request->to;
$bookingtype = $request->bookingtype;
$usertype = $request->usertype;
return (new BookingsExport($from, $to, $bookingtype, $usertype))->download('invoices.xlsx');
}
Route :
Route::post('/admin/reports/collect',[
'uses' => 'ReportController#report',
'as' => 'reports.generate'
]);
Maatwebsite Class:
<?php
namespace App\Exports;
use App\Booking;
use App\User;
use Carbon\Carbon;
use App\Http\Controllers\ReportController;
use Maatwebsite\Excel\Concerns\FromQuery;
use Maatwebsite\Excel\Concerns\Exportable;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithStrictNullComparison;
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
use PhpOffice\PhpSpreadsheet\Shared\Date;
use Maatwebsite\Excel\Concerns\WithColumnFormatting;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
use Maatwebsite\Excel\Concerns\WithMapping;
class BookingsExport implements FromQuery, WithStrictNullComparison, WithHeadings, ShouldAutoSize, WithColumnFormatting, WithMapping {
use Exportable;
public function __construct($from, $to, $bookingtype, $usertype)
{
$this->from = $from;
$this->to = $to;
$this->bookingtype = $bookingtype;
$this->usertype = $usertype;
}
public function headings(): array
{
return [
'Booking Id',
'Block Key',
'Bus Type',
'DOJ',
'status',
'Created At',
'Updated At',
'Usertype',
'Name'
];
}
public function map($booking): array
{
return [
$booking->bookingid,
$booking->blockkey,
$booking->busType,
$booking->doj,
$booking->status,
$booking->createdAt,
$booking->updatedAt,
$booking->users->usertype,
$booking->users->name
];
}
public function columnFormats(): array
{
return [
'D' => 'dd-mm-yyy',
'E' => NumberFormat::FORMAT_DATE_DDMMYYYY
];
}
public function query()
{
$from = $this->from;
$to = $this->to;
$bookingtype = $this->bookingtype;
$usertype = $this->usertype;
if(isset($from) && isset($to) && is_null($bookingtype) && is_null($usertype))
{
return Booking::query()->whereBetween('createdAt',[$from, $to]);
}
if(isset($from) && isset($to) && isset($bookingtype) && is_null($usertype))
{
return Booking::query()->whereBetween('createdAt',[$from, $to])->where('status','=', $bookingtype);
}
if(isset($from) && isset($to) && isset($bookingtype) && isset($usertype))
{
return Booking::query()->with('users')->whereHas("users", function($subQuery){
$subQuery->where("usertype", "=", $usertype);})->whereBetween('createdAt',[$from, $to])->where('status','=', $bookingtype);
}
}
}
The error i am getting is "Undefined variable: usertype" from the last query of Maatwebsite class file. But i am seeding all the values from controller to this, i even dd($usertype) but i am getting the value as agent, but it shows error while using in it query ! Kindly guide

How can I test a store function with Laravel and Mockery

I want to test a repository pattern in Laravel 5.6 using PHPUnit and Mockery.
This is my code:
// PackageControllerTest.php
namespace Tests\Feature;
use Tests\TestCase;
use App\Contracts\PackageInterface;
use App\Http\Controllers\PackageController;
use Illuminate\Database\Eloquent\Collection;
class PackageControllerTest extends TestCase
{
protected $mock;
protected $target;
public function setUp()
{
parent::setUp();
parent::initDatabase();
$this->mock = $this->initMock(PackageInterface::class);
$this->target = $this->app->make(PackageController::class);
}
public function testIndex()
{
$expected = new Collection([
['name' => 'Name 1', 'html_url' => 'HTML URL 1'],
['name' => 'Name 2', 'html_url' => 'HTML URL 2'],
['name' => 'Name 3', 'html_url' => 'HTML URL 3'],
]);
$this->mock
->shouldReceive('getAllPackages')
->once()
->andReturn($expected);
$actual = $this->target->index()->packages;
$this->assertEquals($expected, $actual);
}
public function testUpdate()
{
//
}
public function tearDown()
{
parent::resetDatabase();
$this->mock = null;
$this->target = null;
}
}
// PackageControllerTest.php
...
public function index()
{
$packages = $this->package->getAllPackages();
return view('package.index', compact('packages'));
}
public function update($package_id)
{
$package = $this->package->updatePackage($package_id);
return redirect()->back();
}
// PackageRepository.php
...
public function getAllPackages()
{
$packages = $this->package->all();
return $packages;
}
public function updatePackage($package_id)
{
$package = $this->package->find($package_id);
$package->description = $this->request->description;
$package->save();
return $package;
}
The part of "testIndex()" works.
But next, I want to test the part of "testUpdate()".
How can I do?
Please help, thanks.
like this
$this->mock
->shouldReceive('updatePackage')
->with(1)
->once()
->andReturn($expected);
$actual = $this->target->update(1);
$this->assertRedirect('edit page url');
or
use DatabaseTransactions in top of class
$id = DB::table('your_table')->insertGetId(['description' => 'old'])
request()->set('description', 'test');
$actual = $this->target->update(id);
$this->assertDatabaseHas('your_table', [
'id' => $id,
'description' => 'test'
]);

Resources