Laravel: Convert an upload action into a array with foreach - php

I have a dynamic action class that is reusable through the entire app UploadImageAction
It handles single image upload per request:
class UploadImageAction implements UploadImageContract
{
public function handle(Request $request, $imageProperty, $image, $imageDir)
{
if ($request->hasFile($imageProperty)) {
// Handle uploading lf_image
if (!is_null($image) && Storage::exists($image)) {
// Throw exceptions here
Storage::delete($image);
}
// Throw exceptions here
return $request->file($imageProperty)->store($imageDir);
}
}
}
The way I handle the action is by creating a method in service class:
protected function handleAttachments($request, $report)
{
// Handle Attachments
$uploadImageAction = resolve(UploadImageAction::class);
// Handle lf_image action
if($request->hasFile('attachments')) {
$report->attachments = $uploadImageAction->handle($request, 'attachments', $report->attachments, 'incidents');
}
return $report;
}
Then call in within the storeReport():
$report = Report::create($request->validated());
$report = $this->handleAttachments($request, $report); // Called here
What I'm trying to do is make it possible to upload multiple images, I wrote the below code block in the storeReport method in Service class just to test (commented out the handleAttachments()):
$ir_counter = $report->ir_number->prefix ?? 'IR#' . str_pad($report->ir_number, 4, '0', STR_PAD_LEFT);
$attachments = [];
if($request->hasfile('attachments'))
{
foreach($request->file('attachments') as $attachment)
{
$name = time().rand(100, 1000) . '.' . $attachment->extension();
$attachment->move(public_path('app/incident/' . $ir_counter . '/'), $name);
$attachments[] = $name;
}
}
$report->attachments = implode('|', $attachments);
It's working as expected. But how can I reflect the array/loop in the UploadImageAction code? When I try to do so, the DB column of attachments shows: [{}].

Related

PHP: opposite of extends (for parent class)

I'm aware of using extends but i am wondering what's the best practice for doing the opposite:
I'm having a "parent" class called c_film and 2 child classes called c_wikipedia and c_imdb - they need to access the general settings ($aOptions) and functions / error handler from c_film.
here's a simplified version:
$aOptions = array(
"wikidata_id" => "Q63985561"; // the movie is: tenet
"verbose_output" => true,
"logging" => true
);
$o = new c_film( $aOptions );
$aData = $o->load_film(); // scrape wikipedia, scrape imdb, merge data into array
these are the requirements:
c_film has functions for scraping/parsing/error handling for all child classes/logging/misc which can be used from both child classes
c_wikipedia and c_imdb can access options / functions from c_film and trigger errors
here's my current solution (simplified):
class c_film
{
function __construct( $aOptions )
{
$this->aOptions = $aOptions;
}
function load_film()
{
$o = new c_wikipedia( $this );
$this->aWikipedia = $o->get_data();
$o = new c_imdb( $this );
$this->aImdb = $o->get_data();
$aData = $this->get_merged_data();
}
private function get_merged_data()
{
// process data / merge into one array
$aResult = array_merge( $this->aWikipedia, $this->aImdb );
result $aResult;
}
function scrape($url)
{
// scrape data / handle 404 / log errors/ html parsing
// [code here]
return $html;
}
function log($msg, $class, $function)
{
// log to file
}
function error( Throwable $t )
{
// log error into file
}
}
class c_wikipedia
{
function __construct( $oFilm ) // parent class object c_film
{
$this->oFilm = $oFilm;
}
function get_data()
{
try {
// scrape data from wikipedia
$aData = $this->get_data();
$url = $this->get_url_from_wikidata_id();
$html = $oFilm->scrape($url);
} catch(Throwable $t ){
//
$oFilm->error( $t );
}
}
private function get_data()
{
$oFilm = $this->oFilm;
$aOptions = $oFilm->aOptions;
$wikidata_id = $aOptions['wikidata_id'];
$bLog = $oFilm->aOptions['logging'];
$output = $oFilm->aOptions['verbose_output'];
// .. load + parse data
$url = // determine url
$msg = "loading data for " . $wikidata_id;
if($bLog) $oFilm->log($msg, get_class(), __FUNCTION__ ); // log to file including class name and function name
if($output) echo $msg;
$html = $oFilm->scrape($url);
return $aData;
}
}
So - is passing the c_film object to the child classes the best practice or is there a more elegant method?

PHP - ZF2 - render template from string variable

i have problem with rendering template in ZF2, where template is in string in variable. There is simple example:
$template = "<div>easy</div>";
$view = new \Zend\View\Model\ViewModel();
$view->setTemplate($template);
$renderer = new \Zend\View\Renderer\PhpRenderer();
$html = $renderer->render($view);
This code fail on rendering, the renderer think that the template is a path to file. And iam reallz not sure how to tell rendere its a string.
Thx for your time and respond.
You have to extend the PhpRenderer class and override the render method, in such a way that will use the string in the $template as the actual template:
class MyPhpRenderer extends PhpRenderer {
public function render($nameOrModel, $values = null)
{
if ($nameOrModel instanceof Model) {
$model = $nameOrModel;
$nameOrModel = $model->getTemplate();
if (empty($nameOrModel)) {
throw new Exception\DomainException(sprintf(
'%s: received View Model argument, but template is empty',
__METHOD__
));
}
$options = $model->getOptions();
foreach ($options as $setting => $value) {
$method = 'set' . $setting;
if (method_exists($this, $method)) {
$this->$method($value);
}
unset($method, $setting, $value);
}
unset($options);
// Give view model awareness via ViewModel helper
$helper = $this->plugin('view_model');
$helper->setCurrent($model);
$values = $model->getVariables();
unset($model);
}
// find the script file name using the parent private method
$this->addTemplate($nameOrModel);
unset($nameOrModel); // remove $name from local scope
$this->__varsCache[] = $this->vars();
if (null !== $values) {
$this->setVars($values);
}
unset($values);
// extract all assigned vars (pre-escaped), but not 'this'.
// assigns to a double-underscored variable, to prevent naming collisions
$__vars = $this->vars()->getArrayCopy();
if (array_key_exists('this', $__vars)) {
unset($__vars['this']);
}
extract($__vars);
unset($__vars); // remove $__vars from local scope
while ($this->__template = array_pop($this->__templates)) {
$this->__file = $this->resolver($this->__template);
try {
if (!$this->__file) {
$this->__content = $this->__template; // this line does what you need
}else{
ob_start();
$includeReturn = include $this->__file;
$this->__content = ob_get_clean();
}
} catch (\Exception $ex) {
ob_end_clean();
throw $ex;
}
if ($includeReturn === false && empty($this->__content)) {
throw new Exception\UnexpectedValueException(sprintf(
'%s: Unable to render template "%s"; file include failed',
__METHOD__,
$this->__file
));
}
}
$this->setVars(array_pop($this->__varsCache));
if ($this->__filterChain instanceof FilterChain) {
return $this->__filterChain->filter($this->__content); // filter output
}
return $this->__content;
}
}
and then you code should look like:
$template = "<div>easy</div>";
$view = new \Zend\View\Model\ViewModel();
$view->setTemplate($template);
$renderer = new MyPhpRenderer();
$html = $renderer->render($view);
Try by replacing '\' with _ underscore as Zend_View_Renderer_PhpRenderer

How to generate a custom CSV export?

I have a page called EventPage that I am managing via a Model admin. Also using Catalog manager: https://github.com/littlegiant/silverstripe-catalogmanager
Question is I need to be able to export all the expired events (And all of the fields).
I have an 'EndDate' => 'Date', field on the EventPage.
So I want to only show EventPages in my CSV export where the EndDate is GreaterThanOrEqual to todays date e.g Expired.
The following generates an CSV export button, but currently it is exporting all the fields, where as I want to filter it so we only show the expired events.
How do I go about this?
<?php
class EventAdmin extends CatalogPageAdmin {
public $showImportForm = false;
private static $managed_models = array(
'EventPage',
'EventCategory',
'EventSubmission',
);
private static $url_segment = 'events';
private static $menu_title = 'Events';
public function getEditForm($id = null, $fields = null) {
$form = parent::getEditForm($id, $fields);
$gridFieldName = 'EventPage';
$gridField = $form->Fields()->fieldByName($gridFieldName);
if ($gridField) {
$gridField->getConfig()->addComponent(new GridFieldExportButton());
}
return $form;
}
}
We can create a custom export button to filter the list of items before exporting them.
First we create a GridFieldExportExpiredEventsButton which extends GridFieldExportButton. This is a complete copy of the current SilverStripe 3.5 generateExportFileData function but with an added filterByCallback on the $items list to filter items that have an EndDate < date('Y-m-d').
class GridFieldExportExpiredEventsButton extends GridFieldExportButton {
public function getHTMLFragments($gridField) {
$button = new GridField_FormAction(
$gridField,
'export',
'Export expired events',
'export',
null
);
$button->setAttribute('data-icon', 'download-csv');
$button->addExtraClass('no-ajax action_export');
$button->setForm($gridField->getForm());
return array(
$this->targetFragment => '<p class="grid-csv-button">' . $button->Field() . '</p>',
);
}
public function generateExportFileData($gridField) {
$separator = $this->csvSeparator;
$csvColumns = $this->getExportColumnsForGridField($gridField);
$fileData = '';
$member = Member::currentUser();
if($this->csvHasHeader) {
$headers = array();
// determine the CSV headers. If a field is callable (e.g. anonymous function) then use the
// source name as the header instead
foreach($csvColumns as $columnSource => $columnHeader) {
$headers[] = (!is_string($columnHeader) && is_callable($columnHeader)) ? $columnSource : $columnHeader;
}
$fileData .= "\"" . implode("\"{$separator}\"", array_values($headers)) . "\"";
$fileData .= "\n";
}
//Remove GridFieldPaginator as we're going to export the entire list.
$gridField->getConfig()->removeComponentsByType('GridFieldPaginator');
$items = $gridField->getManipulatedList();
$items = $items->filterByCallback(function($item) {
// The following line modifies what items are filtered. Change this to change what items are filtered
return $item->EndDate < date('Y-m-d');
});
// #todo should GridFieldComponents change behaviour based on whether others are available in the config?
foreach($gridField->getConfig()->getComponents() as $component){
if($component instanceof GridFieldFilterHeader || $component instanceof GridFieldSortableHeader) {
$items = $component->getManipulatedData($gridField, $items);
}
}
foreach($items->limit(null) as $item) {
if(!$item->hasMethod('canView') || $item->canView($member)) {
$columnData = array();
foreach($csvColumns as $columnSource => $columnHeader) {
if(!is_string($columnHeader) && is_callable($columnHeader)) {
if($item->hasMethod($columnSource)) {
$relObj = $item->{$columnSource}();
} else {
$relObj = $item->relObject($columnSource);
}
$value = $columnHeader($relObj);
} else {
$value = $gridField->getDataFieldValue($item, $columnSource);
if($value === null) {
$value = $gridField->getDataFieldValue($item, $columnHeader);
}
}
$value = str_replace(array("\r", "\n"), "\n", $value);
$columnData[] = '"' . str_replace('"', '""', $value) . '"';
}
$fileData .= implode($separator, $columnData);
$fileData .= "\n";
}
if($item->hasMethod('destroy')) {
$item->destroy();
}
}
return $fileData;
}
}
The extra line that we have added that filters the export items is:
return $item->EndDate < date('Y-m-d');
Alter this to alter the list of items that are exported. I have set this to only return items which have an EndDate that is in the past. Change this as you need.
We then add this export button to our grid field in our event model admin:
class EventAdmin extends CatalogPageAdmin {
private static $managed_models = array(
'EventPage'
);
public function getEditForm($id = null, $fields = null) {
$form = parent::getEditForm($id);
if ($this->modelClass == 'EventPage') {
$gridField = $form->Fields()->fieldByName($this->modelClass);
$gridField->getConfig()->removeComponentsByType('GridFieldExportButton');
$gridField->getConfig()->addComponent(new GridFieldExportExpiredEventsButton('buttons-before-left'));
}
return $form;
}
}
This was originally two answers...
To limit the fields
Have you had a look at the GridFieldExportButton class ?
The constructor
/**
* #param string $targetFragment The HTML fragment to write the button into
* #param array $exportColumns The columns to include in the export
*/
public function __construct($targetFragment = "after", $exportColumns = null) {
$this->targetFragment = $targetFragment;
$this->exportColumns = $exportColumns;
}
So you should be able to pass $exportColumns as an argument.
in your code that would be
if ($gridField) {
$gridField->getConfig()->addComponent(new GridFieldExportButton("after", ["field1", "field2"]));
}
OR - EVEN BETTER SOLUTION
you can define your summary fields on EventPage as such
private static $summary_fields = ["FIeld1", "Field2"];
Then make sure your flush and it should use that as fields.
To filter which items to export in your CSV
So in this case, I think you should create a new class that extends GridFieldExportButton (maybe called EventPageCSVExportButton or something) and override the methods you want. In your case it would probably be generateExportFileData(), just do a check in the loop and exclude data you don't want.
Then use that new class in your EventAdmin.
Have you had a look at the GridFieldExportButton class ?
The constructor
/**
* #param string $targetFragment The HTML fragment to write the button into
* #param array $exportColumns The columns to include in the export
*/
public function __construct($targetFragment = "after", $exportColumns = null) {
$this->targetFragment = $targetFragment;
$this->exportColumns = $exportColumns;
}
So you should be able to pass $exportColumns as an argument.
in your code that would be
if ($gridField) {
$gridField->getConfig()->addComponent(new GridFieldExportButton("after", ["field1", "field2"]));
}
OR - EVEN BETTER SOLUTION
you can define your summary fields on EventPage as such
private static $summary_fields = ["FIeld1", "Field2"];
Then make sure your flush and it should use that as fields.

Logging SOAP envelope in third party library

I am attempting to add logging for the envelope generated by a third party library. I am modifying the updateMetadataField() method below.
I am creating $client like so:
$client = new UpdateClient($UPDATE_END_POINT, $USER_AUTH_ARRAY);
I have tried both $this->client->__getLastRequest() and $this->__getLastRequest() with the same error as a result.
When the SoapClient is instantiated trace is set to true.
Error is
Fatal error: Call to undefined method UpdateClient::__getLastRequest()
So how do I correctly access the __getLastRequest() method?
$USER_AUTH_ARRAY = array(
'login'=>"foo",
'password'=>"bar",
'exceptions'=>0,
'trace'=>true,
'features' => SOAP_SINGLE_ELEMENT_ARRAYS
);
class UpdateClient {
private $client;
public function __construct($endpoint, $auth_array) {
$this->client = new SoapClient($endpoint, $auth_array);
}
public function updateMetadataField($uuid, $key, $value) {
$result = $this->client->updateMetadataField(array(
'assetUuid' => $uuid,
'key' => $key,
'value' => $value)
);
if(is_soap_fault($result)) {
return $result;
}
return $result->return . "\n\n" . $this->client->__getLastRequest();
} // updateMetadataField()
} // UpdateClient
UPDATE - adding calling code This code iterates over an array which maps our data to the remote fields.
What I am hoping to do is begin storing the envelope we send to aid in debugging.
$client = new UpdateClient($UPDATE_END_POINT, $USER_AUTH_ARRAY);
foreach ($widen_to_nool_meta_map as $widen => $nool) { // array defined in widen.php
if ($nool != '') {
// handle exceptions
if ($nool == 'asset_created') { // validate as date - note that Widen pulls exif data so we don't need to pass this
if (!strtotime($sa->$nool)) {
continue;
}
} else if ($nool == 'people_in_photo' || $nool == 'allow_sublicensing' || $nool == 'allowed_use_pr_gallery') {
// we store as 0/1 but GUI at Widen wants Yes/No
$sa->$nool = ($sa->$nool == '1') ? 'Yes' : 'No';
} else if ($nool == 'credit_requirements') {
$sa->$nool = $sa->credit_requirements()->label;
}
$result = $client->updateMetadataField($sa->widen_id, $widen, $sa->$nool);
if(is_soap_fault($result)) {
$sync_result = $sync_result . "\n" . $result->getMessage();
} else {
$sync_result = $sync_result . "\n" . print_r($result, 1);
}
} // nool field set
} // foreach mapped field
If you want to access UpdateClient::__getLastRequest() you have to expose that method on the UpdateClient class since the $client is a private variable. The correct way of calling it is $this->client->__getLastRequest().
Take a look at this working example, as you can see I'm consuming a free web service for testing purposes.
<?php
$USER_AUTH_ARRAY = array(
'exceptions'=>0,
'trace'=>true,
'features' => SOAP_SINGLE_ELEMENT_ARRAYS
);
class TestClient {
private $client;
public function __construct($endpoint, $auth_array) {
$this->client = new SoapClient($endpoint, $auth_array);
}
public function CelsiusToFahrenheit( $celsius ) {
$result = $this->client->CelsiusToFahrenheit(array(
'Celsius' => $celsius
)
);
if(is_soap_fault($result)) {
return $result;
}
return $result;
}
public function __getLastRequest() {
return $this->client->__getLastRequest();
}
}
try
{
$test = new TestClient( "http://www.w3schools.com/webservices/tempconvert.asmx?wsdl", $USER_AUTH_ARRAY);
echo "<pre>";
var_dump($test->CelsiusToFahrenheit( 0 ));
var_dump($test->__getLastRequest());
var_dump($test->CelsiusToFahrenheit( 20 ));
var_dump($test->__getLastRequest());
echo "</pre>";
}
catch (SoapFault $fault)
{
echo $fault->faultcode;
}
?>

Counting parameters of object constructor (external method)

I write a code that autoload classes, and I encountered a problem which I think is because of weakness implementation/design type. What I want to do is to count default parameters of an object (external).
I can count the number of passed arguments to constructor, but I will need to check that inside object constructor and that method does not help me.
CODE EXAMPLE:
// This is simple
function test($arg1,$arg2,$arg3) {return func_num_args();}
// How can I count like this?
class load
{
public function __construct($id="",$path="") {}
}
$l = new load();
// How to count object default parameters count(object($l)), I need answer to be 2`
MY CODE WHERE I NEED TO USE THIS METHOD:
[File: global_cfg.php]
<?php
// File: global_cfg.php
define(ROOT, __DIR__); // Root directory
define(DEBUG, true); // Set debugging state ON or OFF
define(MODE, "producer"); // If debug mode is ON: producer, publisher, tester
/*
* PATH CONFIGURATIONS:
*/
define(DS, "/");
define(LIB, "library");
/*
* SIGN AUTOLOAD CLASSES:
* Setting class sign to true value, the autoloader will create automatically
* an instance of the class lowercase type.
*/
$signClasses = Array
(
"Ralor" => false,
"NaNExist" => true,
"Message" => array(MODE),
"Debug" => DEBUG,
"Resource" => true,
"View" => true
);
[File: autoload_classes.php]
<?php
// File: autoload_classes.php
require_once("global_cfg.php");
print "<b>Loaded classes:</b> <br>";
function __autoloadClasses($list, $suffix="class", $extension="php")
{
$path="";
foreach($list as $fileName => $classInstance)
{
$path = ROOT.DS.LIB.DS.$fileName.".".$suffix.".".$extension;
if(!file_exists($path))
{
print "Signed class ".$fileName." does not exist!<br>";
continue;
}
require_once($path);
print $path;
if($classInstance)
{
$GLOBALS[strtolower($fileName)] = new $fileName();
// ??? todo: counting default object parameters
$count = count(get_object_vars($GLOBALS[strtolower($fileName)]));
if(is_array($classInstance))
{
if($count<count($classInstance))
{
print "Arguments passed to object exceeds the limit";
}
else if($count>count($classInstance))
{
print "Insuficient arguments passed to the object!";
}
else
{
// todo: create object and pass parameters
$GLOBALS[strtolower($fileName)] = new $fileName(/*$arg1 .. $argn*/);
}
}
print $count." -> Class was instantiated!<br>";
continue;
}
print "<br>";
}
}__autoloadClasses($signClasses);
After this problem I can finish my bootstrap.
You can use ReflectionFunctionAbstract::getNumberOfParameters. For example.
class load
{
public function __construct($id = "", $path = "")
{
}
}
function getNumberOfParameters($class_name)
{
$class_reflection = new ReflectionClass($class_name);
$constructor = $class_reflection->getConstructor();
if ($constructor === null)
return 0;
else
return $constructor->getNumberOfParameters();
}
var_dump(getNumberOfParameters('load'));

Categories