How to pass form data to event subscriber on submit - php

I need to get the chosen brand for the MetaSubscriber getUrl callback. This function generates a URL like /brand-model based on the brand that gets passed to the function and the baseFieldName that's chosen in the metasubscriber (in this case being model).
This currently gives me the previous brand on updating and null on creating a new entry.
My question
Is there any way I can pass the submitted Brand to the getUrl function to generate my URLs?
My code
This is the MetaSubscriber in my Formbuilder, which calls the getUrl function to generate a URL for the added entry.
$builder->addEventSubscriber(
new AddMetaSubscriber(
'Glasses',
'GlassesDetail',
GlassesRepository::class,
'getUrl',
[
'getData.getId',
'getData.getBrand' //Need to get the submitted brand here
//I've tried with `brand` like it works for the model but I get an "Undefined method" error
],
'model'
)
);
Which gets the URL from the following function:
public function getUrl(string $url, int $id = null, string $brand = null): string
{
$query = $this
->createQueryBuilder('i')
->select('COUNT(i)')
->innerJoin('i.meta', 'm')
->where('m.url = :url')
->setParameter('url', $url);
if ($id !== null) {
$query
->andWhere('i.id != :id')
->setParameter('id', $id);
}
$url = strtolower($brand)."-".$url;
if ((int)$query->getQuery()->getSingleScalarResult() === 0) {
return $url;
}
return $this->getUrl(Model::addNumber($url), $id, $brand);
}
AddMetaSubscriber class
<?php
namespace Backend\Form\EventListener;
use Backend\Core\Engine\Model;
use Backend\Form\Type\MetaType;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Use this class to add meta url generating to your symfony form.
*
* Example: $builder->addEventSubscriber(new AddMetaSubscriber(...));
*/
class AddMetaSubscriber implements EventSubscriberInterface
{
/**
* #var string - The URL shown in the backend will need this "action" to be generated.
*/
private $actionForUrl;
/**
* #var string - The field in the form where the URL should be generated for.
*/
private $baseFieldName;
/**
* #var string - Name of the class or the container service id, f.e.: 'moduleForUrl.repository.item',
*/
private $generateUrlCallbackClass;
/**
* #var string - Name of the public method which returns you the URL, f.e.: "getUrl"
*/
private $generateUrlCallbackMethod;
/**
* #var array - Extra parameters you want to include in the AJAX call to get the URL, f.e.: ["getData.getLocale", "getForm.getParent.getParent.getData.getMenuEntityId"]
*/
private $generateUrlCallbackParameterMethods;
/**
* #var string - The URL shown in the backend will need this "module" to be generated.
*/
private $moduleForUrl;
public function __construct(
string $moduleForUrl,
string $actionForUrl,
string $generateUrlCallbackClass,
string $generateUrlCallbackMethod,
array $generateUrlCallbackParameterMethods,
string $baseFieldName = 'title'
) {
$this->moduleForUrl = $moduleForUrl;
$this->actionForUrl = $actionForUrl;
$this->generateUrlCallbackClass = $generateUrlCallbackClass;
$this->generateUrlCallbackMethod = $generateUrlCallbackMethod;
$this->generateUrlCallbackParameterMethods = $generateUrlCallbackParameterMethods;
$this->baseFieldName = $baseFieldName;
}
public static function getSubscribedEvents(): array
{
// Tells the dispatcher that you want to listen on the form.pre_set_data
// event and that the preSetData method should be called.
return array(FormEvents::PRE_SET_DATA => 'preSetData');
}
public function preSetData(FormEvent $event)
{
$event->getForm()->add(
'meta',
MetaType::class,
[
'base_field_name' => $this->baseFieldName,
'detail_url' => Model::getUrlForBlock($this->moduleForUrl, $this->actionForUrl),
'generate_url_callback_class' => $this->generateUrlCallbackClass,
'generate_url_callback_method' => $this->generateUrlCallbackMethod,
'generate_url_callback_parameters' => $this->buildCallbackParameters($event),
]
);
}
private function buildCallbackParameters(FormEvent $event): array
{
$parameters = [];
foreach ($this->generateUrlCallbackParameterMethods as $generateUrlCallbackParameterMethod) {
$parameter = $event;
$methods = explode('.', $generateUrlCallbackParameterMethod);
foreach ($methods as $method) {
$parameter = $parameter->{$method}();
}
$parameters[] = $parameter;
}
return $parameters;
}
}

Related

bavix/laravel-wallet new updated function to get transfers data from database in laravel

I have created a tiktok like app which retrieve data from mysql database using api from laravel, first i was using bavix/laravel-wallet package version 5.3.2. to create a virtual wallet for gift system in the app. And i have a function which used to send gift to another user (coins) and that user receive gifts in his wallet, i display a list of the Items which contain a number and value of the gifts he received from other users so he can redeem them later.
below is the class which was used to retrieve list of the gift of the user to and display on his wallet ready to be redeemed..
<?php
namespace App\Http\Resources;
use App\Models\Item as ItemModel;
use Bavix\Wallet\Models\Transfer;
use Illuminate\Http\Resources\Json\JsonResource;
class Gift extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
static $currency;
if (empty($currency)) {
$code = setting('payment_currency', config('fixtures.payment_currency'));
$symbol = $code;
$currency = $symbol !== $code ? $symbol : $code;
}
$data = [];
/** #var \App\Models\User $this */
/** #var ItemModel $item */
foreach (ItemModel::get() as $item) {
$transfers = $this->transfers()
->where('to_type', $item->getMorphClass())
->where('to_id', $item->getKey())
->where('status', Transfer::STATUS_PAID)
->count();
$data[] = [
'item' => Item::make($item),
'balance' => $transfers,
'value' => sprintf('%s%.2f', $currency, ($item->value * $transfers) / 100),
];
}
return $data;
}
}
The above class was getting a list of all the gifts which was received by the current user in the transfers table and 'to_type' column was returning the Item modal(App/Models/Item) and i was get the result correctly.
But the question here is when i update my laravel to verion 9 and bavix/laravel-wallet to version 9.0 the transfers variable return 0 data from the database because 'to_type' column in database change and does not return Item modal anymore otherwise it returning the Wallet Model and I have no idea how to change my codes to get the same result as from the previous versions.
I read the package documentation but i did not get any luck.
Below is the Item Model class ...
<?php
namespace App\Models;
use Bavix\Wallet\Interfaces\Customer;
use Bavix\Wallet\Interfaces\ProductInterface;
use Bavix\Wallet\Traits\HasWallet;
use Illuminate\Database\Eloquent\Model;
use Spatie\Activitylog\LogOptions;
use Spatie\Activitylog\Traits\LogsActivity;
class Item extends Model implements ProductInterface
{
use HasWallet, LogsActivity;
// protected static $logFillable = true;
// protected static $logOnlyDirty = true;
protected $fillable = [
'name', 'image', 'price', 'value', 'minimum',
];
/**
* #param Customer $customer
* #param int $quantity
* #param bool $force
*
* #return bool
*/
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults()
->logFillable()
->logOnlyDirty();
}
public function canBuy(Customer $customer, int $quantity = 1, bool $force = null): bool
{
return true;
}
public function getAmountProduct(Customer $customer): int|string
{
return $this->price;
}
public function getDescriptionForEvent(string $event): string
{
return sprintf('Item "%s" was %s.', $this->name, $event);
}
/**
* #return array
*/
public function getMetaProduct(): ?array
{
return ['title' => $this->name];
}
/**
* #return string
*/
public function getUniqueId(): string
{
return (string)$this->getKey();
}
}
Below is the item Db...
Below is the Transfer table
Below is the Wallet Table
Anyone who can help or get any direction on where to read so i can understand how to accomplish that..

setFetchMode() seems not to be working and not returning an object?

I'm currently working with one of my friends on making a portfolio for all of his projects and as strangely as it seems, I cannot manage to make setFetchMode(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, 'Class Namespace') working on his code while it is working on my projects.
Here is the error returned by PHP :
!(https://cdn.discordapp.com/attachments/525023910698156034/583938068311179265/unknown.png)
Here is the class Entity which contains the __construct() function and the hydration() function :
<?php
namespace App\Entity;
class Entity {
public function __construct(array $array) {
$this->hydrate($array);
}
public function hydrate($array) {
foreach ($array as $key => $value) {
$setter = 'set' . ucfirst($key);
if (method_exists($this, $setter)) {
$this->$setter($value);
}
}
}
}
Then, here is the Project Class which implements the class Entity :
<?php
namespace App\Entity;
class Project extends Entity implements \JsonSerializable {
private $_title;
private $_description;
private $_imagePath;
private $_link;
private $_repoLink;
private $_creationDate;
public function jsonSerialize() {
return [
'title' => $this->_title,
'description' => $this->_description,
'imagePath' => $this->_imagePath,
'link' => $this->_link,
'repoLink' => $this->_repoLink,
'creationDate' => $this->_creationDate,
];
}
/
* #return string
*/
public function getTitle(): string {
return $this->_title;
}
/
* #param string $title
*/
public function setTitle(string $title) {
$this->_title = $title;
}
/
* #return string
*/
public function getDescription(): string {
return $this->_description;
}
/
* #param string $description
*/
public function setDescription(string $description) {
$this->_description = $description;
}
/
* #return string
*/
public function getImagePath(): string {
return $this->_imagePath;
}
/
* #param string $imagePath
*/
public function setImagePath(string $imagePath) {
$this->_imagePath = $imagePath;
}
/
* #return string
*/
public function getLink(): string {
return $this->_link;
}
/
* #param string $link
*/
public function setLink(string $link) {
$this->_link = $link;
}
/
* #return string
*/
public function getRepoLink(): string {
return $this->_repoLink;
}
/
* #param string $repoLink
*/
public function setRepoLink(string $repoLink) {
$this->_repoLink = $repoLink;
}
/
* #return \DateTime
*/
public function getCreationDate(): \DateTime {
return $this->_creationDate;
}
/
* #param string $creationDate
*/
public function setCreationDate(string $creationDate) {
$this->_creationDate = new \DateTime($creationDate);
}
}
And finally, here is the SQL request :
<?php
namespace App\Model;
class ProjectManager extends Manager {
/**
* return a collection of Project objects
* #return Project[]
* #throws \Exception
*/
public function getProjects() {
$db = $this->getDb();
$q = $db->query(
'SELECT id,
title,
description,
image_path AS imagePath,
link,
repo_link AS repoLink,
creation_date AS creationDate
FROM my_website_projects
ORDER BY creationDate'
);
$q->setFetchMode(\PDO::FETCH_CLASS | \PDO::FETCH_PROPS_LATE, 'App\Entity\Project');
$projects = $q->fetchAll();
return $projects;
}
}
The only thing that seems to work is to add PDO::FETCH_ASSOC in the fetchAll() but then it doesn't return an object but an array....
Your help would be much appreciated on this problem ! :)
As far as I know, there is no solution to this problem. There is no fetch mode that creates an object passing the returned row as a constructor parameter.
So I would change the code to this a bit clumsy but working solution
public function getProjects() {
$db = $this->getDb();
$q = $db->query(
'SELECT id,
title,
description,
image_path AS imagePath,
link,
repo_link AS repoLink,
creation_date AS creationDate
FROM my_website_projects
ORDER BY creationDate'
);
$projects = [];
while($row = $q->fetch(PDO::FETCH_ASSOC)) {
$projects[] = new App\Entity\Project($row);
}
return $projects;
}
Your mistake is that you slightly confused the way PDO creates objects.
This way is rather blunt, PDO just takes an object and fills it properties, the process is not much different from filling an associative array.
So now you can tell how did your other code work:
first, the constructor parameter in your other class is optional, it means PHP won't complain for it.
second, in your other class properties are spelled equal to column names in the database, and PDO happily fills them as described above.
So, as another solution you can fix these 2 issues: make the constructor parameter optional and remove the underscore from the property names.
Some time ago I wrote an article on fetching obejects with PDO, you may find it useful.

ReflectionMethod's get type is retuning an empty object

So, I am trying the get the types of the methods, to instantiate the classes, for example:
I have a class called mycontroller and a simple method called page which has a class Type hint, for example:
class MyController
{
public function page(AnotherClass $class)
{
$class->intro;
}
}
I also have another class, litterly called anotherclass (very original, I know)
class AnotherClass
{
public $intro = "Hello";
}
Okay, so that's the basics, now I am trying to get the type of MYControllers method arguments page: anotherclass
You can see the logic of my code below:
Class Route
{
/**
* Method paramaters
*
* #var array
*/
protected $params;
/**
* The class and method
*
* #var array
*/
protected $action;
/**
* Get the paramaters of a callable function
*
* #return void
*/
public function getParams()
{
$this->params = (new ReflectionMethod($this->action[0], $this->action[1]))->getParameters();
}
/**
* Seperate the class and method
*
* #param [type] $action
* #return void
*/
public function getClassAndMethod($action = null)
{
$this->action = explode("#", $action);
}
/**
* A get request
*
* #param string $route
* #return self
*/
public function get($route = null)
{
if(is_null($route)) {
throw new Exception("the [$route] must be defined");
}
return $this;
}
public function uses($action = null)
{
if(is_null($action)){
throw new Exception("the [$action] must be set");
}
if(is_callable($action)){
return call_user_func($action);
}
// Get the action
$this->getClassAndMethod($action);
// Get the params of the method
$this->getParams();
foreach ($this->params as $param) {
print_R($param->getType());
}
// var_dump($action[0]);
}
}
Which is simply being called like so:
echo (new Route)->get('hello')->uses('MyController#page');
So, what the above does, is it splits the uses method paramater via the # sign, the [0] will be the class and the [1] will be the class's method, then I am simply ReflectionMethod to get the parameters of said method, and then I am trying to get the parameter type, which, is what I am stuck on, because it just keeps returning an empty object:
ReflectionNamedType Object { )
So, my question is, why is it returning an empty object, and how can I get the type of the parameter?
You have to echo instead of print_r :
foreach ($this->params as $param) {
echo $param->getType() ; //AnotherClass
}
Because ReflectionType use __toString() to display it.
Or
foreach ($this->params as $param) {
print_r($param->getClass()) ; //AnotherClass
}

Using one class's properties in another OOP PHP

I have the following class
namespace PG\Referrer\Single\Post;
class Referrer implements ReferrerInterface
{
/**
* #var $authorReferrer = null
*/
protected $isAuthorReferrer = null;
/**
* #var $dateReferrer = null
*/
protected $isDateReferrer = null;
/**
* #var $searchReferrer = null
*/
protected $isSearchReferrer = null;
/**
* #var $taxReferrer = null
*/
protected $isTaxReferrer = null;
/**
* #param array $values = null;
*/
public function __construct(array $values = null)
{
if ($values)
$this->setBulk($values);
}
/**
* Bulk setter Let you set the variables via array or object
*/
public function setBulk($values)
{
if (!is_array($values) && !$values instanceof \stdClass) {
throw new \InvalidArgumentException(
sprintf(
'%s needs either an array, or an instance of \\stdClass to be passed, instead saw %s',
__METHOD__,
is_object($values) ? get_class($values) : gettype($values)
)
);
}
foreach ($values as $name => $value) {//create setter from $name
global $wp_query;
if (array_key_exists($value, $wp_query->query_vars)) { //Check that user don't set a reserved query vars
throw new \InvalidArgumentException(
sprintf(
'%s is a reserved query_vars and cannot be used. Please use a unique value',
$value
)
);
}
$setter = 'set' . $name;
$condition = isset($_GET[$value]);
if ($setter !== 'setBulk' && method_exists($this, $setter)) {
$this->{$setter}($condition);//set value (bool)
}
}
return $this;
}
/**
* #param bool $authorReferrer
* #return $this
*/
public function setAuthorReferrer($isAuthorReferrer)
{
$this->isAuthorReferrer = $isAuthorReferrer;
return $this;
}
/**
* #param bool $dateReferrer
* #return $this
*/
public function setDateReferrer($isDateReferrer)
{
$this->isDateReferrer = $isDateReferrer;
return $this;
}
/**
* #param bool $searchReferrer
* #return $this
*/
public function isSearchReferrer($isSearchReferrer)
{
$this->isSearchReferrer = $isSearchReferrer;
return $this;
}
/**
* #param bool $taxReferrer
* #return $this
*/
public function setTaxReferrer($isTaxReferrer)
{
$this->isTaxReferrer = $isTaxReferrer;
return $this;
}
}
with its interface
namespace PG\Referrer\Single\Post;
interface ReferrerInterface
{
/**
* #param array $values
* #return $this
*/
public function setBulk($values);
/**
* #param bool $authorReferrer
* #return $this
*/
public function setAuthorReferrer($isAuthorReferrer);
/**
* #param bool $dateReferrer
* #return $this
*/
public function setDateReferrer($isDateReferrer);
/**
* #param bool $searchReferrer
* #return $this
*/
public function isSearchReferrer($isSearchReferrer);
/**
* #param bool $taxReferrer
* #return $this
*/
public function setTaxReferrer($isTaxReferrer);
}
This class sets up 4 conditionals that I need to use in another class. The values that is used in this class is also set from the other class, so basically the user sets values in the other class (lets call it class b) that is then used by class Referrer and returns the 4 conditionals which is then used by class b.
The reason why I'm doing it this way is because there will be two other classes that will need to do the same, but will returns different info
What is the more correct way to achieve this?
EDIT
To clear this up
class Referrer
The properties $isAuthorReferrer, $isDateReferreretc will either have a value of null or a boolean value depending on what is set by the user.
Example:
$q = new Referrer(['authorReferrer' => 'aq']);
In the code above, $isAuthorReferrer is set via the setBulk() method in the class to true when the variable aq is available in the URL or false when not present. The three other properties will return null because they are not set in the example.
The above works as expected, but I need to do this in another class, lets again call it class b. The arguments will be set to class b, and in turn, class b will set this arguments to class Referrer, class Referrer will use this arguments and return the proper values of its properties, and class b will use this results to do something else
Example:
$q = new b(['authorReferrer' => 'aq']);
Where class b could be something like this (it is this part that I'm not sure how to code)
class b implements bInterface
{
protected $w;
protected $other;
public function __construct($args = [])
{
//Do something here
// Do something here so that we can use $other in other classes or functions
}
public function a()
{
$w = new Referrer($args);
}
public function b()
{
// use $w properties here
// return $other for usage in other classes and functions
}
}
The best way is to inject the referrer to your classes in order to do loose coupling between them and the referrer (this pattern use the benefit of your ReferrerInterface):
class b implements bInterface
{
protected $referrer;
public function __construct(ReferrerInterface $referrer, array $values = array())
{
$this->referrer = $referrer;
$this->referrer->setBulk($values);
}
public function getReferrer()
{
return $this->referrer;
}
public function b()
{
// use $this->referrer properties here
}
}
// Instantiation (use your dependency injection if you have one):
$referrer = new Referrer();
$b = new b($referrer, ['authorReferrer' => 'aq']);
I do not understand what is $other so I removed it but explain me if you want me to I add it again.
If you need to use the properties of the referrer in b, you should add some getters in your ReferrerInterface to allow that. I would use setAuthorReferrer($isAuthorReferrer) to set the value and isAuthorReferrer() to get it for instance.

How to change value of field in FOSRestBundle/JMS Serializer?

I have the field "image" in entities. But depends of action I want to show not original image, but image's preview (which I make in LiipImagineBundle). The one solution which I can imagine:
public function cgetAction(Request $request)
{
$events = $this->container->get('gc.event_manager')->getEvents();
foreach ($events as &$event) {
$previewURL = $this->getPreview($event->getPhoto());
$event->setPhoto($previewURL);
}
$event = false;
return array(
'events' => $events,
);
}
But I don't like it, because if an entity has deep children's entities the code will be very confusing.
How to do it correctly?
Common problem when you want to return absolute url through API while using LiipImagine and FOSRest
Change EE\TYSBundle to your own, code taken from https://github.com/EE/TellYourStoryBundle/tree/develop
JMSSerializer Handler Service with injected Cache Manager to get correct prefix
ee_tys.serializer.filename_handler:
class: EE\TYSBundle\Handler\FilenameHandler
arguments:
- "#liip_imagine.cache.manager"
tags:
- { name: 'jms_serializer.handler', type: Filename, format: json}
Handler for custom Filename type
EE\TYSBundle\Handler\FilenameHandler.php
<?php
namespace EE\TYSBundle\Handler;
use EE\TYSBundle\Entity\Filename;
use JMS\Serializer\GraphNavigator;
use JMS\Serializer\Handler\SubscribingHandlerInterface;
use JMS\Serializer\VisitorInterface;
use Liip\ImagineBundle\Imagine\Cache\CacheManager;
class FilenameHandler implements SubscribingHandlerInterface
{
function __construct(CacheManager $manager)
{
$this->manager = $manager;
}
public static function getSubscribingMethods()
{
return array(
array(
'direction' => GraphNavigator::DIRECTION_SERIALIZATION,
'format' => 'json',
'type' => 'Filename',
'method' => 'serializeFilenameToJson',
),
);
}
public function serializeFilenameToJson(VisitorInterface $visitor, Filename $filename, array $type)
{
// `tile` is a name of Imagine filter you want to apply
return $this->manager->getBrowserPath($filename, 'tile', true);
}
}
EE\TYSBundle\Entity\Filename
<?php
namespace EE\TYSBundle\Entity;
/**
* Class Filename
* #package EE\TYSBundle\Entity
*/
class Filename {
/**
* #var string
*/
public $name;
/**
* #var string
*/
public $extension;
public function __construct($filename)
{
$parts = explode(".", $filename);
$this->setName($parts[0]);
$this->setExtension($parts[1]);
}
/**
* #param mixed $extension
* #return Media
*/
public function setExtension($extension)
{
$this->extension = $extension;
return $this;
}
/**
* #return mixed
*/
public function getExtension()
{
return $this->extension;
}
/**
* #param mixed $name
* #return Filename
*/
public function setName($name)
{
$this->name = $name;
}
/**
* #return mixed
*/
public function getName()
{
return $this->name;
}
/**
* #return string filename
*/
public function __toString()
{
return join(".", array($this->name, $this->extension));
}
}
usage of custom Filename type
/**
* #var string|null
*
* #ORM\Column(name="background_filename", type="string", length=255, nullable=true)
*
* #Serializer\Expose
* #Serializer\Type("Filename")
* #Serializer\SerializedName("background_uri")
*/
private $backgroundFilename;
From now on you will get background_uri with absolute url to your preview Image
One possible solution is to define Handler.
Handlers allow you to change the serialization, or deserialization
process for a single type/format combination.
Something like
class ImageHandler
{
public function serialize(VisitorInterface $visitor, \FQCN\Image $image, array $type, Context $context)
{
// do some stuff
return ...;
}
}
And register it in services.yml
serializer.handler.image_handler:
class: FQCN\Handler\ImageHandler
arguments: []
tags:
- { name: "jms_serializer.handler", type: FQCN\AdvertImage, format: json, method: serialize }
If i understand your question well, you need change value for correct before serialisation, i think manual can help #Accessor
class User
{
private $id;
/** #Accessor(getter="getTrimmedName",setter="setName") */
private $name;
// ...
public function getTrimmedName()
{
return trim($this->name);
}
public function setName($name)
{
$this->name = $name;
}
}

Categories