Cannot declare class Spatie\MediaLibrary\UrlGenerator\GcsUrlGenerator because the name is already in use - laravel

I'm having a problem with Spatie Media Library. I created my class to use a different filesystem (specifically a Google bucket). Everything works smooth and I can integrate the filesystem correctly, save and view through the custom url. I created my class and gave what "Spatie" describes in its documentation as a namespace namespace Spatie\MediaLibrary\UrlGenerator;
. However, when I run the "artisan config: cache" command I get the error mentioned above.
Here my Custom Class Code extending BaseUrlGenerator:
namespace Spatie\MediaLibrary\UrlGenerator;
use DateTimeInterface;
use Illuminate\Contracts\Config\Repository as Config;
use Illuminate\Filesystem\FilesystemManager;
class GcsUrlGenerator extends BaseUrlGenerator
{
/** #var \Illuminate\Filesystem\FilesystemManager */
protected $filesystemManager;
public function __construct(Config $config, FilesystemManager $filesystemManager)
{
$this->filesystemManager = $filesystemManager;
parent::__construct($config);
}
/**
* Get the url for a media item.
*
* #return string
*/
public function getUrl(): string
{
$url = $this->getPathRelativeToRoot();
if ($root = config('filesystems.disks.'.$this->media->disk.'.root')) {
$url = $root.'/'.$url;
}
$url = $this->rawUrlEncodeFilename($url);
$url = $this->versionUrl($url);
return config('medialibrary.gcs.domain').'/'.$url;
}
/**
* Get the temporary url for a media item.
*
* #param \DateTimeInterface $expiration
* #param array $options
*
* #return string
*/
public function getTemporaryUrl(DateTimeInterface $expiration, array $options = []): string
{
return $this
->filesystemManager
->disk($this->media->disk)
->temporaryUrl($this->getPath(), $expiration, $options);
}
/**
* Get the url for the profile of a media item.
*
* #return string
*/
public function getPath(): string
{
return $this->getPathRelativeToRoot();
}
/**
* Get the url to the directory containing responsive images.
*
* #return string
*/
public function getResponsiveImagesDirectoryUrl(): string
{
$url = $this->pathGenerator->getPathForResponsiveImages($this->media);
if ($root = config('filesystems.disks.'.$this->media->disk.'.root')) {
$url = $root.'/'.$url;
}
return config('medialibrary.gcs.domain').'/'.$url;
}
}
Here what I included in the published vendor of medialibrary
'custom_url_generator_class' => \Spatie\MediaLibrary\UrlGenerator\GcsUrlGenerator::class,
What I'm missing here?
Thanks for helping me

According to the documentation you should implement the Spatie\MediaLibrary\UrlGenerator interface, not the namespace. Alternatively you can extend Spatie\MediaLibrary\UrlGenerator\BaseUrlGenerator which implements that interface itself. So the namespace of your custom class should still adhere to default naming, meaning it should have namespacing according to the folder structure and classname so it gets autoloaded properly.

Related

Laravel router namespace method

In Laravel documentation routing there is a namespace method.
Route::namespace
I tried to explore what does it really do but couldn't find it's definition in Laravel source codes. Where is it?
It's not related to code, just to group the routes. Like this:
The source is here: https://github.com/laravel/framework/blob/b73691ac7b309cd2c4fb29b32d3eed76fecca58b/src/Illuminate/Routing/RouteGroup.php#L40, it just adds the namespace at end of the current namespace.
You have a controller group like 'Products' for example,
App/
Http/
Controllers/
Products/
Stocks.php
Prices.php
Sizes.php
And you need to modify their namespaces like this to meet the PSR-4 requirements to enable autoloading of controllers:
namespace App\Http\Controllers\Products;
class Stocks {
function index(){
}
}
Then if you want to access the methods of these controllers, you might want to group them with Route::namespace():
Route::namespace("Products")->group(function(){
Route::get("stocks", "Stocks#index");
});
This will search for the Stocks class in the App\Http\Controllers\Products namespace instead of App\Http\Controllers namespace. and call the index method.
Note that you might run composer dumpautoload to let the framework rebuild the autoload.php with the PSR-4 namespaces to make these things effective.
Later Edit:
framework/src/Illuminate/Routing/Router.php defines the Route class, which redirects the Route::namespace method to RouterRegistrar class at this line:
/**
* Dynamically handle calls into the router instance.
*
* #param string $method
* #param array $parameters
* #return mixed
*/
public function __call($method, $parameters)
{
if (static::hasMacro($method)) {
return $this->macroCall($method, $parameters);
}
if ($method == 'middleware') {
return (new RouteRegistrar($this))->attribute($method, is_array($parameters[0]) ? $parameters[0] : $parameters);
}
return (new RouteRegistrar($this))->attribute($method, $parameters[0]);
}
in the last line. And in that method,
/**
* The attributes that can be set through this class.
*
* #var array
*/
protected $allowedAttributes = [
'as', 'domain', 'middleware', 'name', 'namespace', 'prefix',
];
/**
* Set the value for a given attribute.
*
* #param string $key
* #param mixed $value
* #return $this
*
* #throws \InvalidArgumentException
*/
public function attribute($key, $value)
{
if (! in_array($key, $this->allowedAttributes)) {
throw new InvalidArgumentException("Attribute [{$key}] does not exist.");
}
$this->attributes[Arr::get($this->aliases, $key, $key)] = $value;
return $this;
}
namespace attribute is being set, to use in ->group() method.

What is the best way for reusable values throughout the application in Symfony 3?

I want to have a file or list that I can update easily with values that might change throughout my application.
I don't really want to hard code text values into the templates. I prefer to have all of these values in one place and labelled correctly.
Examples of values that might get updated are:
Page title
Logo text
Brand or company name
I have thought about two options:
Add them to the twig config in config.yml. This is a bit messy and doesn't seem organised if I decide to put a lot of values there.
Make a database table for these and include the entity in each controller where I need to use the values. This might be creating too much work.
Are there any other options or are one of these more suitable?
Thank you.
You need to create a twig function and use it to return the value you want. For example:
namespace AppBundle\Twig;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
use Symfony\Component\DependencyInjection\ContainerInterface;
class TwigExtension extends \Twig_Extension implements ContainerAwareInterface
{
use ContainerAwareTrait;
/**
* #var ContainerInterface
*/
protected $container;
public function getFunctions()
{
return array(
new \Twig_SimpleFunction('parameter', function($name)
{
try {
return $this->container->getParameter($name);
} catch(\Exception $exception) {
return "";
}
})
);
}
/**
* Returns the name of the extension.
*
* #return string The extension name
*/
public function getName()
{
return 'app.twig.extension';
}
}
This will create a function called parameter and once you call it in twig {{ parameter('my.parameter') }} it will return the parameter. You need to load it as a service, which you can do by adding the following to your services.yml file:
app.twig.extension:
class: AppBundle\Twig\TwigExtension
calls:
- [setContainer, ["#service_container"]]
tags:
- { name: twig.extension }
From personal experience people usually want to be able to change some of the parameters. This is why I usually prefer to create a Setting or Parameter entity which would look something like this:
/**
* Setting
*
* #ORM\Table(name="my_parameters")
* #ORM\Entity(repositoryClass="AppBundle\Repository\ParameterRepository")
*/
class Parameter
{
/**
* #var integer
*
* #ORM\Id
* #ORM\Column(name="parameter_id", type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* #var string
*
* #ORM\Column(name="value", type="text", nullable=true)
*/
private $value;
/**
* #param string|null $name
* #param string|null $value
*/
public function __construct($name = null, $value = null)
{
$this->setName($name);
$this->setValue($value);
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*
* #return Parameter
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set value
*
* #param string $value
*
* #return Parameter
*/
public function setValue($value = null)
{
$this->value = serialize($value);
return $this;
}
/**
* Get value
*
* #return string
*/
public function getValue()
{
$data = #unserialize($this->value);
return $this->value === 'b:0;' || $data !== false ? $this->value = $data : null;
}
}
Then I would add a CompilerPass which will help get all of the parameters from the database and cache them so that your app doesn't make unnecessary sql queries to the database. That might look something similar to the following class:
// AppBundle/DependencyInjection/Compiler/ParamsCompilerPass.php
namespace AppBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class ParamsCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$em = $container->get('doctrine.orm.default_entity_manager');
$settings = $em->getRepository('AppBundle:Parameter')->findAll();
foreach($settings as $setting) {
// I like to prefix the parameters with "app."
// to avoid any collision with existing parameters.
$container->setParameter('app.'.strtolower($setting->getName()), $setting->getValue());
}
}
}
And finally, in your bundle class (i.e. src/AppBundle/AppBundle.php) you add the compiler pass:
namespace AppBundle;
use AppBundle\DependencyInjection\Compiler\ParamsCompilerPass;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class AppBundle extends Bundle
{
public function build(ContainerBuilder $builder)
{
parent::build($builder);
$builder->addCompilerPass(new ParamsCompilerPass(), , PassConfig::TYPE_AFTER_REMOVING);
}
}
Now you can create a DoctrineFixture template to load the parameters you use all the time. With the TwigExtension you will still be able to call the parameter from the twig template and you can create a web UI to change some of the parameters/settings.

Laravel5 extend Facade

I want to extend Laravel5 Cookies functionality.
I want to make it this way:
I will create file App\Support\Facades\Cookie.php and than file App\Libraries\CookieJar.php. In app.php I will change row for Cookie to this:
'Cookie' => 'App\Support\Facades\Cookie',
Anyway, when I try to use it like this:
Cookie::test()
it returns:
Call to undefined method Illuminate\Cookie\CookieJar::test()
Do you have any idea, why it do this? And is the way, how I want to extend Cookie functionality good?
Thank you for your help.
Here is content of files:
Cookie.php:
<?php namespace App\Support\Facades;
/**
* #see \App\Libraries\CookieJar
*/
class Cookie extends \Illuminate\Support\Facades\Facade
{
/**
* Determine if a cookie exists on the request.
*
* #param string $key
* #return bool
*/
public static function has($key)
{
return !is_null(static::$app['request']->cookie($key, null));
}
/**
* Retrieve a cookie from the request.
*
* #param string $key
* #param mixed $default
* #return string
*/
public static function get($key = null, $default = null)
{
return static::$app['request']->cookie($key, $default);
}
/**
* Get the registered name of the component.
*
* #return string
*/
protected static function getFacadeAccessor()
{
return 'cookie';
}
}
CookieJar.php:
<?php namespace App\Libraries;
class CookieJar extends \Illuminate\Cookie\CookieJar
{
public function test() {
return 'shit';
}
}
The class with all your new cookie functions need to extend Illuminate\CookieJar\CookieJar
<?php
namespace App\Support\Cookie;
class CookieJar extends \Illuminate\Cookie\CookieJar
{
/**
* Determine if a cookie exists on the request.
*
* #param string $key
* #return bool
*/
public static function has($key)
{
return !is_null(static::$app['request']->cookie($key, null));
}
/**
* Retrieve a cookie from the request.
*
* #param string $key
* #param mixed $default
* #return string
*/
public static function get($key = null, $default = null)
{
return static::$app['request']->cookie($key, $default);
}
}
Then make a new facade:
namespace App\Support\Facades;
class CookieFacade extends \Illuminate\Support\Facades\Facade
{
protected static function getFacadeAccessor()
{
/*
* You can't call it cookie or else it will clash with
* the original cookie class in the container.
*/
return 'NewCookie';
}
}
Now bing it in the container:
$this->app->bind("NewCookie", function() {
$this->app->make("App\\Support\\Cookie\\CookieJar");
});
Finally add the alias in your app.php config:
'NewCookie' => App\Support\Facades\CookieFacade::class
Now you can use NewCookie::get('cookie') and NewCookie::has('cookie').
I hope this helps.

Specify a different public path

My Laravel app works in the private folder and I need to tell Laravel that the public path is different.
Today I have upgraded a Laravel app from 4.2 to 5.0 and I can't find where we specify the public path since the paths.php file doesn't exist anymore in Laravel 5.0.
In laravel 4.2 we had the /bootstrap/paths.php file:
/*
|--------------------------------------------------------------------------
| Public Path
|--------------------------------------------------------------------------
|
| The public path contains the assets for your web application, such as
| your JavaScript and CSS files, and also contains the primary entry
| point for web requests into these applications from the outside.
|
*/
'public' => __DIR__.'/../../../public_html',
I'm not used with the Laravel 5.0 folders structure yet, any help would be greatly appreciated.
What worked for me flawlessly was adding to public/index.php the following three lines:
$app->bind('path.public', function() {
return __DIR__;
});
This was answered at Laracast.
I think that this could be made in different ways, here is mine.
Create a new helper file. You can create it at Services folder:
# app/Services/helper.php
if ( ! function_exists('private_path')){
function private_path($path = ''){
return app_path() . 'private/'
}
}
A good place to import the helper file is in AppServiceProvider that resides at app/Providers/AppServiceProvider.php. Use the boot to do so.
public function boot()
{
include __dir__ . "/../Services/helper.php";
}
Rename the folder from public to private, and finally you can call your own function from anywhere as:
$path = private_path();
According to this post, to replace the original public path we need to override the application paths:
<?php namespace App;
use Illuminate\Foundation\Application;
class MyApp extends Application {
protected $appPaths = array();
/**
* Create a new Illuminate application instance.
*
* #param array|null $appPaths
* #return \MyApp
*/
public function __construct($appPaths = null)
{
$this->registerBaseBindings();
$this->registerBaseServiceProviders();
$this->registerCoreContainerAliases();
if (!is_array($appPaths)) {
abort(500, '_construct requires paths array');
}
if (!isset($appPaths['base'])) {
abort(500, '_construct requires base path');
}
$this->appPaths = $appPaths;
$this->setBasePath($appPaths['base']);
}
/**
* Set the base path for the application.
*
* #param string $basePath
* #return $this
*/
public function setBasePath($basePath)
{
$this->basePath = $basePath;
$this->bindPathsInContainer();
return $this;
}
/**
* Bind all of the application paths in the container.
*
* #return void
*/
protected function bindPathsInContainer()
{
$this->instance('path', $this->path());
foreach (['base', 'config', 'database', 'lang', 'public', 'storage'] as $path)
{
$this->instance('path.'.$path, $this->{$path.'Path'}());
}
}
/**
* Get the path to the application "app" directory.
*
* #return string
*/
public function path()
{
return $this->basePath.'/app';
}
/**
* Get the base path of the Laravel installation.
*
* #return string
*/
public function basePath()
{
return $this->basePath;
}
/**
* Get the path to the application configuration files.
*
* #return string
*/
public function configPath()
{
if (isset($this->appPaths['config'])) {
return $this->appPaths['config'];
}
return $this->basePath.'/config';
}
/**
* Get the path to the database directory.
*
* #return string
*/
public function databasePath()
{
if (isset($this->appPaths['database'])) {
return $this->appPaths['database'];
}
return $this->basePath.'/database';
}
/**
* Get the path to the language files.
*
* #return string
*/
public function langPath()
{
if (isset($this->appPaths['lang'])) {
return $this->appPaths['lang'];
}
return $this->basePath.'/resources/lang';
}
/**
* Get the path to the public / web directory.
*
* #return string
*/
public function publicPath()
{
if (isset($this->appPaths['public'])) {
return $this->appPaths['public'];
}
return $this->basePath.'/public';
}
/**
* Get the path to the storage directory.
*
* #return string
*/
public function storagePath()
{
if (isset($this->appPaths['storage'])) {
return $this->appPaths['storage'];
}
return $this->basePath.'/storage';
}
}
This seems weird for me and as it's been mentioned in the post, it feels like we did a step back in Laravel's functionalities, I hope they will change it in a future update.

FOSRestbundle, JMS Serializer and SonataMediaBundle return public url for image

I'm trying to return a JSON object('Module') with a ManyToOne link to an Sonata\MediaBundle\Entity through FOSRestBundle and JMS Serializer. How should I go about doing that?
Here's a hack that I did, but dont think it's the best practise.
class Module
{
...
/**
* #var Application\Sonata\MediaBundle\Entity\Media
*
* #ORM\ManyToOne(targetEntity="Application\Sonata\MediaBundle\Entity\Media", inversedBy="module")
* #ORM\JoinColumn(name="hero_image_id", referencedColumnName="id")
* #JMS\Expose()
*/
private $heroImage;
...
}
class Media extends BaseMedia
{
...
/**
* A Quick hack not the best method.
*
* #JMS\VirtualProperty
* #JMS\SerializedName("url")
*
* #return string
*/
public function getUrlMethod()
{
global $kernel;
$imageProvider = $kernel->getContainer()->get('sonata.media.provider.image');
return $imageProvider->generatePublicUrl($this, 'reference');
}
...
}
thank you!
EDIT
Thanks to Tautrimas Pajarskas and the post he mention.
Here's the class.
<?php
namespace AXO\APIBundle\Listener\Serialization;
use JMS\DiExtraBundle\Annotation\Service;
use JMS\DiExtraBundle\Annotation\Tag;
use JMS\DiExtraBundle\Annotation\Inject;
use JMS\DiExtraBundle\Annotation\InjectParams;
use JMS\Serializer\EventDispatcher\EventSubscriberInterface;
use JMS\Serializer\EventDispatcher\ObjectEvent;
use JMS\Serializer\GraphNavigator;
/**
* Add data after serialization
*
* #Service("axo.listener.serializationlistener")
* #Tag("jms_serializer.event_subscriber")
*/
class SerializationListener implements EventSubscriberInterface
{
/**
* #inheritdoc
*/
static public function getSubscribedEvents()
{
return array(
array('event' => 'serializer.post_serialize', 'class' => 'Application\Sonata\MediaBundle\Entity\Media', 'method' => 'onPostSerialize'),
);
}
public function onPostSerialize(ObjectEvent $event)
{
global $kernel;
$imageProvider = $kernel->getContainer()->get('sonata.media.provider.image');
$event->getVisitor()->addData('url',$imageProvider->generatePublicUrl($event->getObject(), 'reference'));
}
}
You might want to see Add extra fields using JMS Serializer bundle as it has an example, on how to add additional fields to serialized data that depend on external classes.

Resources