I have a problem with my cacheManager instance migration into TYPO3 10. According to the TYPO3 10 documentation, the cache should be injected via the constructor instead of an instance of "cacheManager".
I followed the documentation, the yaml config is also correct and I get an error message in the frontend.
"Class TYPO3\CMS\Core\Cache\Frontend\Frontend does not exist. Reflection failed."
As far as I know, this class does not exist at all.
Is there a solution for this error?
EXT:my_ext/Configuration/Services.yaml
services:
cache.my_ext:
class: TYPO3\CMS\Core\Cache\Frontend\FrontendInterface
factory: ['#TYPO3\CMS\Core\Cache\CacheManager', 'getCache']
arguments: ['my_ext']
MyVendor\MyExt\Connection\Http:
arguments:
$cache: '#cache.my_ext'
EXT:my_ext/Classes/Connection/Http.php
/**
* #var FrontendInterface
*/
private $cache;
public function __construct(FrontendInterface $cache)
{
$this->cache = $cache;
}
public function initializeObject() {
$className = explode('\\', get_class($this));
$this->extensionKey = \TYPO3\CMS\Core\Utility\GeneralUtility::camelCaseToLowerCaseUnderscored($className[1]);
}
public function getDataFromUrl($url, $cachingTags = array()) {
$cacheIdentifier = sha1($url);
$remoteData = $this->getUrl($url . "&" . time());
if (($this->cache->get($cacheIdentifier)) === false) {
$this->cache->set($cacheIdentifier, $remoteData, $cachingTags,36000);
}
return $remoteData;
}
I followed the documentation here:
https://docs.typo3.org/m/typo3/reference-coreapi/10.4/en-us/ApiOverview/CachingFramework/Developer/Index.html
In my case, the problem was that in docs there were missing service default configs.
Following is the minimal configuration for me that worked.
services:
_defaults:
autowire: true
autoconfigure: true
public: false
Mohsin\HeadlessProducts\:
resource: '../Classes/*'
cache.headless_products:
class: TYPO3\CMS\Core\Cache\Frontend\FrontendInterface
factory: ['#TYPO3\CMS\Core\Cache\CacheManager', 'getCache']
arguments: ['headless_products']
Mohsin\HeadlessProducts\Utility\ProductsUtility:
arguments:
$cache: '#cache.headless_products'
Also, it's important to remove the cache from the install tool or CLI otherwise your changes will not reflect if you remove it from the backend only.
My solution:
Content of the Services.yaml file
services: _defaults:
autowire: true
autoconfigure: true
public: false
WimRoukema\WrCollection\:
resource: '../Classes/*'
cache.wr_collection:
class: TYPO3\CMS\Core\Cache\Frontend\FrontendInterface
factory: ['#TYPO3\CMS\Core\Cache\CacheManager', 'getCache']
arguments: ['wr_collection']
WimRoukema\WrCollection\Controller\IndexController:
public: true
arguments:
- '#cache.wr_collection'
My solution was not to use different keys in localconf.php.
According to named documentation you should use myext_mycache and my_cache in different places. When I used only my_cache it worked.
Related
I am a bit confused about my folder structure for the scraping code. Using console/commands, not the controller. So, in the handle function I am writing the whole scraping code. But should I suppose to do that? Or... what is the best approach for this?
UPDATED
If I understand correctly the answer below. It should look like this right now.
calling services
class siteControl extends Command
{
protected $signature = 'bot:scrape {website_id}';
protected $description = 'target a portal site and scrape';
public function __construct()
{
parent::__construct();
}
public function handle()
{
$website_id = $this->argument("website_id");
if ($website_id == 1) {
$portal = "App\Services\Site1";
}
$crawler = new $portal;
$crawler->run();
}
}
in handle method
class Site1 extends Utility
{
public function __construct()
{
parent::__construct();
}
public function run()
{
echo "method runs";
}
}
abstract:
use Goutte\Client;
abstract class Utility implements SiteInterfaces
{
protected $client;
public function __construct()
{
$this->client = new Client();
}
}
interfaces:
namespace App\Services;
interface SiteInterfaces
{
public function run();
}
and finally, I should write the whole scraping code inside the run() method? Please correct me If wrong about this... I am searching the best solution.
A best practice would be to call a separate service from your command handle() method. That way you could reuse that same service in a controller for instance.
The technical version:
Your application is given a specific thing to do (a command if you will). This command comes from outside of your application, which can be a anything from a web controller, to an API controller or a CLI application. In terms of hexagonal architecture this is called a port.
Once the application receives such a command it should not care which port it came from. By handling all similar commands in a single spot (a command handler) it does not have to worry about the origins of the command.
So to give you a short overview:
[Web request] [CLI command] <-- these are ports
\ /
\ /
\ /
[Command] <--- this is a method call to your service
|
|
|
[Command handler] <--- this is the service doing the actual work
Updated my answer
Based on the code you provided I implemented what I mentioned above like so:
app/Console/Command/BotScrapeCommand.php
This is the CLI command I mentioned above. All this class has to do is:
1. Gather input arguments; (website_id) in this case
2. Wrap those arguments in a command
3. Fire off the command using the command handler
namespace App\Console\Commands;
use App\Command\ScrapePortalSiteCommand;
use CommandHandler\ScrapePortalSiteCommandHandler;
class BotScrapeCommand extends Command
{
protected $signature = 'bot:scrape {website_id}';
protected $description = 'target a portal site and scrape';
public function handle(ScrapePortalSiteCommandHandler $handler)
{
$portalSiteId = $this->argument("website_id");
$command = new ScrapePortalSiteCommand($portalSiteId);
$handler->handle($command);
}
}
app/Command/ScapePortalSiteCommand.php
This is the Command I mentioned above. Its job is to wrap all input arguments in a class, which can be used by a command handler.
namespace App\Command;
class ScrapePortalSiteCommand
{
/**
* #var int
*/
private $portalSiteId;
public function __construct(int $portalSiteId)
{
$this->portalSiteId = $portalSiteId;
}
public function getPortalSiteId(): int
{
return $this->portalSiteId;
}
}
app/CommandHandler/ScrapePortalSiteCommandHandler.php
The command handler should implement logic based on its command. In this case that's figuring out which crawler to pick, then fire that one off.
namespace App\CommandHandler;
use App\Command\ScrapePortalSiteCommand;
use App\Crawler\PortalSite1Crawler;
use App\Crawler\PortalSiteCrawlerInterface;
use InvalidArgumentException;
class ScrapePortalSiteCommandHandler
{
public function handle(ScrapePortalSiteCommand $command): void
{
$crawler = $this->getCrawlerForPortalSite($command->getPortalSiteId());
$crawler->crawl();
}
private function getCrawlerForPortalSite(int $portalSiteId): PortalSiteCrawlerInterface {
switch ($portalSiteId) {
case 1:
return new PortalSite1Crawler();
default:
throw new InvalidArgumentException(
sprintf('No crawler configured for portal site with id "%s"', $portalSiteId)
);
}
}
}
app/Crawler/PortalSiteCrawlerInterface.php
This interface is there to make sure all crawlers can be called in similar fashion. Additionally it makes for nice type hinting.
namespace App\Crawler;
interface PortalSiteCrawlerInterface
{
public function crawl(): void;
}
app/Crawler/PortalSite1Crawler.php
This is where the implementation of the actual scraping goes.
namespace App\Crawler;
class PortalSite1Crawler implements PortalSiteCrawlerInterface
{
public function crawl(): void
{
// Crawl your site here
}
}
Another update
As you had some additional questions I've updated my answer once more.
:void
The use of : void in a method declaration means the method will not return anything. In a same way public function getPortalSiteId(): int means this method will always return an integer. The use of return typehints was added to PHP 7 and is not specific to Laravel. More information on return typehints can be found in the PHP documentation.
Commands and handlers
The use of commands and command handlers is a best practice which is part of the command bus pattern. This pattern describes an universal way of dealing with user input (a command). This post offers a nice explanation on commands and handlers. Additionally, this blog post describes in more details what a command bus is, how it's used and what the advantages are. Please note that in the code I've provided the bus implementation itself is skipped. In my opinion you do not need it per se, but in some cases it does add value.
I'm trying to use zend expressive nested application, so I'm following this blog post :
https://framework.zend.com/blog/2017-03-15-nested-middleware-in-expressive.html
The problem seems to be in the Middleware factory:
class CreateBookMiddlewareFactory
{
public function __invoke(ContainerInterface $container)
{
$nested = new Application(
$container->get(RouterInterface::class),
$container
);
$nested->pipe(AuthenticationMiddleware::class);
$nested->pipe(ContentValidationMiddleware::class);
$nested->pipe(BodyParamsMiddleware::class);
$nested->pipe(BookValidationMiddleware::class);
$nested->pipe(CreateBookMiddleware::class);
return $nested;
}
}
I don't get how CreateBookMiddleware could be added to the pipe here as we are in its Factory. So piping it will call the factory, create a new nested application, which will call the factory, which will create another nested application...
( ! ) Fatal error: Maximum function nesting level of '256' reached, aborting! in /var/www/project/vendor/zendframework/zend-stratigility/src/Next.php on line
158
Is there something I'm not getting right from this blog post?
You named the factory CreateBookMiddlewareFactory. And then inside __invoke you have $nested->pipe(CreateBookMiddleware::class);. It depends on your config, but usually CreateBookMiddlewareFactory would be the factory for CreateBookMiddleware. So it's stuck in a loop because it keeps creating itself.
As you have the exact same code as in the blogpost, I'm guessing it's an error in that blog post. I think it should have been like in the last delegator factory example: without the last $nested->pipe(CreateBookMiddleware::class);.
I've notified the author of the blog post.
Edit: The blog post is updated with this fix:
namespace Acme\Api;
use Acme\AuthenticationMiddleware;
use Acme\ContentNegotiationMiddleware;
use Psr\Container\ContainerInterface;
use Zend\Expressive\Application;
use Zend\Expressive\Helper\BodyParams\BodyParamsMiddleware;
use Zend\Expressive\Router\RouterInterface;
class CreateBookMiddlewareFactory
{
public function __invoke(ContainerInterface $container)
{
$nested = new Application(
$container->get(RouterInterface::class),
$container
);
$nested->pipe(AuthenticationMiddleware::class);
$nested->pipe(ContentValidationMiddleware::class);
$nested->pipe(BodyParamsMiddleware::class);
$nested->pipe(BookValidationMiddleware::class);
// If dependencies are needed, pull them from the container and pass
// them to the constructor:
$nested->pipe(new CreateBookMiddleware());
return $nested;
}
}
I accepted #xtreamwayz answer for the clarification. But here's how I made it work:
class CreateBookMiddlewareFactory
{
public function __invoke(ContainerInterface $container)
{
$nested = new Application(
$container->get(RouterInterface::class),
$container
);
$nested->pipe($container->get(AuthenticationMiddleware::class));
$nested->pipe($container->get(ContentValidationMiddleware::class));
$nested->pipe($container->get(BodyParamsMiddleware::class));
$nested->pipe($container->get(BookValidationMiddleware::class));
// instanciate the new class, so it will not call the factory again
$nested->pipe(new CreateBookMiddleware());
return $nested;
}
}
Hi is there way to clear ALL cached data from symfony cache component?
Here http://symfony.com/doc/current/components/cache/cache_pools.html on bottom is: (i need console command)
$cacheIsEmpty = $cache->clear();
and command:
bin/console cache:clear
keeps this cache untouched
I am lookig for console command witch i can call in *.sh script every deploy.
EDIT (example):
Default input options:
$cache = new FilesystemAdapter();
$defaultInputOptions = $cache->getItem('mainFilter.defaultInputOptions');
if (!$defaultInputOptions->isHit()) {
// collect data, format etc.
$expiration = new \DateInterval('P1D');
$defaultInputOptions->expiresAfter($expiration);
$cache->save($defaultInputOptions);
} else {
return $defaultInputOptions->get();
}
But if i change something in 'collect data, format etc.' on my machine and after that make deploy (git pull, composer install, bin/console cache:clear...) then new version on server has still valid cache (1 day) and take data from it...
You can make any service you want implement the Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface to be used by symfony native cache:clear
use Symfony\Component\HttpKernel\CacheClearer\CacheClearerInterface;
use Symfony\Component\Cache\Adapter\AdapterInterface;
final class SomeCacheClearer implements CacheClearerInterface
{
private $cache;
public function __construct(AdapterInterface $filesystemAdapter)
{
$this->cache = $filesystemAdapter;
}
public function clear($cacheDir)
{
$this->cache->clear();
}
}
Config is done by the tag kernel.cache_clearer
cache_provider.clearer:
class: AppBundle\Path\SomeCacheClearer
arguments:
- 'my_app_cache'
tags:
- { name: kernel.cache_clearer }
EDIT: precision about cache as a service:
Your cache service could be defined like that
my_app_cache:
class: Symfony\Component\Cache\Adapter\FilesystemAdapter
Or if you want to specify a namespace and ttl
my_app_cache:
class: Symfony\Component\Cache\Adapter\FilesystemAdapter
arguments:
- 'my_cache_namespace'
- 30 #cache ttl (in seconds)
So you should not use
$cache = new FilesystemAdapter();
But with service injection
$cache = $this->get('my_app_cache');
At the end - suffices edit only services.yml and add:
appbundle.cachecomponent.clearall:
class: Symfony\Component\Cache\Adapter\FilesystemAdapter
tags:
- { name: kernel.cache_clearer }
I am new in symfony so i hope that is right solution.
I am going through an online course on Laravel. This course is using the League\commonmark package for converting markdown to html.
Whenever the package is used in the app, I get:
Unable to find corresponding renderer for block type League\CommonMark\Block\Element\Document
The app uses the following presenter to do the conversion.
class PagePresenter extends AbstractPresenter
{
protected $markdown;
public function __construct($object, CommonMarkConverter $markdown)
{
$this->markdown = $markdown;
parent::__construct($object);
}
public function contentHtml()
{
return $this->markdown->convertToHtml($this->content);
}
}
Can anyone point me in the right direction?
That happens because the IoC is resolving the dependencies for CommonMarkConverter, specifically Environment which is instantiated with all null properties.
You can probably resolve this by using a Laravel specific integration: https://github.com/GrahamCampbell/Laravel-Markdown
Or you can bind and instance to the service container this way:
In your AppServiceProvider, register method add this:
$this->app->singleton('Markdown', function ($app) {
// Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go
$environment = \League\CommonMark\Environment::createCommonMarkEnvironment();
// Define your configuration:
$config = ['html_input' => 'escape'];
// Create the converter
return new \League\CommonMark\CommonMarkConverter($config, $environment);
});
Now remove CommonMarkConverter from your Presenter constructor add use app('Markdown'):
class PagePresenter extends AbstractPresenter {
protected $markdown;
public function __construct($object)
{
$this->markdown = app('Markdown');
parent::__construct($object);
}
public function contentHtml()
{
return $this->markdown->convertToHtml($this->content);
}
}
You just put a line in the config/app.php file
'Markdown' => GrahamCampbell\Markdown\Facades\Markdown::class,
I'm trying to create dynamic routes as I have created a CMS where each page created can be associated to a route. I'm using the example from this link - http://php-and-symfony.matthiasnoback.nl/2012/01/symfony2-dynamically-add-routes/ and all works fine, however the routing is cached, therefore one route will work but then the next won't unless I clear the cache. Is it possible to remove just the routing cache at this stage or is there another alternative? I don't want to remove the whole cache directory on each page load as that wouldn't make sense. Here is the example code:
namespace Acme\RoutingBundle\Routing;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\Config\Loader\LoaderResolver;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
class ExtraLoader implements LoaderInterface
{
private $loaded = false;
public function load($resource, $type = null)
{
if (true === $this->loaded) {
throw new \RuntimeException('Do not add this loader twice');
}
$routes = new RouteCollection();
$pattern = '/extra';
$defaults = array(
'_controller' => 'AcmeRoutingBundle:Demo:extraRoute',
);
$route = new Route($pattern, $defaults);
$routes->add('extraRoute', $route);
return $routes;
}
public function supports($resource, $type = null)
{
return 'extra' === $type;
}
public function getResolver()
{
}
public function setResolver(LoaderResolver $resolver)
{
// irrelevant to us, since we don't need a resolver
}
}
Then I've made a service for the ExtraLoader:
<!-- in /src/Acme/RoutingBundle/Resources/config/services.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="acme.routing_loader" class="Acme\RoutingBundle\Routing\ExtraLoader">
<tag name="routing.loader"></tag>
</service>
</services>
</container>
The last thing we need, is a few extra lines in /app/config/routing.yml:
AcmeRoutingBundle:
resource: .
type: extra
This is quite inefficient, because with each new route you have to clear cache, so you'll be bound by hdd/ssd with useless clear cache.
The alternative is to create a new method in controller which accepts a dynamic page on GET and to show the dynamic content in twig.
You can create a service to render the dynamic pages, which will simplify things.
Do you have looked at the DynamicRouter from the symfony-cmf project? I think this fits your needs and is exactly created for your use case.
You current implementation has some really issues you should know about. First of all, you have to clear the routing cache, for each route you create/edit/delete. This leads to race conditions and memory peaks for no reason.
The default implementation from symfony is to handel static routes, not dynamic ones.
I researched and tried out a bit and I found out that you can just delete the following files:
for dev:
/app/cache/dev/appDevUrlGenerator.php
/app/cache/dev/appDevUrlGenerator.php.meta
/app/cache/dev/appDevUrlMatcher.php
/app/cache/dev/appDevUrlMatcher.php.meta
for prod:
/app/cache/prod/appProdUrlGenerator.php
/app/cache/prod/appProdUrlMatcher.php
There is only one minior downside of this. I am using the current route to determine if a menu item is active or not:
{% set currentPath = path(app.request.attributes.get('_route'), app.request.attributes.get('_route_params')) %}
...
<li{% if currentPath == path('mybundle_default_index') %} class="active"{% endif %}>
In this case app.request.attributes.get('_route') is still cached as a route that might not exist anymore. I don't know yet if this only concerns the twig cache or other parts too.
Also I don't understand why you would have to delete the whole cache on each page load? You only have to clear the cache when new routes are added.
I've resolved this problem in my own CMS.
At first I overrode the base Router class:
parameters:
router.class: Name\Of\Your\Router
and extended it:
use Symfony\Bundle\FrameworkBundle\Routing\Router as BaseRouter;
class Router extends BaseRouter
{
public function clearCache($cache_dir, $environment, $warm_up)
{
$cache_dir .= '/'. $environment;
$environment = ucfirst($environment);
#unlink($cache_dir .'/app'. $environment .'UrlMatcher.php');
#unlink($cache_dir .'/app'. $environment .'UrlGenerator.php');
if ($warm_up) {
$this->matcher = null;
$this->generator = null;
$this->warmUp($cache_dir);
}
}
}
Secondly I created a service CacheService:
cache_service:
class: Name\Of\Your\CacheService
arguments:
- #router
- %kernel.environment%
- %kernel.root_dir%/cache
and added the following method:
public function clearCache($environment = null)
{
if (null === $environment) {
$environment = $this->environment;
}
$this->router->clearCache($this->cache_dir, $environment, $this->environment == $environment);
}
So now I can call this method when I need to clear cache for current or specific environment.