I need to make functionality to validate JSON message, with particular validators, base on specific configuration.
I've got validator and constraints defined as services:
services:
validator.constraint.message.country_code:
class: RenamedBundle\Validator\Constraints\CountryCode
arguments: ...
validator.constraint.message.price_comma:
class: RenamedBundle\Validator\Constraints\PriceComma
arguments: ...
message.validator:
class: RenamedBundle\Service\Validator\MessageValidatorService
arguments: ['#validator']
calls:
- [addConstraint, ['#validator.constraint.message.country_code']]
- [addConstraint, ['#validator.constraint.message.price_comma']]
In DtoValidatorService I call validate()using constraints list.
The probl... challenge is that same JSON message can require validation only with few validators from list, depends on message properties ie. for Poland I want do validate all float values (in Poland separator is ',', not '.'). I've try to do this by config.yml.
renamed:
pritners:
warehouse_wa:
characteristic:
country: 'pl'
source: 'hq-pl'
validators:
- '#validator.constraint.message.country_code'
- '#validator.constraint.message.price_comma'
warehouse_ny:
characteristic:
country: 'us'
source: 'hq-us'
validators:
- '#validator.constraint.message.country_code'
I've added extension:
class Configuration implements ConfigurationInterface
{
/**
* {#inheritDoc}
*
* #throws \RuntimeException
*/
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('renamed');
$rootNode
->children()
->arrayNode('printers')
->useAttributeAsKey('name')
->prototype('array')
->children()
->arrayNode('characteristic')
->children()
->scalarNode('country')->end()
->scalarNode('source')->end()
->end()
->end()
->end()
->children()
->arrayNode('validators')
->prototype('scalar')->end()
->end()
->end()
->end()
->end()
->end();
return $treeBuilder;
}
}
class RenamedExtension extends Extension
{
/**
* {#inheritDoc}
* #throws \Exception
*/
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
$container->setParameter('printers', $config['printers']);
$loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
$loader->load('services.yml');
}
}
This configuration works, but problem is when i pass '%printers%' parameter into service, I recieve list of services names:
Array
(
[0] => #validator.constraint.message.country_code
)
but when I pass constraint I got object.
array(1) {
[0] =>
class RenamedBundle\Validator\Constraints\CountryCode#688 (5) {
...
}
}
I'm in dead end now. How can I parameterized printers configuration and avoid passing inline/hardcode validator class name. Calling services in paremeters section is not allowed. Calling them in configuration provide me additional controll and validation.
Maybe someone got better solution?
edit:
According to #Artur Vesker suggestion, I change extension load method implementation.
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
$printers = [];
foreach ($config['printers'] as $printerName => $printerConfig) {
$constraints = [];
foreach($printerConfig['valdiators'] as $constraintName) {
$constraintName = ltrim($constraintName, '#');
$constraints[] = new Reference($constraintName);
}
$printerConfig['valdiators'] = $constraints;
$printers[$printerName] = $printerConfig;
}
$container->setParameter('printers', $printers);
$loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
$loader->load('services.yml');
}
trying to build cache I got:
Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
You cannot dump a container with parameters that contain references to other services
It looks my approach in not allowed in symfony world ;]
Use Reference
Set just ids in config:
validators:
- 'validator.constraint.message.country_code'
- 'validator.constraint.message.price_comma'
And create parameter with Reference in you Extension
$validators = array_map(function($id) {
return new Reference($id);
}, $config['pritners.warehouse_wa.validators']);
$messageValidatorDefinition = new Definition('RenamedBundle\Service\Validator\MessageValidatorService', [new Reference('validator)]);
foreach ($validators as $validator) {
messageValidatorDefinition->addMethodCall('addConstraint', [$validator])
}
Thanks Artur Vesker, Your solution works for me.
I tried to keep message.validator in services.yml (I want to have PHPStorm hints) and get them in extension by $container->getDefinition('message.validator'), but in this way addMethodCall not working. So anyone that will try this way, it will not work, you must create service dynamillacy as Artur suggest.
My full code:
- same class Configuration implements ConfigurationInterface
- same config.yml
- removed message.validator from services.yml
- in messageValidator I decide to group constraints by printer name
/**
* Class MessageValidatoService
* #package RenamedBundle\Service
*/
class MessageValidatoService
{
/** PrinterConfigurationDto[] */
protected $printersConfig;
/**
* array of constraints for preValidator grouped by printer name ex:
* [
* 'printer_name' => [Constraint1, Constraint2]
* ]
* #var array
*/
protected $constraints;
/**
* MessageValidatorService constructor.
*
* #param array $printersConfiguration
* #param Serializer $serializer
*
* #throws \RuntimeException
*/
public function __construct(array $printersConfiguration, Serializer $serializer)
{
$this->serializer = $serializer;
$this->constraints = [];
$this->printersConfig = [];
foreach ($printersConfiguration as $printerConfig) {
/** #var PrinterConfigurationDto $configDto */
$configDto = $this->serializer->fromArray(
$printerConfig,
'RenamedBundle\Dto\PrinterConfiguration\PrinterConfigurationDto'
);
$this->printersConfig[] = $configDto;
}
}
/**
* #param string $printerName
* #param Constraint $constraint
*
* #return $this
*/
public function addConstraint($printerName, Constraint $constraint)
{
$this->constraints[$printerName][] = $constraint;
return $this;
}
/**
* Return PrinterConfigurationDto, or throw RuntimeException if none configuration fir to ReceiptDto parameters.
*
* #param ReceiptDto $receiptDto
*
* #return PrinterConfigurationDto
*
* #throws \RuntimeException
* #throws \OutOfRangeException
*/
protected function getPrinterConfig($receiptDto)
{
$countryCodes = $receiptDto->getCountryCodes();
if (array_key_exists($receiptDto->getCountryId(), $countryCodes) === false) {
throw new \OutOfRangeException('Missing country code for country id:' . $receiptDto->getCountryId());
}
/** #var PrinterConfigurationDto $printerConfig */
foreach ($this->printersConfig as $printerConfig) {
if ($printerConfig->getSourceApp() === $receiptDto->getSourceApp()
&& $printerConfig->getCountry() === $countryCodes[$receiptDto->getCountryId()]
) {
return $printerConfig;
}
}
throw new \RuntimeException(
'No printer configuration found for app:' . $receiptDto->getSourceApp()
. ', country id: ' . $receiptDto->getCountryId()
);
}
/**
* #param ReceiptDto $receiptDto
*
* #return Constraint[]
*
* #throws \RuntimeException
* #throws \OutOfRangeException
*/
public function getValidatorConstraints(ReceiptDto $receiptDto)
{
$printerConfig = $this->getPrinterConfig($receiptDto);
if (array_key_exists($printerConfig->getName(), $this->constraints) === false) {
return [];
}
return $this->constraints[$printerConfig->getName()];
}
}
/**
* Class RenamedExtension
* #package RenamedBundle\DependencyInjection
*/
class RenamedExtension extends Extension
{
/**
* {#inheritDoc}
* #throws \Exception
* #throws ServiceNotFoundException
* #throws InvalidArgumentException
* #throws BadMethodCallException
*/
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
//set parameters before load services
//rewrite printers configuration from RenamedBundle\Resources\printers.yml
//structure changes must be implemented also in RenamedBundle\Dto\PrinterConfiguration\PrinterConfigurationDto
$printers = [];
foreach ($config['printers'] as $printerName => $printerConfig) {
$printers[] = [
'name' => $printerName,
'country' => $printerConfig['characteristic']['country'],
'source_app' => $printerConfig['characteristic']['source_app'],
];
}
$container->setParameter('printers', $printers);
//load services, add constraints
$loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
$loader->load('services.yml');
$definition = new Definition(PrinterManagerService::class);
$definition->addArgument('%printers%');
$definition->addArgument(new Reference('serializer'));
foreach ($config['validators'] as $printerName => $printerConfig) {
if (array_key_exists('validators', $printerConfig)
&& is_array($printerConfig['validators'])
) {
foreach ($printerConfig['validators'] as $constraintName) {
$constraint = new Reference(ltrim($constraintName, '#'));
$definition->addMethodCall('addConstraint', [$printerName, $constraint]);
}
}
}
$container->setDefinition('message.validator', $definition);
}
}
Related
This is Laravel 8. I'm extending Illuminate\Mail\Transport\Transport class to create a custom mail transport in order to utilize the company's custom mail API with Illuminate\Mail\Mailable. I got most of it working, including file attachments, however the Swift_Mime_SimpleMimeEntity and the classes deriving from it contain getBody(), getFilename(), getSize(), and getContentType() but no methods to extract file extension.
<?php
namespace App\CustomMailDriver;
use GuzzleHttp\ClientInterface;
use Illuminate\Mail\Transport\Transport;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Swift_Mime_SimpleMessage;
class CustomTransport extends Transport
{
/**
* Guzzle client instance.
*
* #var \GuzzleHttp\ClientInterface
*/
protected $client;
/**
* API key.
*
* #var string
*/
protected $key;
/**
* The API URL to which to POST emails.
*
* #var string
*/
protected $url;
/**
* Create a new Custom transport instance.
*
* #param \GuzzleHttp\ClientInterface $client
* #param string|null $url
* #param string $key
* #return void
*/
public function __construct(ClientInterface $client, string $url, string $key)
{
$this->key = $key;
$this->client = $client;
$this->url = $url;
}
/**
* {#inheritdoc}
*/
public function send(Swift_Mime_SimpleMessage $message, &$failedRecipients = null)
{
$this->beforeSendPerformed($message);
$payload = $this->getPayload($message);
try {
// ignore ssl (esp when working in DEV/QA)
$response = Http::withoutVerifying()->withHeaders([
'X-Authorization' => $this->key
])->post($this->url, $payload);
Log::info($response->body());
} catch (\Exception $e) {
Log::error($e->getMessage());
}
$this->sendPerformed($message);
return $this->numberOfRecipients($message);
}
/**
* Get the HTTP payload for sending the message.
*
* #param \Swift_Mime_SimpleMessage $message
* #return array
*/
protected function getPayload(Swift_Mime_SimpleMessage $message): array
{
// to
if (!empty($message->getTo())) {
$payload['payload']['to']['email'] = key($message->getTo());
}
// cc
if (!empty($message->getCc())) {
$payload['payload']['cc']['email'] = key($message->getCc());
}
// bcc
if (!empty($message->getBcc())) {
$payload['payload']['bcc']['email'] = key($message->getBcc());
}
// subject
$payload['payload']['subject'] = $message->getSubject();
// html
$payload['payload']['message']['html'] = $message->getBody();
// message children contains plain text, attachments, etc
$children = $message->getChildren();
if (!empty($children)) {
foreach($children as $child) {
// attachments
if (get_class($child) === 'Swift_Attachment') {
$payload['payload']['attachments'][] = [
'content' => base64_encode($child->getBody()),
'filename' => $child->getFilename(),
];
}
// plain text
if (get_class($child) === 'Swift_MimePart') {
$payload['payload']['message']['text'] = $child->getBody();
}
}
}
return $payload;
}
}
I had to go different route. Instead of searching the extension inside Transport class using Swift_Mime_SimpleMessage, I passed the filename with the original extension to Transport class from Illuminate\Mail\Mailable
public function build()
{
$tempUpload = request()->file('file_attachment');
$filename = $tampUpload->getClientOriginalName() . "." . $tempUpload->getClientOriginalExtension();
return $this->from($this->from_email, $this->from_name)
->subject('subject line')
->attach($tempUpload, ['as' => $filename)
->view('emails.gce.supplier_supplier')
->with($this->data);
}
What I want to archive: I'd like to create a Zammad ticket using the Zammad-api but also parse markdown.
To do so, I created a custom channel to send a notification to the Zammad Helpdesk system using Zammad's API.
This is the specific class:
<?php
namespace App\Channels;
use Illuminate\Mail\Mailable;
class ZammadMessage extends Mailable
{
/**
* The issuer of the ticket.
*
* #var string
*/
public $from;
/**
* The text content of the message.
*
* #var string
*/
private $content;
public function __construct($from, $content = '')
{
$this->from = $from;
$this->content = $content;
}
public static function create($from = '', $content = '')
{
return new static($from, $content);
}
/**
* Set the text content of the message.
*
* #param $content
*
* #return $this
*/
public function content($content)
{
$this->content = $content;
return $this;
}
public function asMarkdown()
{
$this->build();
$this->body = $this->buildView();
return $this;
}
public function build()
{
return $this->from($this->from)
->markdown('emails.contact.submitted', ['data' => $this->content]);
}
/**
* Set the issuer of the ticket.
*
* #param $address
* #param string $name
*
* #return $this
*/
public function from($address, $name = 'null'): static
{
$this->from = $address;
return $this;
}
}
Using this class by my notification class
public function toTicket($notifiable)
{
$address = $notifiable instanceof AnonymousNotifiable
? collect($notifiable->routeNotificationFor('zammad'))->first()
: $notifiable->email;
return ZammadMessage::create()
->from($address)
->content($this->content)
->asMarkdown();
}
I am getting this error:
PHP Deprecated: Passing an $environment into the "League/CommonMark/CommonMarkConverter" constructor is deprecated in 1.6 and will not be supported in 2.0; use MarkdownConverter instead. See https://commonmark.thephpleague.com/2.0/upgrading/consumers/#commonmarkconverter-and-githubflavoredmarkdownconverter-constructors for more details. in /var/www/html/vendor/league/commonmark/src/CommonMarkConverter.php on line 43
That E_USER_DEPRECATED error is not stopping code execution so you should be able to ignore it. Double-check your error_reporting setting in php.ini and/or any similar settings in your framework and adjust as needed.
I have an entity with one TEXT (MySQL) attributes
<?php
namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\Mapping\Table;
use Doctrine\ORM\Mapping\Index;
use ApiPlatform\Core\Annotation\ApiProperty;
/**
* #ApiResource(
* attributes={},
* collectionOperations={
* "get"={},
* "post"={
* "access_control"="is_granted('ROLE_COMPANY')"
* },
* },
* itemOperations={
* "get"={},
* "put"={"access_control"="is_granted('ROLE_COMPANY')"},
* }
* )
* #ORM\Entity(
* repositoryClass="App\Repository\SettingRepository",
* )
* #ORM\Table(
* indexes={#Index(name="domain_idx", columns={"domain"})}
* )
*/
class Setting
{
/**
* #var Uuid
* #ApiProperty(identifier=true)
* #ORM\Id
* #ORM\Column(type="string")
* #ORM\GeneratedValue(strategy="NONE")
*/
private $identifier;
/**
* #ORM\Column(type="text", nullable=true)
*/
private $data = array();
/**
* #ORM\Column(type="string", nullable=true)
*/
private $domain = array();
public function getData()
{
if($this->data == null) return array();
$data = unserialize($this->data);
return $data;
}
public function setData($data): self
{
$this->data = serialize($data);
return $this;
}
/**
* #return mixed
*/
public function getIdentifier()
{
return $this->identifier;
}
/**
* #param mixed $key
*/
public function setIdentifier($identifier): self
{
$this->identifier = $identifier;
return $this;
}
/**
* #return mixed
*/
public function getDomain()
{
return $this->domain;
}
/**
* #param mixed $domain
*/
public function setDomain($domain): self
{
$this->domain = $domain;
return $this;
}
}
If I try to invoke the service with the following parameter structure it works fine:
{
"data": "testData",
"identifier": "testIdentifier",
"domain": "domain1"
}
But If I would like to store an embedded JSON string, for example:
"data": {"temp": 123}
I receive the following error:
hydra:description": "The type of the \"data\" attribute must be \"string\", \"array\" given.",
I tried to convert the object into an string in the method setData. But this method will not be invoked. It seams, that the API-Platform detects the wrong type and throws the exception.
I found some comments, that it is necessary to decorate the property:
https://api-platform.com/docs/core/serialization/#decorating-a-serializer-and-adding-extra-data
Can anyone give me an example? It does not work!
Where is the right place to serialise and unserialise the property data?
Does anyone have an idea?
Kind regards
You need to set the column type to json in MySQL. It should behave as expected.
/**
* #var array Additional data describing the setting.
* #ORM\Column(type="json", nullable=true)
*/
private $data = null;
I think null is more consistent than an empty array, but that's your choice.
I want to check Use as Default for all product for a particular store view
I use this code
$objectManager = \Magento\Framework\App\ObjectManager::getInstance();
$collection = $this->_productCollectionFactory->create();
foreach($collection as $data){
$product=$this->_product->setStoreId(1)->load($data->getEntityId());
$product->setData('name',false);
$product->save();
}
But this is removing product from categories.
Can you please let me know how I can check Use as default checkbox programmatically.
Magento 2.1.3 EE
The following solution creates a CLI command to directly manipulate the database and delete store specific product attribute information. It was written for Magento Enterprise edition, so if you're using Community Edition you'll have to modify this code to utilize entity_id instead of row_id.
Please be careful with this. The solution laid forth here bypasses model classes and performs direct delete queries on the default database connection's catalog_product_entity_datetime, catalog_product_entity_decimal, catalog_product_entity_int, catalog_product_entity_text, and catalog_product_entity_varchar tables. Back up your database first.
Step 1: Create the module
app/code/StackOverflow/Question40177336/registration.php
<?php
\Magento\Framework\Component\ComponentRegistrar::register(
\Magento\Framework\Component\ComponentRegistrar::MODULE,
'StackOverflow_Question40177336',
__DIR__
);
app/code/StackOverflow/Question40177336/etc/module.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
<module name="StackOverflow_Question40177336" setup_version="0.0.1"/>
</config>
app/code/StackOverflow/Question40177336/etc/di.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<type name="Magento\Framework\Console\CommandListInterface">
<arguments>
<argument name="commands" xsi:type="array">
<item name="stackoverflow_question40177336" xsi:type="object">StackOverflow\Question40177336\Console\Command\Product\UseDefaultValue</item>
</argument>
</arguments>
</type>
</config>
app/code/StackOverflow/Question40177336/Console/Command/Product/UseDefaultValue.php
<?php
namespace StackOverflow\Question40177336\Console\Command\Product;
use Magento\Catalog\Model\Product;
use Magento\Eav\Setup\EavSetup;
use Magento\Framework\App\ResourceConnection;
use Magento\Store\Model\StoreManagerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class UseDefaultValue extends Command
{
/**
* flag indicating if the command has been initialized yet
*
* #var bool
*/
protected $initialized = false;
/**
* The attribute_id to use for the current command.
*
* #var int
*/
protected $attributeId;
/**
* The row_id values(s) to use for the command (if any).
*
* #var array|bool
*/
protected $rowIds;
/**
* The store_id to use for the current command.
*
* #var int
*/
protected $storeId;
/**
* The table name to use for the current command.
*
* #var string
*/
protected $tableName;
/**
* #var \Magento\Framework\DB\Adapter\AdapterInterface
*/
protected $connection;
/**
* #var EavSetup
*/
protected $eavSetup;
/**
* #var StoreManagerInterface
*/
protected $storeManager;
public function __construct(
EavSetup $eavSetup,
ResourceConnection $resourceConnection,
StoreManagerInterface $storeManager
) {
$this->connection = $resourceConnection->getConnection();
$this->eavSetup = $eavSetup;
$this->storeManager = $storeManager;
parent::__construct();
}
/**
* Configures the current command.
*/
protected function configure()
{
$this
->setName('catalog:product:attributes:use-default-value')
->setDescription('Removes store specific data from a product(s) of given attribute code.')
->addArgument(
'attribute_code',
InputArgument::REQUIRED,
'Attribute Code'
)
->addArgument(
'store',
InputArgument::REQUIRED,
'Store code or store_id (cannot be \'admin\' or \'0\')'
)
->addArgument(
'sku',
InputArgument::OPTIONAL,
'Sku (omit to apply to all products)'
)
;
}
/**
* Executes the current command.
*
* #param InputInterface $input An InputInterface instance
* #param OutputInterface $output An OutputInterface instance
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->init($input);
$conn = $this->connection;
$bind = [
$conn->quoteInto('store_id = ?', $this->getStoreId()),
$conn->quoteInto('attribute_id = ?', $this->getAttributeId())
];
if ($this->getRowIds()) {
$bind[] = $conn->quoteInto('row_id IN (?)', $this->getRowIds());
}
$rows = $conn->delete($this->getTableName(), $bind);
$output->writeln($rows.' rows deleted.');
}
/**
* Return the row_id value(s) to use for the command (if any).
*
* #return array|boolean
*/
protected function getRowIds()
{
if (!$this->initialized) {
$this->errorInit(__METHOD__);
}
return $this->rowIds;
}
/**
* Initializes some class properties.
*
* #param InputInterface $input
*/
protected function init(InputInterface $input)
{
if (!$this->initialized) {
$attributeCode = trim($input->getArgument('attribute_code'));
if ($attributeCode == '') {
throw new \RuntimeException(__('attribute_code is required.'));
} elseif (is_numeric($attributeCode)) {
throw new \RuntimeException(__('attribute_code cannot be numeric.'));
}
$attribute = $this->eavSetup->getAttribute(
Product::ENTITY,
$attributeCode
);
if (!$attribute) {
throw new \RuntimeException(__('Invalid attribute_code "%1"', $attributeCode));
}
$backendType = $attribute['backend_type'];
$allowedTypes = ['datetime','decimal','int','text','varchar'];
if (!in_array($backendType, $allowedTypes)) {
throw new \RuntimeException(__(
'backend_type "%1" is not allowed. Allowed types include: %2',
$backendType,
implode(', ', $allowedTypes)
));
}
$this->tableName = $this->connection->getTableName('catalog_product_entity_'.$backendType);
$this->attributeId = (int) $attribute['attribute_id'];
$store = $this->storeManager->getStore($input->getArgument('store'));
if ($store->getCode() == 'admin') {
throw new \RuntimeException(__('Admin Store is not allowed for this command.'));
}
$this->storeId = (int) $store->getId();
$sku = trim($input->getArgument('sku'));
if ($sku != '') {
$sql = $this->connection->select()
->from($this->connection->getTableName('catalog_product_entity'), 'row_id')
->where('sku = ?', $sku)
;
$rowIds = $this->connection->fetchCol($sql);
if (!$rowIds) {
throw new \RuntimeException(__('Invalid Sku "%1"', $sku));
}
foreach ($rowIds as $k => $v) {
$rowIds[$k] = (int) $v;
}
$this->rowIds = $rowIds;
} else {
$this->rowIds = false;
}
$this->initialized = true;
}
}
/**
* Returns the attribute_id to use for the current command.
*
* #return int
*/
protected function getAttributeId()
{
if (!$this->attributeId) {
$this->errorInit(__METHOD__);
}
return $this->attributeId;
}
/**
* Return the store id to use for the current command.
*
* #param InputInterface $input
*/
protected function getStoreId()
{
if (!$this->storeId) {
$this->errorInit(__METHOD__);
}
return $this->storeId;
}
/**
* Return the qualified table name to use for the current command.
*
* #param InputInterface $input
*/
protected function getTableName()
{
if (!$this->tableName) {
$this->errorInit(__METHOD__);
}
return $this->tableName;
}
/**
* Throws an exception.
*
* #param string $methodName
* #throws \LogicException
*/
protected function errorInit($methodName)
{
throw new \LogicException(
__('Command has not been intialized. Call UseDefaultValue::init() before calling '.$methodName));
;
}
}
Step 2: Enable the module
php -f bin/magento module:enable StackOverflow_Question40177336
Step 3: Utilize your new CLI command.
The command has two required arguments, attribute_code and store. Store can be either the ID or Code. Admin store is not allowed for obvious reasons. The command also has an optional third parameter of SKU if you wish to only target a specific SKU (omitting this applies to all products).
For example, if you wanted to delete all "name" values from the "default" store view your command would be as follows:
php -f bin/magento catalog:product:attributes:use-default-value name default
I am trying to handle image uploads via a form and then display these image uploads elsewhere in my website.
So far I have been looking at the Cookbook and also at other tutorials.I have created an Entity and a Form and am able to submit , but I am not sure if I am storing the file itself in my DB or just the name of it.
When I try to display the images in my view resource not found error on the images. Any suggestions on the correct way to upload images in Symfony?
and this is what I have so far. Entity
<?php
namespace BlogBundle\Entity;
use Symfony\Component\HttpFoundation\File\File;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Photo
*
*
* #ORM\Entity
* #ORM\HasLifecycleCallbacks
*/
class Upload
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
*Image File
* #var File
* #Assert\File( maxSize = "5M",mimeTypes = {"image/jpeg", "image/gif", "image/png", "image/tiff"}, mimeTypesMessage = "Please upload a valid Image")
*
*/
private $file;
/**
* #ORM\Column(type="string", length=500)
*/
private $title;
/**
* #ORM\Column(type="string", length=500)
*/
private $description;
/**
* Image path
*
* #var string
*
* #ORM\Column(type="text", length=255, nullable=false)
*/
protected $path;
protected function getUploadRootDir()
{
return __DIR__.'/../../../../web/'.$this->getUploadDir();
}
protected function getUploadDir()
{
// get rid of the __DIR__ so it doesn't screw up
// when displaying uploaded doc/image in the view.
return 'uploads/documents';
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set title
*
* #param string $title
* #return Upload
*/
public function setTitle($title)
{
$this->title = $title;
return $this;
}
/**
* Get title
*
* #return string
*/
public function getTitle()
{
return $this->title;
}
/**
* Set description
*
* #param string $description
* #return Upload
*/
public function setDescription($description)
{
$this->description = $description;
return $this;
}
/**
* Get description
*
* #return string
*/
public function getDescription()
{
return $this->description;
}
/**
* Set path
*
* #param string $path
* #return Upload
*/
public function setPath($path)
{
$this->path = $path;
return $this;
}
/**
* Get path
*
* #return string
*/
public function getPath()
{
return $this->path;
}
/**
* Called before saving the entity
* #ORM\PrePersist()
* #ORM\PreUpdate()
*/
public function preUpload()
{
if (null !== $this->file) {
// do whatever you want to generate a unique name
$filename = sha1(uniqid(mt_rand(), true));
$this->path = $filename.'.'.$this->file->guessExtension();
}
}
/**
* Called before entity removal
*
* #ORM\PreRemove()
*/
public function removeUpload()
{
if ($file = $this->getAbsolutePath())
{
unlink($file);
}
}
/**
* Called after entity persistence
*
* #ORM\PostPersist()
* #ORM\PostUpdate()
*/
public function upload()
{
// The file property can be empty if the field is not required
if (null === $this->file) {
return;
}
// Use the original file name here but you should
// sanitize it at least to avoid any security issues
// move takes the target directory and then the
// target filename to move to
$this->file->move(
$this->getUploadRootDir(),
$this->getFile()->getClientOriginalName()
);
// set the path property to the filename where you've saved the file
$this->path = $this->getFile()->getClientOriginalName();
// Clean up the file property as you won't need it anymore
$this->file = null;
}
/**
* Sets file.
*
* #param UploadedFile $file
*#return Upload
*/
public function setFile(File $file = null)
{
$this->file = $file;
}
/**
* Get file.
*
* #return UploadedFile
*/
public function getFile()
{
return $this->file;
}
}
Controller methods
/** This is the homepage for the admin area, for adding,deleting,editing of blog posts.
* #Route("/posted/admin/upload", name="upload")
* #Template()
*/
public function uploadimageAction(Request $request)
{
$upload = new Upload();
//create checkboxtype form
$form = $this->createForm(new ImageFormType(), $upload, array(
'action' => $this->generateUrl('upload'),
'method' => 'POST',
));
$form->handleRequest($request);
if($form->isValid()){
$em = $this->getDoctrine()->getManager();
$upload->upload();
$em->persist($upload);
$em->flush();
// exit(\Doctrine\Common\Util\Debug::dump($post));
return $this->render('BlogBundle:Default:success.html.twig'
);
;
}
return $this->render('BlogBundle:Default:upload.html.twig',array(
'form' =>$form->createView(),
));
}
You are only storing the path in the DB, and only it's necesary that.
Using path you can show the file in your view
pd: you are storing the file in your server with:
$this->file->move(
$this->getUploadRootDir(),
$this->getFile()->getClientOriginalName()
);