Skip to content

Commit

Permalink
First release
Browse files Browse the repository at this point in the history
  • Loading branch information
vienthuong committed Jan 12, 2021
0 parents commit e5b3fe5
Show file tree
Hide file tree
Showing 9 changed files with 396 additions and 0 deletions.
60 changes: 60 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Shopware Psysh
----

![showcase](https://media.giphy.com/media/lMqlDUn389q7P31kti/source.gif)

Inspired by the `frosh tinker` that inspired by `laravel tinker` command from laravel this plugin adds a similar command to Shopware 6.

It's basically a fork version of `frosh tinker` for Shopware 6 with additional features.

Read more:

- [`frosh/tinker`](https://github.com/FriendsOfShopware/FroshTinker) repository
- [`bobthecow/psysh`](https://github.com/bobthecow/psysh) repository

## New command:

```
bin/console sw:psysh
```

- Enter `ls` to get list of scoped variables.
- Enter `list` to get list of avaialbe commands.

## Additional features:
- Shopware's Core Services aliases
(e.g new EqualsFilter via CLI instead of fully qualified class name `Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter`.
_(There is some rare cases that two classes use the same class alias so you need to give the fullname to make it work)_

- Shopware's service container auto-completion.
![alias and auto suggest](https://i.imgur.com/xhK4QIy.png)



- Struct Object caster.
![Struct object caster](https://i.imgur.com/C9pUnOy.png)

- Default scoped variables: $container (Service Container object), $connection (Doctrine Connection object), $context (Default context object), $criteria (default criteria object)...and more

## Requirements

- Shopware 6.3 or above (older versions might work, but were not tested)
- PHP 7.1 or above

## Installation via composer

```
composer require vin-sw/psysh
bin/console plugin:refresh
bin/console plugin:install --activate ShopwarePsysh
bin/console sw:psysh
```

## Contributing

Feel free to fork and send pull requests!


## Licence

This project uses the [MIT License](LICENCE.md).
21 changes: 21 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "vin-sw/psysh",
"description": "Psysh integration for Shopware",
"type": "shopware-platform-plugin",
"license": "MIT",
"autoload": {
"psr-4": {
"ShopwarePsysh\\": "src/"
}
},
"extra": {
"shopware-plugin-class": "ShopwarePsysh\\ShopwarePsysh",
"label": {
"de-DE": "Psysh integration for Shopware",
"en-GB": "Psysh integration for Shopware"
}
},
"require": {
"psy/psysh": "^0.10.5"
}
}
22 changes: 22 additions & 0 deletions src/Caster/ShopwareCaster.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php declare(strict_types=1);
namespace ShopwarePsysh\Caster;

use Shopware\Core\Framework\Struct\Struct;
use Symfony\Component\VarDumper\Cloner\Stub;
use Symfony\Component\VarDumper\Exception\ThrowingCasterException;

class ShopwareCaster implements ShopwareCasterInterface
{
public function cast($object, $array, Stub $stub, $isNested, $filter): array
{
if (!$object instanceof Struct) {
return $array;
}

try {
return $object->jsonSerialize();
} catch (ThrowingCasterException $exception) {
return $array;
}
}
}
9 changes: 9 additions & 0 deletions src/Caster/ShopwareCasterInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php declare(strict_types=1);
namespace ShopwarePsysh\Caster;

use Symfony\Component\VarDumper\Cloner\Stub;

interface ShopwareCasterInterface
{
public function cast($object, $array, Stub $stub, $isNested, $filter): array;
}
165 changes: 165 additions & 0 deletions src/Command/PsyshCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
<?php declare(strict_types=1);

namespace ShopwarePsysh\Command;

use Doctrine\DBAL\Connection;
use Psr\Container\ContainerInterface;
use Psy\Configuration;
use Psy\Shell;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\Struct\Struct;
use ShopwarePsysh\Caster\ShopwareCasterInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\Finder\Finder;

class PsyshCommand extends Command
{
protected static $defaultName = 'sw:psysh';
/**
* @var ContainerInterface
*/
private $container;
/**
* @var iterable
*/
private $casters;

/**
* PsyshCommand constructor.
* @param string|null $name
* @param iterable $casters
*/
public function __construct(iterable $casters, string $name = null)
{
parent::__construct($name ?? self::$defaultName);

$this->casters = $casters;
}

/**
* @internal
* @required
*/
public function setContainer(ContainerInterface $container): ?ContainerInterface
{
$previous = $this->container;
$this->container = $container;

return $previous;
}

protected function configure()
{
$this->addArgument('include', InputArgument::IS_ARRAY, 'Include file(s) before starting tinker');
}

protected function execute(InputInterface $input, OutputInterface $output)
{
$this->getApplication()->setCatchExceptions(false);

$config = new Configuration();
$config->loadConfig($input->getArguments());

$config->setUpdateCheck('never');
$config->setPrompt('> ');
$config->setStartupMessage('
<info>Enter list to see available commands</info>
<info>Enter ls to see list of scoped variables</info>
');

$config->addMatchers([
new ShopwareClassesMatcher(),
new ShopwareServicesMatcher(),
]);

$config->getPresenter()->addCasters(
$this->getCasters()
);

try {
$this->registerAliases();
} catch (\Throwable $ex) {
}

$shell = new Shell($config);

$shell->setScopeVariables([
'application' => $this->getApplication(),
'container' => $this->container,
'connection' => $this->container->get(Connection::class),
'commands' => $this->getCommands(),
'context' => Context::createDefaultContext(),
'criteria' => new Criteria(),
'env' => $_ENV
]);

$shell->addCommands($this->getCommands());
$shell->setIncludes($input->getArgument('include'));

return $shell->run();
}

protected function getCommands(): array
{
$commands = $this->getApplication()->all();

foreach ($commands as $command) {
if ($command instanceof ContainerAwareInterface) {
$command->setContainer($this->getApplication()->getContainer());
}
}

return $commands;
}

private function registerAliases(): void
{
$projectDir = $this->container->get('kernel')->getProjectDir();
$platformDir = $projectDir . '/platform';

$finder = new Finder();
$finder->files()->name('*.php')->in((file_exists($platformDir) ? $platformDir : $projectDir) .'/src/Core');

foreach ($finder as $file) {
$namespace = 'Shopware\\Core\\';

if ($relativePath = $file->getRelativePath()) {
$namespace .= strtr($relativePath, '/', '\\') . '\\';
}

if (strpos($namespace, 'Test')) {
continue;
}

$baseName = $file->getBasename('.php');

$class = $namespace . $baseName;

try {
class_alias($class, $baseName);
} catch (\Throwable $e) {
//
}
}
}

private function getCasters(): array
{
$casters = [];

foreach ($this->casters as $caster) {
if (!$caster instanceof ShopwareCasterInterface) {
continue;
}

$casters[Struct::class] = [$caster, 'cast'];
}

return $casters;
}
}
37 changes: 37 additions & 0 deletions src/Command/ShopwareClassesMatcher.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php declare(strict_types=1);

namespace ShopwarePsysh\Command;

use Psy\TabCompletion\Matcher\AbstractMatcher;
use Psy\TabCompletion\Matcher\ClassNamesMatcher;

class ShopwareClassesMatcher extends ClassNamesMatcher
{
/**
* {@inheritdoc}
*/
public function getMatches(array $tokens, array $info = [])
{
$class = $this->getNamespaceAndClass($tokens);
if (\strlen($class) > 0 && $class[0] === '\\') {
$class = \substr($class, 1, \strlen($class));
}
$quotedClass = \preg_quote($class);

return array_values(\array_map(
function ($className) use ($class) {
// get the number of namespace separators
$nsPos = \substr_count($class, '\\');
$pieces = \explode('\\', $className);
//$methods = Mirror::get($class);
return \implode('\\', \array_slice($pieces, $nsPos, \count($pieces)));
},
\array_filter(
\get_declared_classes(),
function ($className) use ($quotedClass) {
return AbstractMatcher::startsWith($quotedClass, $className) || (bool) strpos($className, $quotedClass);
}
)
));
}
}
53 changes: 53 additions & 0 deletions src/Command/ShopwareServicesMatcher.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php declare(strict_types=1);

namespace ShopwarePsysh\Command;

use Psy\TabCompletion\Matcher\AbstractContextAwareMatcher;
use Psy\TabCompletion\Matcher\AbstractMatcher;
use Symfony\Component\DependencyInjection\Container;

class ShopwareServicesMatcher extends AbstractContextAwareMatcher
{
/**
* {@inheritdoc}
*/
public function getMatches(array $tokens, array $info = [])
{
if (count($tokens) <= 5 || $tokens[1][1] !== '$container') {
return [];
}
$input = str_replace("'", '', $tokens[5][1]);

$object = $this->getVariable('container');

if (!$object instanceof Container) {
return [];
}

$services = $object->getServiceIds();

return \array_filter(
$services,
function ($var) use ($input) {
return AbstractMatcher::startsWith($input, $var) || (bool) strpos($var, $input);
}
);
}

/**
* {@inheritdoc}
*/
public function hasMatched(array $tokens)
{
$token = \array_pop($tokens);
$prevToken = \array_pop($tokens);

if (empty($tokens[1][1]) || $tokens[1][1] !== '$container') {
return false;
}

if ($prevToken === '(' || $token === '(') {
return true;
}
}
}
20 changes: 20 additions & 0 deletions src/Resources/config/services.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?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="ShopwarePsysh\Command\PsyshCommand">
<argument type="tagged" tag="shopware.caster"/>
<tag name="console.command"/>
<call method="setContainer">
<argument type="service" id="service_container"/>
</call>
</service>

<service id="ShopwarePsysh\Caster\ShopwareCaster">
<tag name="shopware.caster"/>
</service>
</services>
</container>
Loading

0 comments on commit e5b3fe5

Please sign in to comment.