"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.