"URL key for specified store already exists." when renameing category
This is an acknowledge issue of Magento 2, it's fixed permanently in 2.2 but it wasn't backported to 2.1. So I will provide you a workaround for it and let see how does it go :)
app/code/Stackoverflow/UrlRewrite/etc/di.xml
<?xml version="1.0"?>
<!--
~ Copyright © 2017 Toan Nguyen. All rights reserved.
~ See COPYING.txt for license details.
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<preference for="Magento\UrlRewrite\Model\Storage\DbStorage" type="Stackoverflow\UrlRewrite\Model\Storage\DbStorage"/>
</config>
app/code/Stackoverflow/UrlRewrite/Model/Storage/DbStorage.php
<?php
/**
* Copyright © 2017 Toan Nguyen. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Stackoverflow\UrlRewrite\Model\Storage;
use Magento\UrlRewrite\Service\V1\Data\UrlRewrite;
use Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory;
class DbStorage extends \Magento\UrlRewrite\Model\Storage\DbStorage
{
/**
* Save new url rewrites and remove old if exist. Template method
*
* @param \Magento\UrlRewrite\Service\V1\Data\UrlRewrite[] $urls
*
* @return void
* @throws \Magento\Framework\Exception\AlreadyExistsException
*/
protected function doReplace($urls)
{
foreach ($this->createFilterDataBasedOnUrls($urls) as $type => $urlData) {
$urlData[UrlRewrite::ENTITY_TYPE] = $type;
$this->deleteByData($urlData);
}
$data = [];
foreach ($urls as $url) {
$urlArray = $url->toArray();
$urlPath = $urlArray['request_path'];
$storeId = $urlArray['store_id'];
$dataKey = $storeId . '..' . $urlPath;
$data[$dataKey] = $urlArray;
}
$this->insertMultiple($data);
}
}
I tried myself a lot of ways/workaround on GitHub and this one seems fixed the problem permanently. Gods know why...
Hope it helps.
I've fixed this issue. And I have 2 solutions for that.
The first one: Clean up your database in table url_rewrite (Change the url_key of all category). You can write UpgradeData script for this solution.
The second one: Remove the duplication data when saving category.
This data is throw in method doReplace($urls) in \vendor\magento\module-url-rewrite\Model\Storage\DbStorage.php
file.
protected function doReplace($urls)
{
foreach ($this->createFilterDataBasedOnUrls($urls) as $type => $urlData) {
$urlData[UrlRewrite::ENTITY_TYPE] = $type;
$this->deleteByData($urlData);
}
$data = [];
foreach ($urls as $url) {
$data[] = $url->toArray();
}
$this->insertMultiple($data);
}
After debugging, I found out $data variable has a duplicate record. If you want this method to work without any errors. Rewrite this method above to:
protected function doReplace($urls)
{
foreach ($this->createFilterDataBasedOnUrls($urls) as $type => $urlData) {
$urlData[UrlRewrite::ENTITY_TYPE] = $type;
$this->deleteByData($urlData);
}
$data = [];
$storeId_requestPaths = [];
foreach ($urls as $url) {
$storeId = $url->getStoreId();
$requestPath = $url->getRequestPath();
// Skip if is exist in the database
$sql = "SELECT * FROM url_rewrite where store_id = $storeId and request_path = '$requestPath'";
$exists = $this->connection->fetchOne($sql);
if ($exists) continue;
$storeId_requestPaths[] = $storeId . '-' . $requestPath;
$data[] = $url->toArray();
}
// Remove duplication data;
$n = count($storeId_requestPaths);
for ($i = 0; $i < $n - 1; $i++) {
for ($j = $i + 1; $j < $n; $j++) {
if ($storeId_requestPaths[$i] == $storeId_requestPaths[$j]) {
unset($data[$j]);
}
}
}
$this->insertMultiple($data);
}
If you want to get more details. Please, read my comment in
https://github.com/magento/magento2/issues/7298
Hope this will help you.