Which rule can I use to change variable name? - rector

I need to replace all occurencies of $connection with $link?
I know I could do with a regexp replacement using my IDE, but I need to be able to re-run the sostitution automatically.
So I want to use rector.
Is there a way to replace a var name ? Which is the rule name?

There are a couple of potentially suitable rules & sets:
RenamePropertyRector
There is also the '\Rector\Set\ValueObject\SetList::NAMING' set that can be enabled in rector.php that will perform some similar rules, like renaming variables according to the type.
The complete set of basic rules (not including framework or library-specific) are at https://github.com/rectorphp/rector/blob/main/docs/rector_rules_overview.md

I create a custom rule, very specific for my needs
<?php
namespace Rules;
use PhpParser\Node;
use Rector\Core\Rector\AbstractRector;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
use PhpParser\Node\Expr\Variable;
class ReplaceConnectionVarNameWithLink extends AbstractRector
{
public function getNodeTypes(): array
{
return [
Variable::class
];
}
public function getRuleDefinition(): \Symplify\RuleDocGenerator\ValueObject\RuleDefinition
{
return new RuleDefinition(
'rename $connect into $link',
[]
);
}
public function refactor(\PhpParser\Node $node)
{
if (!$this->isName( $node, 'connection')) {
// return null to skip it
return null;
}
$node->name = "link";
return $node;
}
}

Related

Api platform add or update

Good afternoon I am adding data in via POST method in PLATFORM API can I make this method work like adding or updating data.
So that when the data is already there for the object, it will simply update the pinOrder field.
My input:
{
"chat": "/api/chats/01FVKRYXMMTHKJ2EZB02F4FZ3Z",
"pinOrder": 3
}
Insert or update (upsert) is not available in Api Platform. However, you can achieve this behavior with a custom (or decorated) Data Persister.
https://api-platform.com/docs/core/data-persisters/
In the persist method of the data persister you could manually check if an item matching your criteria does already exist and, if yes, update this one instead of persisting a new one.
You can use PRE_WRITE event. For exemple, an order item quantity.
public static function getSubscribedEvents(): array
{
return [
KernelEvents::VIEW => [
'updateExistingItemQuantity', EventPriorities::PRE_WRITE,
],
];
}
public function updateExistingItemQuantity(
ViewEvent $event
): void {
$item = $event->getControllerResult();
$method = $event->getRequest()->getMethod();
if (!$item instanceof MyItemObject || Request::METHOD_POST !== $method) {
return;
}
// find duplicateItem
if ($duplicateItem) {
$duplicateItem->setQuantity("UPDATED QUANTITY");
// save $duplicateItem
$event->setControllerResult($duplicateItem);
}
}

Laravel tag all class implemeting an interface

I'm using Laravel 8 and i want to get all the classes that implements an Interface X.
I did it with symfony4 few month ago with DI :
services.yml
_instanceof:
App\Calculator\Budget\BudgetCalculatorInterface:
tags: ['app.budget_calculator']
App\Handler\CalculatorBudgetHandler:
arguments: [!tagged app.budget_calculator]
and then in my class CalculatorBudgetHandler.php
private $calculatorList = [];
public function __construct(iterable $calculatorList)
{
$this->calculatorList = $calculatorList;
}
public function __construct(iterable $calculatorList)
{
$this->calculatorList = $calculatorList;
}
public function calculate(array $data): float
{
foreach ($this->calculatorList as $calculator) {
if ($calculator->supports($data)) {
return $calculator->calculate($data);
}
}
}
but i dot not understand how to do it with Laravel. I think i have to pass all my classes in a bind or tag :
$this->app->tag([CpuReport::class, MemoryReport::class], 'reports');
thats mean if i got a new class implementing X, i have to add it in the bind/tag ?
I want to do it automatically .
thx !
I needed this too. Looked for a longer time and I basically found a solution. The bad thing about this is that in PHP classes aren't actually declared when you did not use them. So you'll have to either scan the entire project for classes and test each class to find classes implementing your interface or (better) you use the composer autoload class maps. There you could probably limit the searching scope for classes to a sub namespace.
A small but cool package working this way is this one: https://gitlab.com/hpierce1102/ClassFinder - basically it uses composer PSR4 classmaps and is in general fine performance wise.
Here is the solution to which I came:
// Add to service provider
private function tagByInterface(string $interfaceName, string $tagName, string $rootNamespace)
{
foreach (ClassFinder::getClassesInNamespace($rootNamespace, ClassFinder::RECURSIVE_MODE) as $className) {
$class = new \ReflectionClass($className);
if ($class->isAbstract() || $class->isInterface()) {
continue;
}
if ($class->implementsInterface($interfaceName)) {
$this->app->tag($className, $tagName);
}
}
}
Which can then be used like this in the register():
$this->tagByInterface(SomeInterface::class, 'some-tag', 'App\Domain\Something');
$this->app->when(SomeClass::class)->needs('$myServices')->giveTagged('some-tag');
As the classes are loaded using reflection, this operation still might take time if your root namespace is not properly set or too wide. Reflection is quick (as far as I know quicker than loading the information from cache), but you should still think of using a deferred provider for the task so that the search for implementing classes only triggers when it's actually needed.
Update some months later
This solution works, but might be a huge drain on performance if the project gets big. I'm now caching the tagged classes. Something like this:
use HaydenPierce\ClassFinder\ClassFinder as HPClassFinder;
use Illuminate\Contracts\Cache\Repository;
class InheritanceClassFinder
{
public function __construct(private ?Repository $cache = null)
{
}
public function findClassesImplementingOrExtending(string $interfaceOrClass, string $rootNamespace): array
{
if ($this->cache) {
return $this->cache->rememberForever(
'inheriting-classes-'.$interfaceOrClass,
fn () => $this->findClassesInheriting($interfaceOrClass, $rootNamespace));
}
return $this->findClassesInheriting($interfaceOrClass, $rootNamespace);
}
private function findClassesInheriting(string $interfaceOrClass, string $rootNamespace): array
{
$classes = [];
foreach (HPClassFinder::getClassesInNamespace($rootNamespace, HPClassFinder::RECURSIVE_MODE) as $className) {
if (!is_subclass_of($className, $interfaceOrClass)
|| ($class = new \ReflectionClass($className))->isAbstract() || $class->isInterface()) {
continue;
}
$classes[] = $className;
}
return $classes;
}
}
This means as long as the cache is injected, stuff will be loaded once and then taken from cache. I inject the cache only in production, so locally its a bit slower but always up to date. In production I throw away the cache with every deployment, so I get a fresh load once after every deployment.

How to always filter a collection on specific field value in api-platform GET operations?

In the GET operations, I'd like to exclude from the returning collections my entities that have an " archive" field that equals " true ".
I'd like that to be the default for my endpoints like /users or /companies and i want to avoid to add an URL filter by hand like /users?filter[archive]=true
what would be the best way to do that ?
Thanks for any help :)
I had to do something like that, and I solved it by applying a DoctrineExtension to a Collection that will add the WHERE clause to the QueryBuilder.
How?
Define your Filter, I see you already have that.
Add the Doctrine Extension to by applied only to Collections, or if you need, also to an Item: https://api-platform.com/docs/core/extensions/#custom-doctrine-orm-extension
services:
App\Doctrine\Extension\ArchivedExtension:
tags:
- { name: api_platform.doctrine.orm.query_extension.collection }
Code your Extension.
You could check if you are receiving a certain "filter" (your "filter[archive]=true" query parameter): if YES, dont apply the condition to the QueryBuilder, your Filter will be applied by the filtering mechanism of ApiPlatform.
Your extension class should looked something like this:
class ArchivedExtension implements QueryCollectionExtensionInterface
{
public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null, array $context = [])
{
$this->addWhere($queryBuilder, $resourceClass, $context);
}
private function addWhere(QueryBuilder $queryBuilder, string $resourceClass, array $context = [])
{
if (MyResource::class !== $resourceClass) {
return;
}
// Search if a "archive" Filter is being requested, if not, apply a restriction to the QueryBuilder
if (array_key_exists('archive', $context['filters'])) {
return;
}
$rootAlias = $queryBuilder->getRootAliases()[0];
$queryBuilder->andWhere(sprintf('%s.archive = :archive', $rootAlias));
$queryBuilder->setParameter('archive', true);
}
}

Magento2: Argument 1 [...] must be an instance of Magento\Framework\App\Helper\Context

First of all, I'm quite new to Magento 2, but I've used Magento 1.x for some time.
I've read a lot about how to solve DI-related problems, but I'm stuck on this one:
Exception #0 (Exception): Recoverable Error: Argument 1 passed to Cefar\AO\Helper\Ao::__construct() must be an instance of Magento\Framework\App\Helper\Context, instance of Magento\Framework\ObjectManager\ObjectManager given, called in .../vendor/magento/framework/ObjectManager/Factory/AbstractFactory.php on line 93 and defined in .../Cefar/AO/Helper/Ao.php on line 11
Many other answers have suggested deleting the var/di and var/generation folders, sometimes var/cache also. While this solves the problem, it occurs again once bin/magento setup:di:compile is run, which means the code cannot be used in a production environment.
I've checked that the Ao class does not instantiate any objects. It also doesn't try to re-make any objects that could be provided by the context given. Here's the code:
namespace Cefar\AO\Helper;
class Ao extends \Magento\Framework\App\Helper\AbstractHelper
{
const DEFAULT_GRID_COLS = 4;
protected $_session;
public function __construct(
\Magento\Framework\App\Helper\Context $context,
\Magento\Customer\Model\Session $session
)
{
parent::__construct($context);
$this->_session = $session;
}
public function getConfig($path)
{
return $this->scopeConfig->getValue($path);
}
public function isActive($url = null, $print = true) {
$active = ($url && strstr($_SERVER['REQUEST_URI'], $url) !== false);
if ($active && $print) {
echo "active";
} else {
return $active;
}
}
public function isLoggedIn()
{
return $this->_session->isLoggedIn();
}
public function limitWords($text = '', $limit = 10, $showDots = true)
{
$words = explode(' ', $text);
$limited = array_slice($words, 0, $limit);
$newText = implode(' ', $limited);
if (count($words) > $limit && $showDots) {
$newText .= '...';
}
return $newText;
}
public function getCurrentGrid()
{
return ($this->_getRequest()->getParam('grid'))
? $this->_getRequest()->getParam('grid')
: self::DEFAULT_GRID_COLS;
}
}
There's nothing particularly special here. I'm confused as to how this is even happening; every other defined class in the extension is getting its DI parameters correctly. Why is the ObjectManager apparatus providing an unwanted argument? The relevant call is given in the error report as:
.../vendor/magento/framework/ObjectManager/Factory/AbstractFactory.php(93): Cefar\AO\Helper\Ao->__construct(Object(Magento\Framework\ObjectManager\ObjectManager))
So it isn't even providing two arguments!
I've also read about providing type hints in a di.xml, but it doesn't seem to be relevant here as both types are part of the Magento libraries? I note that there is an entry for Magento\Framework\App\Helper\Context but not one for Magento\Customer\Model\Session... but that there are framework classes that use ID to import Magento\Customer\Model\Session already which work.
Long story short, this was because of a typo.
Sometimes when the helper was being included, it was being referred to as Cefar\AO\Helper\Ao, and other times, Cefar\AO\Helper\AO. Essentially, the ObjectManager was resolving both of these references to the same class, but it only had type hints for one of the names so it didn't know what to provide to the incorrect one.
A little help would have been nice, Magento! Maybe an error report that the requested class wasn't found? Still, at least this is finally over with.

Codeigniter change database config at runtime

Can I change the database config per method in a controller?
$db['default']['db_debug'] = TRUE;
The default is TRUE, while I need to make it false in a certain method to catch the error and do something else (for example show 404 page).
When I tried $this->config->load('database') it fails.
Another question :
Can I check an incorrect query and catch it to some variables rather than displaying it to users other than setting the db_debug config to FALSE?
I checked the code of system/database/DB_Driver and found that:
$this->db->db_debug = FALSE;
will work in my controller to enable/disable the debug thing on the fly.
Expanding on the answer by comenk, you can extend the database class and implement various methods by which to achieve your goal.
First, you'll need to extend the core Loader class by creating a MY_Loader.php file
class MY_Loader extends CI_Loader
{
function __construct()
{
parent::__construct();
}
/**
* Load the Standard and/or Extended Database function & Driver class
*
* #access public
* #return string
*/
function database( $params = '', $return = FALSE, $active_record = NULL )
{
$ci =& get_instance();
if (class_exists('CI_DB') AND $return == FALSE AND $active_record == NULL AND isset($ci->db) AND is_object($ci->db))
{
return FALSE;
}
$my_db = config_item('subclass_prefix').'DB';
$my_db_file = APPPATH.'core/'.$my_db.EXT;
if(file_exists($my_db_file))
{
require_once($my_db_file);
}
else
{
require_once(BASEPATH.'database/DB'.EXT);
}
// Load the DB class
$db =& DB($params, $active_record);
$my_driver = config_item('subclass_prefix').'DB_'.$db->dbdriver.'_driver';
$my_driver_file = APPPATH.'core/'.$my_driver.EXT;
if(file_exists($my_driver_file))
{
require_once($my_driver_file);
$db = new $my_driver(get_object_vars($db));
}
if ($return === TRUE)
{
return $db;
}
// Initialize the db variable. Needed to prevent
// reference errors with some configurations
$ci->db = '';
$ci->db = $db;
}
}
By implementing the above this will allow you to create a MY_DB_mysqli_driver.php whereby mysqli is replaced by whatever driver you're using in your CI database.php config.
At this point you'd add comenk's answer to MY_DB_mysqli_driver.php
function debug_on() {
return $this->db_debug = TRUE;
}
function debug_off() {
return $this->db_debug = FALSE;
}
function in_error() {
return (bool) $this->_error_number();
}
Then in your model/controller,
$this->db->debug_off();
$this->db->query('SELECT * FROM `table`');
if( $this->db->in_error() ) {
show_404();
}
$this->db->debug_on();
you must add function on system/database/DB_driver.php
function debug_on()
{
$this->db_debug = TRUE;
return TRUE;
}
function debug_off()
{
$this->db_debug = FALSE;
return FALSE;
}
after that you can simply do this command to changes at run-time
$this->db->debug_off();
$this->db->reconnect();
$this->db->db_debug = 0; // 0: off, 1: on
That worx for me...
You can look at the $GLOBALS variable to locate this generic setting.
To hide bad SQL (and other errors) from users, you need to set the php error reporting level. CodeIgniter ships in basically development mode.
Go to index.php and replace this
error_reporting(E_ALL);
with this
error_reporting(0);
This is the quick way to do it. You can also implement this using a hook, so you don't have to touch CI files. You can also add logic to that hook so that it only sets it on the production server.
For debugging SQL, you can create a class that inherits from CI_Model, then create all your model classes to extend that class. In that class, you can add code for running queries that writes the queries to the log so that you can debug them easier. This won't help if the query itself is bad, but you should be able to figure that out before you get to that point.

Resources