Serialization of 'Doctrine\DBAL\Driver\PDOConnection' is not allowed - php

I'm working on a project
and I made an exportable class
this is the class
I'm sending a query to exportal tyope of 'Illuminate\Database\Eloquent\Builder'
<?php
namespace App\Exports;
use Maatwebsite\Excel\Concerns\FromQuery;
use Maatwebsite\Excel\Concerns\Exportable;
use Maatwebsite\Excel\Concerns\WithMapping;
use Illuminate\Contracts\Queue\ShouldQueue;
use Maatwebsite\Excel\Concerns\WithHeadings;
class ExcelExport implements FromQuery, WithMapping, WithHeadings
{
use Exportable;
private $query;
public function __construct($query)
{
$this->query = $query;
}
public function query()
{
return $this->query;
}
public function headings():array
{
$return = [
'H1',
'H2',
];
return $return;
}
public function map($row):array
{
$return = [];
foreach ($row->relation as $rel) {
$return[] = $rel->column;
}
return $return;
}
}
and call it into my controller as
public function export(Request $request, string $type = 'excel')
{
// returns Builder
$query = $this->service->findByReportable($request, 1);
$file = new ExcelExportLead($query);
if ($file){
switch (strtolower($type)) {
case 'pdf':
$file_name = 'export-' . time() . '.pdf';
// OriginExcel refers to 'Maatwebsite\Excel\Excel'
return $file::queue($file, $file_name, OriginExcel::DOMPDF);
break;
default:
$file_name = 'export-' . time() . '.xls';
return $file->queue($file_name);
break;
}
} else{
return back()->withErrors(__('common.Sorry But there Was an issue in exporting Data please try again'));
}
But there the error appeared is 'Serialization of 'Doctrine\DBAL\Driver\PDOConnection' is not allowed'
i don't know how to solve it I've used SerializesModels but it didn't fix the issue

Serialization it's a tool where you encode a variable/object into a text (or binary) representation to then move that variable to another thread/process.
The underlying problem it's that the Builder object has a PDOConnection attribute (the connection to the database) and those objects by definition can't be serialized, as they are usually a file descriptor in the operating system that can't be moved to another process/thread.
The solution should go along the lines of either
Sending the query as a string to ExcelExport using the builder->toSql() method
Encoding the query in some other way (IE anourvalar/eloquent-serialize https://packagist.org/packages/anourvalar/eloquent-serialize).

Related

How to make a fetched result fit into a model?

Let's say I do a
$response = Http::get('http://example.com');
$i_want_to_be_a_model = $response->json();
And I have a \App\Models\Example model.
How can I make the $i_want_to_be_a_model to be an Example model?
I want to do this, because I want to add a method statusText() to the model, that is not part of the result.
class Example extends Model {
// ..
public string $statusText;
public int $status;
public function statusText() {
switch ($this->status) {
case 100:
$this->statusText = "foo";
break;
//..
default:
$this->statusText = "bar";
}
}
}
If there is a more elegant way if doing this, please let me know.
You can define a helper function or a Factory class to create objects of Example class.
For eg:
<?php
namespace App\Factories;
use App\Models\Example;
use Illuminate\Support\Facades\Schema;
class ExampleFactory
{
public function __construct(array $attributes)
{
$example = new Example;
$fields = Schema::getColumnListing($example->getTable());
foreach($attributes as $field => $value) {
if(in_array($field, $fields) {
$example->{$field} = $value;
}
}
return $example;
}
public static function makeFromArray(array $attributes)
{
return new static(... $attributes);
}
}
Then you can use the Factory as
// use App\Factories\ExampleFactory;
$response = Http::get('http://example.com');
$example = ExampleFactory::makeFromArray(json_decode($response->json(), true));
//Now you can do whatever you want with the instance, even persist in database
$example->save();

PHP 8 - Annotated method with Route Attribute not triggered for GET request

I am trying to load (include file) the GetRiskSummaryCommandHandler.php (GetRiskSummary\CommandHandler) at runtime dynamically, while resolving Route api/risks to HandleCommand (of class CommandHandler) method.
How can I do this? If not can I modify any taken approach including modifying autoloader?
My api class snippet looks like this:
API.php
<?php
abstract class API
{
public function processRequest()
{
$id1 = $this->requestObj->id1;
//$id2 = $this->requestObj->id2;
$endpoint1 = $this->requestObj->endpoint1;
$endpoint2 = $this->requestObj->endpoint2;
$isDestination = in_array($id1, ['first', 'prev', 'next', 'last']);
$numSetEndpoints = (int)isset($endpoint1) + (int)isset($endpoint2);
switch($numSetEndpoints)
{
case 0:
if ($isDestination)
return json_decode($this->_response("No Endpoint: ", $endpoint1));
return json_decode($this->_response("ProjectAIM API"));
case 1:
$className = $endpoint1.'Controller';
break;
case 2:
$className = $endpoint2.'Controller';
break;
}
$class = "GetRiskSummaryCommandHandler";
$method = "HandleCommand";
if (class_exists($class))
{
if (method_exists($class, $method))
{
$response = (new $class($this->requestObj))->{$method}($this->requestObj);
if ($response['Succeeded'] == false)
{
return $response['Result'];
}
else if ($response['Succeeded'] == true)
{
header("Content-Type: application/json");
return $this->_response($response);
}
else if ($response['Result'])
{
header("Content-Type: text/html");
return $this->_response($response);
}
}
}
}
**Command Handler Snippet, uses a Route Attiribute
GetRiskSummaryCommandHandler.php
<?php
namespace GetRiskSummary;
use Infrastructure\CommandHandler;
class GetRiskSummaryCommandHandler extends CommandHandler
{
#[Route("/api/risks", methods: ["GET"])]
public function HandleCommand()
{
$qb = $this->getEntityManager()
->createQueryBuilder();
$qb->select('*')
->from('Risks', 'Risks')
->orderBy('RiskID', 'DESC');
$query = $qb->getQuery();
return $query->getResult();
}
}
Autoloader.php
<?php
namespace Autoloader;
class Autoloader
{
private static function rglob($pattern, $flags = 0) {
$files = glob($pattern, $flags);
foreach (glob(dirname($pattern).'/*', GLOB_ONLYDIR|GLOB_NOSORT) as $dir) {
$files = array_merge($files, self::rglob($dir.'/'.basename($pattern), $flags));
}
return $files;
}
public static function ClassLoader($path)
{
$pathParts = explode("\\", $path);
$path = $pathParts[array_key_last($pathParts)];
$matches = self::rglob("*/$path*");
foreach ($matches as $name)
{
$filePath = realpath($name);
if (file_exists($filePath))
include $filePath;
}
}
}
spl_autoload_register("AutoLoader\AutoLoader::ClassLoader");
I think the biggest missing feature of PHP Attributes would be that as far as I'm aware PHP Attributes are static and aren't evaluated at runtime. They are just glorified comments.
If it was true, we could do some truly amazing things like non-intrusively attaching pre/post-processing functions, just like what you wrote.
They work just like in symfony or doctrine. You can't use variables in them and you must write your own code to search your php class tree for attributes in the code and use php reflection to obtain the information and run the attributes.
To be honest it's pretty disappointing, although maybe it's just the first step that will be fixed in later versions of PHP.
But for now, I think what you're asking for is impossible. Because they are not running dynamically like you appear to be asking.

PHP - Assigning a string value to a variable that is type hinted as an enum

I have some PHP snippets for an application I am trying to restrict inputs coming from a request in the front end of my JavaScript application. The page sends a request using JSON object which contains a field value present that I assign as 'Open', 'Complete', or 'Closed'. I want to prevent unwanted input tampering or values to be sent through.
Question:
Below property $eventstatus is type hinted with the enum, but when I assign the string value inside $array['EventStatus'] PHP (7.4.9) reports an error that my types are not compatible. It needs to see a Status type when in fact I am assigning it a string.
How do I fix this?
$event->eventstatus = $array['EventStatus'];
Enum class (Status)
<?php
namespace app\enums;
abstract class Status
{
const Open = 'Open';
const Complete = 'Complete';
const Closed = 'Closed';
}
Mapper Class Member Function - snippet, code below takes an array value and maps it to a class property
<?php
function mapFromArray($event, $array) {
if (!is_null($array['EventStatus'])) $event->eventstatus = $array['EventStatus'];
}
Model Class
<?php
namespace data\model;
use app\enums\Status;
class Event
{
public $eventid;
public $riskid;
public $eventtitle;
public Status $eventstatus;
}
Your type hint actually tells PHP that you expect $eventstatus to be an instance of Status. But the values are actually just simple strings: 'Open', 'Complete' and 'Closed'.
So the correct type hint would be:
<?php
namespace data\model;
use app\enums\Status;
class Event
{
// ...
public string $eventstatus;
}
But with this PHP accepts any string and not only a "valid" one. Using proper Enums here would help but currently PHP 7 has no native support for Enums (which is implemented for PHP 8.1 though).
If you want to use the Status class for more readable code you can just change the type hint to string.
If you want to validate the input data you could extend the code like this:
<?php
namespace app\enums;
abstract class Status
{
const Open = 'Open';
const Complete = 'Complete';
const Closed = 'Closed';
const Valid_Statuses = [
self::Open,
self::Complete,
self::Closed,
];
}
function mapFromArray($event, $array) {
if (!is_null($array['EventStatus'])) {
if (in_array($array['EventStatus'], Status::Valid_Statuses)) {
$event->eventstatus = $array['EventStatus'];
} else {
// handle invalid status value here
}
}
}
If you want to use strict type hinting to ensure validity everywhere you'd need to wrap the value into a instance of the class, e.g.:
namespace app\enums;
abstract class Status
{
const Open = 'Open';
const Complete = 'Complete';
const Closed = 'Closed';
const Valid_Statuses = [
self::Open,
self::Complete,
self::Closed,
];
private string $value;
public function __construct(string $value) {
if (!in_array($value, self::Valid_Statuses)) {
throw \InvalidArgumentException(sprintf('Invalid status "%s"', $value));
}
$this->value = $value;
}
public function getValue(): string {
return $this->value;
}
public function __toString(): string {
return $this->value;
}
}
function mapFromArray($event, $array) {
if (!is_null($array['EventStatus'])) {
try {
$event->eventstatus = new Status($array['EventStatus']);
} catch (\Exception $exception) {
// handle invalid status value here
}
}
}
I tried a slightly different method from what was proposed using array values, but still relying on some sort of array to check for allowed values.
In my Events class I extended from abstract class Mapper (within which I added a new performMapping function to make mapping more dynamic)
<?php
namespace data\mapper;
use app\enums\Status;
use data\model\Event;
class Events extends Mapper
{
public function mapFromArray($array) : Event
{
$event = $this->_performMapping($array, new Event());
return $event;
}
}
Model - Added Magic Methods (__set, __get)
<?php
namespace data\model;
use app\enums\Status;
class Event
{
public $eventid;
public $riskid;
public $eventtitle;
private $eventstatus;
public $eventownerid;
public $actualdate;
public $scheduledate;
public $baselinedate;
public $actuallikelihood;
public $actualtechnical;
public $actualschedule;
public $actualcost;
public $scheduledlikelihood;
public $scheduledtechnical;
public $scheduledschedule;
public $scheduledcost;
public $baselinelikelihood;
public $baselinetechnical;
public $baselineschedule;
public $baselinecost;
public function __set($name, $value)
{
switch ($name)
{
case 'eventstatus':
{
$class = Status::class;
try
{
$reflection = new \ReflectionClass($class);
}
catch (\ReflectionException $ex)
{
return null;
}
$constants = $reflection->getConstants();
if (array_key_exists($value, $constants))
$this->$name = constant("\\".$class."::$constants[$value]");
else
throw (new \Exception("Property $name not found in " . $class));
}
default:
{
if (property_exists(get_class($this), $name))
$this->$name = $value;
else
throw (new \Exception("Property $name not found in " . get_class($this)));
}
}
}
public function __get($name)
{
switch ($name)
{
case 'eventstatus':
return $this->$name;
default:
if (property_exists($this, $name))
return $this->$name;
else
return null;
}
}
}
Mapper
<?php
namespace data\mapper;
abstract class mapper
{
protected $db = null;
public function __construct(\PDO $db)
{
$this->db = $db;
}
abstract public function mapFromArray($array);
protected function _populateFromCollection($results = null)
{
$return = [];
if ($results != null)
{
foreach($results as $result)
{
$return[] = $this->mapFromArray($result);
}
}
return $return;
}
protected function _performMapping($array, $object)
{
foreach (array_keys($array) as $property)
{
$lowerCaseProperty = strtolower($property);
if (property_exists(get_class($object), $property))
$object->$property = $array[$property];
else if (property_exists(get_class($object), $lowerCaseProperty))
$object->$lowerCaseProperty = $array[$property];
}
return $object;
}
Enum
<?php
namespace app\enums;
abstract class Status
{
const Open = 'Open';
const Complete = 'Complete';
const Closed = 'Closed';
}

Maatwebsite Laravel Excel queued export serialization error

I am using Maatwebsite\Excel to handle data exports in my application. Some of the exports are pretty big, so I want to queue the exports. I have followed the documentation, but I get the following error when attempting to export:
You cannot serialize or unserialize PDO instances
I understand that PDO instances cannot be serialized, I just don't understand why it's telling me that since I am following what is said in the docs. Here's my code:
Controller
$path = 'public/validations/' . $filename;
//the $client var is a record retrieved from a model, $input is the result of $request->all()
(new ValidationsExport($client, $input))->queue($path)->chain([
// the script never reaches here, but $user is from \Auth::user()
new NotifyUserOfValidationExport($user, $filename),
]);
Export
class ValidationsExport implements FromQuery, WithHeadings, WithMapping, WithStrictNullComparison, WithCustomQuerySize, WithEvents, WithColumnFormatting
{
use Exportable;
private $client;
private $input;
public function __construct($client, $input)
{
$this->client = $client;
$this->input = $input;
}
public function query()
{
// this does return a query builder object, but this is required for FromQuery and is shown in the docs as an example of how to queue an export
$this->validations = $this->getValidations();
return $this->validations;
}
public function querySize(): int
{
$query = ....
$size = $query->count();
return $size;
}
public function headings(): array
{
// this is an array
return $this->columns;
}
public function columnFormats(): array
{
return [
'A' => NumberFormat::FORMAT_TEXT,
'B' => NumberFormat::FORMAT_TEXT,
'C' => NumberFormat::FORMAT_TEXT
];
}
public function map($row): array
{
$mapping = [];
foreach($row as $key => $value) {
if(is_bool($value)) {
if($value) {
$mapping[$key] = "Yes";
} else {
$mapping[$key] = "No";
}
}else{
$mapping[$key] = $value;
}
}
return $mapping;
}
//.......
}
I assume that the problem comes from using FromQuery, but I can't use FromCollection because I run out of memory since the export is so big. I need the built in chunking that FromQuery uses. Is there a way I can queue an export using FromQuery?
You probably don't need to be setting a member variable $this->validations to that builder. That is what is ending up trying to be serialized. If you are just going to return it you don't need to store a copy on the class.

Laravel model observer repository injection

I'm trying to wrap my head around how to inject Laravel's model observers with repositories.
Currently, I have this setup.
UserPetServiceProvider.php
<?php namespace Bunny\Providers;
use Illuminate\Support\ServiceProvider;
use Bunny\Observers\Pet\UserPetObserver;
class UserPetServiceProvider extends ServiceProvider {
public function register()
{
// User pets
$this->app->bind('Bunny\Repos\Pet\IUserPetRepo', 'Bunny\Repos\Pet\UserPetRepo');
// User pet layers
$this->app->bind('Bunny\Repos\Pet\IUserPetLayerRepo', 'Bunny\Repos\Pet\UserPetLayerRepo');
// User pet markings
$this->app->bind('Bunny\Repos\Pet\IUserPetMarkingRepo', 'Bunny\Repos\Pet\UserPetMarkingRepo');
$this->app->events->subscribe(new UserPetObserver());
}
}
It binds all the interfaces and repositories fine and would with the observer, but I need repository injection which I do in the constructor. Nothing is being passed in the constructor so it would explain why it fails.
UserPetRepo.php
<?php namespace Bunny\Repos\Pet;
use Bunny\Repos\BaseRepo;
use Bunny\Models\Pet\UserPet;
use Bunny\Repos\User\IUserRepo;
use Bunny\Repos\Breed\IStageRepo;
use Bunny\Repos\Breed\IBreedLayerRepo;
use Illuminate\Config\Repository as Config;
use Illuminate\Support\Str as String;
use Illuminate\Session\SessionManager as Session;
use Illuminate\Events\Dispatcher;
class UserPetRepo extends BaseRepo implements IUserPetRepo {
public function __construct(UserPet $pet, IUserRepo $user, IStageRepo $stage, IBreedLayerRepo $layer, Config $config, String $string, Session $session, Dispatcher $events)
{
$this->model = $pet;
$this->user = $user;
$this->stage = $stage;
$this->layer = $layer;
$this->config = $config;
$this->string = $string;
$this->session = $session;
$this->events = $events;
$this->directory = $this->config->get('pathurl.userpets');
$this->url = $this->config->get('pathurl.userpets_url');
}
/**
* Create new user pet
* #param attributes Attributes
*/
public function createWithImage( array $attributes, array $colors, $domMarkings = null, $domMarkingColors = null, $recMarkings = null, $recMarkingColors = null )
{
$this->model->name = $attributes['name'];
$this->model->breed_id = $attributes['breed_id'];
$this->model->user_id = $this->user->getId();
$this->model->stage_id = $this->stage->getBaby()->id;
// Get image
$image = $this->layer->compile(
$attributes['breed_id'],
'baby',
$colors,
$domMarkings,
$domMarkingColors
);
// Write image and set
$file = $this->writeImage( $image );
if( ! $file )
{
return false;
}
$this->model->base_image = $file;
$this->model->image = $file;
if( ! $this->model->save() )
{
return false;
}
$this->events->fire('userpet.create', array($this->model));
return $this->model;
}
/**
* Write image
* #param image Imagick Object
*/
protected function writeImage( $image )
{
$fileName = $this->string->random(50) . '.png';
if( $image->writeImage( $this->directory . $fileName ) )
{
return $fileName;
}
$this->model->errors()->add('writeImage', 'There was an error writing your image. Please contact an administrator');
return false;
}
}
UserPetObserver.php
use Bunny\Repos\Pet\IUserPetLayerRepo;
use Bunny\Repos\Pet\IUserPetMarkingRepo;
use Bunny\Observers\BaseObserver;
class UserPetObserver extends BaseObserver {
public function __construct(IUserPetLayerRepo $layers, IUserPetMarkingRepo $markings)
{
$this->layers = $layers;
$this->markings = $markings;
}
/**
* After create
*/
public function onCreate( $model )
{
$this->layers->user_pet_id = $model->id;
dd(Input::all());
$this->layers->breed_layer_id = $model->id;
}
public function subscribe( $event )
{
$event->listen('userpet.create', 'UserPetObserver#onCreate');
}
}
It throws this as the error:
Argument 1 passed to
Bunny\Observers\Pet\UserPetObserver::__construct() must be an instance
of Bunny\Repos\Pet\IUserPetLayerRepo, none given, called in H:\WD
SmartWare.swstor\HALEY-HP\Source2\bunny-meadows\app\Bunny\Providers\UserPetServiceProvider.php
on line 22 and defined
Which makes sense since I'm not passing anything in the constructor. So I try to pass my repository manually.
$this->app->events->subscribe(new UserPetObserver(new UserPetLayerRepo, new UserPetMarkingRepo));
But then it throws errors of UserPetLayerRepo needing injections... and it just chains on and on. Is there anyway of doing this that I'm just overthinking?
Thanks.
EDIT:::
This is the only thing I could think of doing. This seems like a really ugly/bad way of doing it though:
$this->app->events->subscribe(new UserPetObserver($this->app->make('Bunny\Repos\Pet\UserPetLayerRepo'), $this->app->make('Bunny\Repos\Pet\UserPetMarkingRepo')));
Any other ideas?
Try just:
$this->app->events->subscribe($this->app->make('UserPetObserver'));
When Laravel makes the UserPetObserver object, it will read the type-hinted dependencies in the constructor and automatically make them, as well.

Categories