CakePhp3.7 Plugin Autoload option is not available - cakephp

Prior to CakePhp3.7 it was possible to load a plugin using the autoload option:
Plugin::load('ContactManager', ['autoload' => true]);
This was very useful if you couldn't (or didn't want to) use composer to autoload plugins.
Since version 3.7.0: Plugin::load() and autoload option are deprecated.
$this->addPlugin('ContactManager');
Should be used instead of Plugin::load. But autoload option is not available on addPlugin().
How can I replicate the autoload functionality in CakePhp3.7 without using composer?

Well, there isn't much you can do other than re-implementing/replicating what Plugin::load() does, that is registering an autoloader, see:
https://github.com/cakephp/cakephp/blob/3.7.8/src/Core/Plugin.php#L130-L142
https://github.com/cakephp/cakephp/blob/3.7.8/src/Core/Plugin.php#L157-L170
You could for example put it in your Application class:
use Cake\Core\ClassLoader;
use Cake\Core\Plugin;
// ...
class Application extends BaseApplication
{
// ...
protected static $_loader;
public function bootstrap()
{
// ...
$this->addPlugin('ContactManager', ['autoload' => true]);
}
public function addPlugin($name, array $config = [])
{
parent::addplugin($name, $config);
$config += [
'autoload' => false,
'classBase' => 'src',
];
if ($config['autoload'] === true) {
if (!isset($config['path'])) {
$config['path'] = Plugin::getCollection()->findPath($name);
}
if (empty(static::$_loader)) {
static::$_loader = new ClassLoader();
static::$_loader->register();
}
static::$_loader->addNamespace(
str_replace('/', '\\', $name),
$config['path'] . $config['classBase'] . DIRECTORY_SEPARATOR
);
static::$_loader->addNamespace(
str_replace('/', '\\', $name) . '\Test',
$config['path'] . 'tests' . DIRECTORY_SEPARATOR
);
}
return $this;
}
// ...
}
For now \Cake\Core\ClassLoader isn't deprecated, but it might become at one point, so that you may have to re-implement that too.

Related

Codeigniter autocheck db depending on session value

I'm trying to force my app to check every time it loads a model or controller depending on which is my session value.
This is actually running, but just when I get throw this model.
class News_model extends CI_Model {
public function __construct()
{
parent::__construct();
if($this->session->dbname=='db1'){
$this->db=$this->load->database('db1', TRUE);
}
else{
$this->db=$this->load->database('db2', TRUE);
}
}
public function get_news($slug = FALSE)
{
if ($slug === FALSE)
{
$query = $this->db->get('news');
return $query->result_array();
}
$query = $this->db->get_where('news', array('slug' => $slug));
return $query->row_array();
}
}
But I do not war to include that __construct code to all my models or controllers.
I've tried to add on my autoload.php
$autoload['model'] = array('General');
Where my General code is something like this.
class General extends CI_Model {
function __construct()
{
parent::__construct();
if($this->session->dbname=='db1'){
$this->db=$this->load->database('db1', TRUE);
}
else{
$this->db=$this->load->database('db2', TRUE);
}
}
}
How can I do it?
You can do it by creating a base model which will be extended by your models that require the database check.
I have simplified the checking and loading code. A simple ternary determines the string to use and stores it in the variable $dbname. That variable is used to load the database, i.e. $this->load->database($dbname);.
I don't believe you need the second argument to load::database() which means you don't need to set $this->db explicitly. If I'm wrong, use
$this->db = $this->load->database($dbname, TRUE);
Below is the "base" model. The prefix of the file name is determined in config.php with the setting $config['subclass_prefix'] = 'MY_'; Adjust your base model's file and class name to match the 'subclass_prefix' you use.
/application/core/MY_Model.php
<?php
class MY_Model extends CI_Model
{
public function __construct()
{
parent::__construct();
$dbname = $this->session->dbname == 'db1' ? 'db1' : 'db2';
$this->load->database($dbname);
}
}
Use the above to create other models like so...
class News_model extends MY_Model
{
public function get_news($slug = FALSE)
{
if ($slug === FALSE)
{
$query = $this->db->get('news');
return $query->result_array();
}
$query = $this->db->get_where('news', array('slug' => $slug));
return $query->row_array();
}
}

SONATA generate pdf with knpSnappy

I want to integrate a PDF export into my SONATA ADMIN application. For that, I installed the KnpSnappy Bundle and SonataExporterBundle.
I followed an old tutorial found on google, but in the end it does not work.
config.yml:
knp_snappy:
pdf:
enabled: true
binary: /usr/local/bin/wkhtmltopdf
options: []
image:
enabled: true
binary: /usr/local/bin/wkhtmltoimage
options: []
temporary_folder: %kernel.cache_dir%/snappy
services.yml:
sonata.admin.exporter:
class: AppBundle\Export\Exporter
calls:
- ["setKnpSnappyPdf", ["#knp_snappy.pdf"]]
- ["setTemplateEngine", ["#templating"] ]
in my ModelAdmin, I added it:
public function getExportFormats() {
return array_merge(parent::getExportFormats(), array('pdf'));
}
I created AppBundle/Export/Exporter.php:
namespace AppBundle\Export;
use Exporter\Source\SourceIteratorInterface;
use Symfony\Component\HttpFoundation\Response;
use Sonata\AdminBundle\Export\Exporter as BaseExporter;
class Exporter extends BaseExporter
{
protected $knpSnappyPdf;
protected $templateEngine;
public function getResponse($format, $filename, SourceIteratorInterface $source)
{
if ('pdf' != $format) {
return parent::getResponse($format, $filename, $source);
}
$html = $this->templateEngine->renderView('AppBundle:Export:pdf.html.twig', array(
'source' => $source
));
$content = $this->knpSnappyPdf->getOutputFromHtml($html);
return new Response($content, 200, array(
'Content-Type' => 'application/pdf',
'Content-Disposition' => sprintf('attachment; filename=%s', $filename)
));
}
public function setKnpSnappyPdf($service)
{
$this->knpSnappyPdf = $service;
}
public function setTemplateEngine($service)
{
$this->templateEngine = $service;
}
}
error:
RuntimeException:
Invalid "pdf" format, supported formats are : "csv, json, xls, xml"
at vendor/sonata-project/exporter/src/Exporter.php:52
at Exporter\Exporter->getResponse('pdf', 'export_model_2018_04_20_15_44_05.pdf', object(DoctrineORMQuerySourceIterator))
(vendor/sonata-project/admin-bundle/src/Controller/CRUDController.php:952)
at Sonata\AdminBundle\Controller\CRUDController->exportAction(object(Request))
(vendor/symfony/symfony/src/Symfony/Component/HttpKernel/HttpKernel.php:151)
at Symfony\Component\HttpKernel\HttpKernel->handleRaw(object(Request), 1)
(vendor/symfony/symfony/src/Symfony/Component/HttpKernel/HttpKernel.php:68)
at Symfony\Component\HttpKernel\HttpKernel->handle(object(Request), 1, true)
(vendor/symfony/symfony/src/Symfony/Component/HttpKernel/Kernel.php:202)
at Symfony\Component\HttpKernel\Kernel->handle(object(Request))
(web/app_dev.php:31)
at require('/var/www/html/acianovintra/web/app_dev.php')
(vendor/symfony/symfony/src/Symfony/Bundle/WebServerBundle/Resources/router.php:42)
Can you tell me what I did wrong?
In a project I had to export some data in docx format.
To do that, I wrote a custom writer :
<?php
namespace App\MyBundle\Utils\Exporter\Writer;
use Exporter\Writer\TypedWriterInterface;
class DocxWriter implements TypedWriterInterface
{
/**
* {#inheritdoc}
*/
final public function getDefaultMimeType()
{
return 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
}
/**
* {#inheritdoc}
*/
final public function getFormat()
{
return 'docx';
}
//...
}
Then I had to create a service for the writer and add a tag :
// service.xml
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="app.exporter.writer.docx" class="App\MyBundle\Utils\Exporter\Writer\DocxWriter">
<argument>php://output</argument>
<tag name="sonata.exporter.writer"/>
</service>
</services>
</container>
In the ExporterCompilerPass of the exporter bundle, sonata get all the services tagged with sonata.exporter.writer and pass them to the addWriter of the Exporter class.
To integrate this new writer in sonata admin, you'll have to override the sonata.admin.exporter like you already did, except that you will call this new writer :
<?php
namespace App\MyBundle\Export;
use Exporter\Source\SourceIteratorInterface;
use Sonata\AdminBundle\Export\Exporter as BaseExporter;
use App\MyBundle\Utils\Exporter\Writer\DocxWriter;
use Symfony\Component\HttpFoundation\StreamedResponse;
class Exporter extends BaseExporter
{
public function getResponse($format, $filename, SourceIteratorInterface $source)
{
switch ($format) {
case 'docx':
$writer = new DocxWriter('php://output');
$contentType = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
break;
default:
return parent::getResponse($format, $filename, $source);
}
$callback = function () use ($source, $writer) {
$handler = \Exporter\Handler::create($source, $writer);
$handler->export();
};
return new StreamedResponse($callback, 200, array(
'Content-Type' => $contentType,
'Content-Disposition' => sprintf('attachment; filename="%s"', $filename),
));
}
}
With this, you should be able to add your pdf format in your admin overriding the getExportFormats method.
Hpe this helps

Sonata admin - Sorting by translated property

I have a code:
protected function configureListFields(ListMapper $listMapper)
{
$listMapper
->addIdentifier('name')
[..]
This is a property from translation (KNP translatable). I tried use:
translations.name - label is sortable, but values are missing
name or translate.name - label is not sortable, but values are ok
I don't have any idea how I should to do this. Maybe someone here can help me?
Did you try $listMapper->add('name',null, array('sortable'=>true)) ?
Ok, I made it.
1) Create abstract admin class:
use Sonata\AdminBundle\Admin\AbstractAdmin as BaseAbstractAdmin;
abstract class AbstractAdmin extends BaseAbstractAdmin { .. }
2) Use this class in your admin classes:
class UserAdmin extends AbstractAdmin { .. }
3) Add this to your column definition:
->add(
'fieldName',
null,
[
'sortable' => true,
'sort_field_mapping' => ['fieldName' => 'id'],
'sort_parent_association_mappings' => [],
]
)
4) Add this method to your abstract admin class:
protected function prepareQueryForTranslatableColumns($query)
{
$currentAlias = $query->getRootAliases()[0];
$locale = $this->request->getLocale();
$parameters = $this->getFilterParameters();
$sortBy = $parameters['_sort_by'];
$fieldDescription = $this->getListFieldDescription($sortBy);
$mapping = $fieldDescription->getAssociationMapping();
$entityClass = $mapping['targetEntity'] ?: $this->getClass();
if ($mapping) {
$mappings = $fieldDescription->getParentAssociationMappings();
$mappings[] = $mapping;
foreach ($mappings as $parentMapping) {
$fieldName = $parentMapping['fieldName'];
$query->leftJoin($currentAlias . '.' . $fieldName, $fieldName);
$currentAlias = $fieldName;
}
}
$query
->leftJoin(
$currentAlias . '.translations',
'tr',
'with',
'tr.locale = :lang OR
(NOT EXISTS(SELECT t.id FROM ' . $entityClass . 'Translation t WHERE t.translatable = tr.translatable AND t.locale = :lang)
AND tr.locale = :lang_default)'
)
->addOrderBy('tr.name', $parameters['_sort_order'])
->setParameter(':lang', $locale)
->setParameter(':lang_default', 'en');
return $query;
}
I use JOIN to get translations for currently selected locale and, if translation doesn't exist yet for current locale, I add translation for default locale (it is a reason for use NOT EXIST).
5) Add this method to your admin class:
public function createQuery($context = 'list')
{
$query = parent::createQuery($context);
if ('list' === $context) {
$parameters = $this->getFilterParameters();
$sortBy = $parameters['_sort_by'];
if (in_array($sortBy, ['fieldName', 'fieldName.fieldName2', 'fieldName3', ..])) {
$query = parent::prepareQueryForTranslatableColumns($query);
}
}
return $query;
}
Late answer but I was having the same problem.
The easiest solution for me was to set the right property mapping like this:
$listMapper->add(
'translations',
null,
[
'sortable' => true,
'associated_property' => 'name',
'sort_field_mapping' => [
'fieldName' => 'name',
],
'sort_parent_association_mappings' => [
['fieldName' => 'translations'],
],
]
);

Symfony3 return array from query to json

I have problem, i can't return my posts array to json becouse symfony returns array with entity object?
Its my code:
public function indexAction()
{
$em = $this->getDoctrine()->getManager();
$posts = $em->getRepository('AppBundle:Post')->findAll();
return $this->json($posts);
}
I use $this->json is return json data, feature added on sf3.
But this is my result:
[
{},
{},
{}
]
i want to load my posts.
ps. i know, i can use Query builder, and method toArray or something, but is any method to use and DRY? Thx
Because entity can have multiple boundaries, proxy objects and related entities, I personally prefer to explicitly specify what is about to be serialized, like this:
use JsonSerializable;
/**
* #Entity
*/
class SomeEntity implements JsonSerializable
{
/** #Column(length=50) */
private $title;
/** #Column(length=50) */
private $text;
public function jsonSerialize()
{
return array(
'title' => $this->title,
'text' => $this->text,
);
}
}
And then it's as simple as json_encode($someEntityInstance);.
You can use JMSSerializerBundle as well to accomplish your task DRY.
Also, there is an option to write your own serializer to normalize the data.
UPDATE:
If you want multiple representations of a JSON, it can be achieved like this:
use JsonSerializable;
/**
* #Entity
*/
class SomeEntity implements JsonSerializable
{
// ...
protected $isList;
public function toList()
{
$this->isList = TRUE;
return $this;
}
private function jsonSerializeToList()
{
return [ // array representing list... ]
}
public function jsonSerialize()
{
if( $this->isList ) {
$normalized = $this->jsonSerializeToList();
} else {
$normalized = array(
'title' => $this->title,
'text' => $this->text,
);
}
return $normalized;
}
}
And called as json_encode($someEntityInstance->toList());. Any way, this is a bit dirty, so I suggest to be consistent with an idea of the interface.
A best solution is to enable the serializer component in Symfony:
#app/config/config.yml
framework:
serializer: ~
Note: the serializer component is disabled by default, you have to uncomment the config line in app/config/config.yml file.

Zend framework 2 model for database, separate model for each table?

I looked through the manual of Zend Framework 2 about creating model to managing operations on table. Is the class with method exchangeArray() is necessary? It's only copy data :/ Can i create one model to manage a few tables?
I created two classes:
namespace Application\Model;
use Zend\Db\Adapter\Adapter;
use Zend\Db\Adapter\AdapterAwareInterface;
abstract class AbstractAdapterAware implements AdapterAwareInterface
{
protected $db;
public function setDbAdapter(Adapter $adapter)
{
$this->db = $adapter;
}
}
and:
namespace Application\Model;
class ExampleModel extends AbstractAdapterAware
{
public function fetchAllStudents()
{
$result = $this->db->query('select * from Student')->execute();
return $result;
}
}
I also add entries in Module.php:
'initializers' => [
'Application\Model\Initializer' => function($instance, \Zend\ServiceManager\ServiceLocatorInterface $serviceLocator){
if ($instance instanceof AdapterAwareInterface)
{
$instance->setDbAdapter($serviceLocator->get('Zend\Db\Adapter\Adapter'));
}
}
],
'invokables' => [
'ExampleModel' => 'Application\Model\ExampleModel'
],
I execute methods from model by:
$this->getServiceLocator()->get('ExampleModel')->fetchAllStudents();
You should do 2 things with your code. First, implement AdapterAwareInterface properly. Second, create an initializer which injects the adapter into your model. Consider the code below:
...
'initializers' => [
function($instance, ServiceLocatorInterface $serviceLocator){
if ($instance instanceof AdapterAwareInterface) {
$instance->setDbAdapter($serviceLocator->get('Zend\Db\Adapter\Adapter'));
}
}
]
...
abstract class AbstractModel implements AdapterAwareInterface
{
protected $db;
public function setDbAdapter(Adapter $adapter)
{
$this->db = adapter;
}
}
...
'invokables' => [
'ExampleModel' => 'Application\Model\ExampleModel'
]
As you can see from above, after all, you don't need a factory for each your model. You can either register invokables or create an Abstract Factory to instantiate your models. See an example below:
...
'abstract_factories' => [
'Application\Model\AbstractFactory'
]
...
class AbstractFactory implements AbstractFactoryInterface
{
public function canCreateServiceWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName)
{
return class_exists('Application\Model\'.$requestedName);
}
public function createServiceWithName(\Zend\ServiceManager\ServiceLocatorInterface $serviceLocator, $name, $requestedName)
{
$class = 'Application\Model\'.$requestedName();
return new $class
}
}
Hope this helps

Resources